DEV Community

Ayat Saadat
Ayat Saadat

Posted on

ayat saadati — Complete Guide

Ayat Saadati Core Utilities

I've been building web applications for years, and one recurring challenge always crops up: how do you manage state effectively without ballooning your bundle size or getting tangled in overly complex abstractions? And then there's the constant need for those small, but mighty, utility functions that just make day-to-day development smoother. It's often tempting to pull in massive libraries for every little thing, but my philosophy has always leaned towards "less is more" – especially when it comes to client-side performance.

That's precisely why I started piecing together what I now call ayat-saadati-core. It's not a full-blown framework; honestly, the world has enough of those. Instead, think of it as a carefully curated toolbox. It’s a lightweight collection of essential JavaScript utilities, focusing on reactive state management and common functional programming patterns. My goal was to create something truly lean, performant, and intuitive, built on modern JavaScript, that you can sprinkle into any project – big or small – without a second thought.

Whether you’re building a complex single-page application and need a simple, performant way to manage global state, or you're just looking for a reliable debounce function that doesn't drag in half of Lodash, ayat-saadati-core aims to be your go-to.


🚀 Overview

ayat-saadati-core offers a minimalist yet powerful set of tools designed to enhance developer experience and application performance. At its heart, it provides a very simple, observable state management solution, alongside a selection of battle-tested functional utilities and a few convenient DOM helpers.

Key Features:

  • Reactive Stores: Simple, highly performant observable stores for managing application state. Subscribe to changes, update values, and let your UI react automatically.
  • Functional Utilities: Essential helpers like debounce, throttle, pipe, memoize, and mergeDeep – all implemented with performance and minimal footprint in mind.
  • Lightweight DOM Helpers: A few targeted functions for common DOM interactions without the overhead of a full-fledged manipulation library.
  • TypeScript Support: Built with TypeScript from the ground up, providing excellent type inference and safety.
  • Zero Dependencies: Seriously, nothing else required. Just pure, unadulterated JavaScript.

I really believe in the power of small, composable pieces. This library embodies that. You pick the parts you need, and the rest just stays out of your way.


⚙️ Installation

Getting ayat-saadati-core into your project is straightforward. You can use npm, yarn, or even just a good old-fashioned CDN link for quick prototyping.

Via npm:

npm install ayat-saadati-core
Enter fullscreen mode Exit fullscreen mode

Via yarn:

yarn add ayat-saadati-core
Enter fullscreen mode Exit fullscreen mode

Via CDN (for prototyping or simple scripts):

<!-- For a quick start in a browser environment -->
<script src="https://unpkg.com/ayat-saadati-core@latest/dist/index.umd.js"></script>
<script>
  // Access global 'ayatSaadatiCore' object
  const { createStore } = ayatSaadatiCore;
  const count = createStore(0);
  count.subscribe((value) => console.log('Current count:', value));
  count.set(1); // Output: Current count: 1
</script>
Enter fullscreen mode Exit fullscreen mode

💡 Usage

Let's dive into how you can put ayat-saadati-core to work.

Reactive Stores (createStore)

This is probably the most used feature. The createStore function gives you a simple, reactive container for any piece of data. When the data changes, any subscribed listeners are notified. It's fantastic for anything from a simple counter to complex user profiles.

Basic Counter Example:

import { createStore } from 'ayat-saadati-core';

// Create a store with an initial value
const counter = createStore(0);

// Subscribe to changes in the store
const unsubscribe = counter.subscribe((value) => {
  console.log(`The counter is now: ${value}`);
  // Imagine updating a UI element here
  document.getElementById('counter-display').textContent = String(value);
});

// Update the store's value
counter.set(1);
// Output: The counter is now: 1

counter.set(counter.get() + 1); // Or counter.update((n) => n + 1)
// Output: The counter is now: 2

// You can also update based on the current value
counter.update((currentValue) => currentValue * 2);
// Output: The counter is now: 4

// Don't forget to unsubscribe when the component using it is unmounted
// This prevents memory leaks, especially in single-page applications.
// unsubscribe();
Enter fullscreen mode Exit fullscreen mode

Storing Objects and Complex State:

Stores aren't just for primitives! They handle objects and arrays just as gracefully.

import { createStore } from 'ayat-saadati-core';

interface User {
  id: string;
  name: string;
  email: string;
  isActive: boolean;
}

const userProfile = createStore<User | null>(null);

userProfile.subscribe((user) => {
  if (user) {
    console.log(`User ${user.name} (${user.email}) is ${user.isActive ? 'active' : 'inactive'}`);
  } else {
    console.log('No user logged in.');
  }
});

userProfile.set({
  id: '123',
  name: 'Ayat Saadati',
  email: 'ayat@example.com',
  isActive: true,
});
// Output: User Ayat Saadati (ayat@example.com) is active

// Updating a part of the object state:
userProfile.update((currentUser) => ({
  ...currentUser,
  isActive: false,
}));
// Output: User Ayat Saadati (ayat@example.com) is inactive

// Clearing the user profile
userProfile.set(null);
// Output: No user logged in.
Enter fullscreen mode Exit fullscreen mode

The update method is particularly handy for objects, as it encourages immutable updates by spreading the existing state and then overriding specific properties. This helps avoid tricky bugs down the line.

Functional Utilities

These are the unsung heroes that make a developer's life so much easier.

debounce(func, delay)

Prevents a function from being called too frequently. Useful for things like search inputs, resizing events, or any event that fires rapidly.

import { debounce } from 'ayat-saadati-core';

const searchInput = document.getElementById('search-box');
const searchResults = document.getElementById('search-results');

const performSearch = (query: string) => {
  console.log(`Searching for: "${query}"...`);
  // In a real app, you'd make an API call here
  searchResults.textContent = `Displaying results for: "${query}"`;
};

// Only call performSearch 300ms after the user stops typing
const debouncedSearch = debounce(performSearch, 300);

searchInput.addEventListener('input', (event) => {
  const query = (event.target as HTMLInputElement).value;
  debouncedSearch(query);
});
Enter fullscreen mode Exit fullscreen mode

throttle(func, limit)

Ensures a function is called at most once within a specified time window. Great for scroll events, drag events, or animation frame requests.

import { throttle } from 'ayat-saadati-core';

const scrollContainer = document.getElementById('scroll-area');
const scrollStatus = document.getElementById('scroll-status');

const handleScroll = () => {
  const position = scrollContainer.scrollTop;
  console.log('Scrolled to:', position);
  scrollStatus.textContent = `Scroll position: ${position}`;
};

// Limit scroll event handling to once every 100ms
const throttledScroll = throttle(handleScroll, 100);

scrollContainer.addEventListener('scroll', throttledScroll);
Enter fullscreen mode Exit fullscreen mode

memoize(func)

Caches the results of expensive function calls. If the function is called again with the same arguments, the cached result is returned instead of re-executing the function.

import { memoize } from 'ayat-saadati-core';

let calculationCount = 0;

const expensiveCalculation = (a: number, b: number): number => {
  calculationCount++;
  console.log(`Performing expensive calculation for ${a}, ${b}...`);
  // Simulate heavy computation
  let result = 0;
  for (let i = 0; i < 10000000; i++) {
    result += Math.sqrt(i * a) + Math.sin(i * b);
  }
  return result;
};

const memoizedCalculation = memoize(expensiveCalculation);

console.time('First call');
console.log('Result 1:', memoizedCalculation(5, 10)); // Actual calculation
console.timeEnd('First call');
// Output: Performing expensive calculation for 5, 10...
// Output: Result 1: [some number]
// Output: First call: ~100ms (or more, depending on CPU)

console.time('Second call (same args)');
console.log('Result 2:', memoizedCalculation(5, 10)); // Cached result
console.timeEnd('Second call (same args)');
// Output: Result 2: [same number]
// Output: Second call (same args): ~0.1ms (instant)

console.time('Third call (different args)');
console.log('Result 3:', memoizedCalculation(3, 7)); // Actual calculation
console.timeEnd('Third call (different args)');
// Output: Performing expensive calculation for 3, 7...
// Output: Result 3: [another number]
// Output: Third call (different args): ~100ms

console.log('Total calculations performed:', calculationCount); // Should be 2
Enter fullscreen mode Exit fullscreen mode

pipe(...funcs) and compose(...funcs)

Functional composition allows you to chain functions together, making your code more readable and modular. pipe executes functions from left-to-right, while compose executes from right-to-left (traditional mathematical composition). I personally tend to favor pipe for readability in JavaScript, as it flows naturally.

import { pipe, compose } from 'ayat-saadati-core';

const add5 = (num: number) => num + 5;
const multiplyBy2 = (num: number) => num * 2;
const subtract10 = (num: number) => num - 10;

// pipe: (x + 5) * 2 - 10
const calculatePipe = pipe(add5, multiplyBy2, subtract10);
console.log('Pipe result (initial 10):', calculatePipe(10)); // (10+5)*2 - 10 = 15*2 - 10 = 30 - 10 = 20

// compose: (x - 10) * 2 + 5
const calculateCompose = compose(add5, multiplyBy2, subtract10);
console.log('Compose result (initial 10):', calculateCompose(10)); // (10-10)*2 + 5 = 0*2 + 5 = 5
Enter fullscreen mode Exit fullscreen mode

mergeDeep(target, source)

A recursive object merger. Useful when you need to combine configuration objects or update nested state without losing existing properties.


typescript
import { mergeDeep } from 'ayat-saadati-core';

const defaultSettings = {
  theme: 'dark',
  notifications: {
    email: true,
    sms: false,
    push: true,
  },
  preferences: {
    language: 'en',
    timezone: 'UTC',
  },
};

const userSettings = {
  notifications: {
    sms: true, // User enabled SMS
  },
  preferences: {
    timezone: 'America/New_York', // User changed timezone
  },
};

const finalSettings = mergeDeep(defaultSettings, userSettings);
console.log(finalSettings);
/*
Output:
{
  theme: 'dark',
  notifications: {
    email: true,
    sms: true, // Merged
    push: true,
  },
  preferences: {
    language: 'en',
    timezone: 'America/New_York', // Merged
  },
}
*/

// It also creates
Enter fullscreen mode Exit fullscreen mode

Top comments (0)