UNPKG

@sovgut/state

Version:

<p align="center"> <b>A lightweight, type-safe, and reactive state management library for modern web applications</b> </p>

464 lines (337 loc) â€Ē 11.8 kB
# @sovgut/state <p align="center"> <b>A lightweight, type-safe, and reactive state management library for modern web applications</b> </p> <p align="center"> <img src="https://img.shields.io/npm/v/@sovgut/state" alt="npm version" /> <img src="https://img.shields.io/npm/dm/@sovgut/state" alt="npm downloads" /> <img src="https://img.shields.io/github/license/sovgut/state" alt="license" /> <img src="https://img.shields.io/badge/TypeScript-Ready-blue" alt="TypeScript" /> </p> --- ## 🚀 Features - **ðŸŽŊ Simple & Intuitive API** - Get started in minutes with a familiar localStorage-like API - **ðŸ’ū Multiple Storage Strategies** - Choose between localStorage, sessionStorage, cookies, or in-memory storage - **ðŸ“Ķ TypeScript First** - Full TypeScript support with intelligent type inference - **🔄 Reactive Updates** - Built-in observer pattern for real-time state synchronization - **ðŸ›Ąïļ Type Safety** - Automatic type casting and validation with custom error handling - **ðŸŠķ Lightweight** - Minimal bundle size (10kb) - **🔧 Flexible Configuration** - Fallback values, strict mode, and custom casting options - **🍊 Advanced Cookie Support** - Full cookie options including SameSite, Secure, and expiration ## ðŸ“Ķ Installation ```bash npm install @sovgut/state ``` ```bash yarn add @sovgut/state ``` ```bash pnpm add @sovgut/state ``` ## ðŸŽŊ Quick Start ```typescript import { LocalState, SessionState, MemoryState, CookieState } from "@sovgut/state"; // Store a value LocalState.set("user", { name: "John", age: 30 }); // Retrieve a value const user = LocalState.get("user"); // Listen for changes LocalState.on("user", (event) => { console.log("User updated:", event); }); ``` ## 📖 Table of Contents - [Storage Strategies](#-storage-strategies) - [Basic Usage](#-basic-usage) - [Type Safety & Casting](#-type-safety--casting) - [Observer Pattern](#-observer-pattern) - [React Integration](#-react-integration) - [API Reference](#-api-reference) - [Error Handling](#-error-handling) ## ðŸ’ū Storage Strategies ### LocalState Persists data in `localStorage` - survives browser restarts. ```typescript import { LocalState } from "@sovgut/state"; LocalState.set("theme", "dark"); const theme = LocalState.get("theme"); // "dark" ``` ### SessionState Stores data in `sessionStorage` - cleared when tab closes. ```typescript import { SessionState } from "@sovgut/state"; SessionState.set("tempData", { expires: Date.now() + 3600000 }); const data = SessionState.get("tempData"); ``` ### MemoryState In-memory storage - cleared on page reload. ```typescript import { MemoryState } from "@sovgut/state"; MemoryState.set("cache", new Map([["key", "value"]])); const cache = MemoryState.get("cache"); ``` ### CookieState HTTP cookie storage with advanced options. ```typescript import { CookieState } from "@sovgut/state"; CookieState.set("sessionId", "abc123", { expires: 7, // days secure: true, sameSite: "strict" }); ``` ## 🔧 Basic Usage ### Setting Values All storage types support any serializable JavaScript value: ```typescript // Primitives LocalState.set("count", 42); LocalState.set("name", "Alice"); LocalState.set("isActive", true); LocalState.set("bigNumber", 9007199254740991n); // Objects and Arrays LocalState.set("user", { id: 1, name: "Bob" }); LocalState.set("tags", ["javascript", "typescript"]); // Complex structures LocalState.set("settings", { theme: "dark", notifications: { email: true, push: false }, favorites: ["dashboard", "profile"] }); ``` ### Getting Values #### Basic Retrieval ```typescript const count = LocalState.get("count"); // 42 const user = LocalState.get("user"); // { id: 1, name: "Bob" } const missing = LocalState.get("nonexistent"); // undefined ``` #### With Fallback Values ```typescript // Returns fallback if key doesn't exist const theme = LocalState.get("theme", { fallback: "light" }); // "light" // Fallback also determines the return type const score = LocalState.get("score", { fallback: 0 }); // number const tags = LocalState.get("tags", { fallback: [] as string[] }); // string[] ``` #### Strict Mode ```typescript // Throws StateDoesNotExist error if key is missing try { const required = LocalState.get("required", { strict: true }); } catch (error) { console.error("Key does not exist:", error.message); } ``` ### Removing Values ```typescript // Remove single item LocalState.remove("user"); // Clear all items LocalState.clear(); // Check existence if (LocalState.has("user")) { LocalState.remove("user"); } ``` ## 🎭 Type Safety & Casting ### Automatic Type Inference ```typescript // TypeScript infers types from fallback values const count = LocalState.get("count", { fallback: 0 }); // number const name = LocalState.get("name", { fallback: "" }); // string const items = LocalState.get("items", { fallback: [] as Item[] }); // Item[] ``` ### Explicit Type Casting ```typescript // Cast stored values to specific types LocalState.set("stringNumber", "42"); const asString = LocalState.get("stringNumber", { cast: "string" }); // "42" const asNumber = LocalState.get("stringNumber", { cast: "number" }); // 42 const asBoolean = LocalState.get("stringNumber", { cast: "boolean" }); // true const asBigInt = LocalState.get("stringNumber", { cast: "bigint" }); // 42n ``` ### Generic Type Parameters ```typescript interface User { id: number; name: string; email: string; } // Explicitly type the return value const user = LocalState.get<User>("currentUser"); // With strict mode const user = LocalState.get<User>("currentUser", { strict: true }); ``` ## 👁ïļ Observer Pattern ### Event Listeners ```typescript import { type IStorageEventData } from "@sovgut/state"; // Listen for all changes to a key LocalState.on("user", (event: IStorageEventData) => { console.log("User changed:", event); }); // Listen once LocalState.once("notification", (event) => { console.log("Notification received:", event); }); // Remove specific listener const handler = (event: IStorageEventData) => console.log(event); LocalState.on("data", handler); LocalState.off("data", handler); // Remove all listeners LocalState.removeAllListeners(); ``` ### Cross-Component State Sync ```typescript // Component A LocalState.set("sharedState", { count: 1 }); // Component B - automatically receives updates LocalState.on("sharedState", (event) => { console.log("State updated in Component A:", event); }); ``` ## ⚛ïļ React Integration ### Custom Hook Example ```typescript import { useEffect, useState, useCallback } from "react"; import { LocalState, type IStorageEventData } from "@sovgut/state"; function useLocalState<T>(key: string, initialValue: T) { const [value, setValue] = useState<T>(() => LocalState.get(key, { fallback: initialValue }) ); useEffect(() => { const handler = (event: IStorageEventData<T>) => { setValue(event ?? initialValue); }; LocalState.on(key, handler); return () => LocalState.off(key, handler); }, [key, initialValue]); const updateValue = useCallback((newValue: T) => { LocalState.set(key, newValue); }, [key]); return [value, updateValue] as const; } // Usage function App() { const [theme, setTheme] = useLocalState("theme", "light"); return ( <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}> Current theme: {theme} </button> ); } ``` ### Context Provider Pattern ```typescript import React, { createContext, useContext, useEffect, useState } from "react"; import { LocalState, type IStorageEventData } from "@sovgut/state"; interface AppState { user: User | null; settings: Settings; } const StateContext = createContext<{ state: AppState; updateUser: (user: User | null) => void; updateSettings: (settings: Settings) => void; }>({} as any); export function StateProvider({ children }: { children: React.ReactNode }) { const [state, setState] = useState<AppState>({ user: LocalState.get("user", { fallback: null }), settings: LocalState.get("settings", { fallback: defaultSettings }) }); useEffect(() => { const handleUserChange = (event: IStorageEventData<User>) => { setState(prev => ({ ...prev, user: event })); }; const handleSettingsChange = (event: IStorageEventData<Settings>) => { setState(prev => ({ ...prev, settings: event ?? defaultSettings })); }; LocalState.on("user", handleUserChange); LocalState.on("settings", handleSettingsChange); return () => { LocalState.off("user", handleUserChange); LocalState.off("settings", handleSettingsChange); }; }, []); const updateUser = (user: User | null) => LocalState.set("user", user); const updateSettings = (settings: Settings) => LocalState.set("settings", settings); return ( <StateContext.Provider value={{ state, updateUser, updateSettings }}> {children} </StateContext.Provider> ); } export const useAppState = () => useContext(StateContext); ``` ## 📚 API Reference ### Common Methods All storage classes (`LocalState`, `SessionState`, `MemoryState`, `CookieState`) share these methods: #### `get<T>(key: string, options?: GetOptions<T>): T | undefined` Retrieves a value from storage. **Options:** - `fallback?: T` - Default value if key doesn't exist - `strict?: boolean` - Throw error if key doesn't exist - `cast?: 'string' | 'number' | 'boolean' | 'bigint'` - Type casting #### `set<T>(key: string, value: T, options?: SetOptions): void` Stores a value in storage. **Cookie-specific options:** - `expires?: Date | number` - Expiration date or days from now - `maxAge?: number` - Maximum age in seconds - `domain?: string` - Cookie domain - `path?: string` - Cookie path - `secure?: boolean` - HTTPS only - `sameSite?: 'strict' | 'lax' | 'none'` - CSRF protection #### `remove(key: string): void` Removes a value from storage. #### `clear(): void` Removes all values from storage. #### `has(key: string): boolean` Checks if a key exists in storage. ### Observer Methods #### `on<T>(event: string, callback: (event: IStorageEventData<T>) => void): void` Adds an event listener. #### `once<T>(event: string, callback: (event: IStorageEventData<T>) => void): void` Adds a one-time event listener. #### `off<T>(event: string, callback: (event: IStorageEventData<T>) => void): void` Removes an event listener. #### `removeAllListeners(): void` Removes all event listeners. ## ðŸ›Ąïļ Error Handling ### Built-in Error Types ```typescript import { StateDoesNotExist, StateInvalidCast } from "@sovgut/state"; // Handle missing keys try { const data = LocalState.get("required", { strict: true }); } catch (error) { if (error instanceof StateDoesNotExist) { console.error(`Key "${error.key}" not found in ${error.storage}`); } } // Handle invalid casts try { LocalState.set("invalid", "not-a-number"); const num = LocalState.get("invalid", { cast: "number", strict: true }); } catch (error) { if (error instanceof StateInvalidCast) { console.error(`Cannot cast "${error.value}" to ${error.type} for key "${error.key}" in ${error.storage}`); } } ``` ## ðŸĪ Contributing If you find any issues or have suggestions for improvements, feel free to open an issue or submit a pull request on [GitHub](https://github.com/sovgut/state). ## 📄 License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. --- <p align="center"> Made with âĪïļ by <a href="https://github.com/sovgut">sovgut</a> </p> <p align="center"> <a href="https://github.com/sovgut/state">GitHub</a> â€Ē <a href="https://www.npmjs.com/package/@sovgut/state">npm</a> â€Ē <a href="#-api-reference">Documentation</a> </p>