@sovgut/state
TypeScript icon, indicating that this package has built-in type declarations

2.1.13 • Public • Published

@sovgut/state

A lightweight, type-safe, and reactive state management library for modern web applications

npm version npm downloads license TypeScript


🚀 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

npm install @sovgut/state
yarn add @sovgut/state
pnpm add @sovgut/state

🎯 Quick Start

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

LocalState

Persists data in localStorage - survives browser restarts.

import { LocalState } from "@sovgut/state";

LocalState.set("theme", "dark");
const theme = LocalState.get("theme"); // "dark"

SessionState

Stores data in sessionStorage - cleared when tab closes.

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.

import { MemoryState } from "@sovgut/state";

MemoryState.set("cache", new Map([["key", "value"]]));
const cache = MemoryState.get("cache");

CookieState

HTTP cookie storage with advanced options.

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:

// 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

const count = LocalState.get("count"); // 42
const user = LocalState.get("user"); // { id: 1, name: "Bob" }
const missing = LocalState.get("nonexistent"); // undefined

With Fallback Values

// 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

// 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

// 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 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

// 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

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

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

// 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

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

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

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.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


Made with ❤️ by sovgut

GitHubnpmDocumentation

Package Sidebar

Install

npm i @sovgut/state

Weekly Downloads

73

Version

2.1.13

License

MIT

Unpacked Size

45.2 kB

Total Files

13

Last publish

Collaborators

  • sergey_sovgut