@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
Markdown
# @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>