⚠️ Warning: This library is still in early development (pre-1.0.0). The API may change between minor versions. Please pay attention to updates and changelogs when upgrading.
This package provides type-safe storage utilities with Zod schema validation. It supports multiple storage backends including browser's localStorage, React Native's AsyncStorage, and an in-memory storage for testing.
npm install @nicholascostadev/typed-local-storage
# or
pnpm add @nicholascostadev/typed-local-storage
# or
yarn add @nicholascostadev/typed-local-storage
import { z } from 'zod';
import { createTypedStorage, LocalStorageAdapter } from '@nicholascostadev/typed-local-storage';
const typedStorage = createTypedStorage(new LocalStorageAdapter(), {
user: {
// key is optional, will use "user" if not provided
schema: z.object({
name: z.string(),
age: z.number(),
}),
defaultValue: { name: '', age: 0 },
isJson: true,
},
theme: {
schema: z.enum(['light', 'dark']),
defaultValue: 'light',
},
});
// Get user data
const userData = await typedStorage.user.get();
// ^? { name: string; age: number }
// Set user data
await typedStorage.user.set({ name: 'Jane', age: 25 });
// ^? param typed as { name: string; age: number }
// Remove user data
await typedStorage.user.remove();
// Get theme
const theme = await typedStorage.theme.get();
// ^? 'light' | 'dark'
// Set theme
await typedStorage.theme.set('dark');
// ^? param typed as 'dark' | 'light'
First, install AsyncStorage:
npm install @react-native-async-storage/async-storage
# or
yarn add @react-native-async-storage/async-storage
Then use it with the AsyncStorageAdapter:
import { z } from 'zod';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
createTypedStorage,
AsyncStorageAdapter
} from '@nicholascostadev/typed-local-storage';
// Create a typed storage with AsyncStorage
const typedStorage = createTypedStorage(new AsyncStorageAdapter(AsyncStorage), {
user: {
schema: z.object({
name: z.string(),
age: z.number(),
}),
defaultValue: { name: '', age: 0 },
isJson: true,
},
theme: {
schema: z.enum(['light', 'dark']),
defaultValue: 'light',
},
});
// Usage is the same as before, but uses AsyncStorage under the hood
const userData = await typedStorage.user.get();
await typedStorage.user.set({ name: 'Jane', age: 25 });
import { z } from 'zod';
import {
createTypedStorage,
MemoryStorageAdapter
} from '@nicholascostadev/typed-local-storage';
// Create an in-memory storage adapter
const memoryAdapter = new MemoryStorageAdapter();
// Create a typed storage with in-memory storage
const typedStorage = createTypedStorage(memoryAdapter, {
user: {
schema: z.object({
name: z.string(),
age: z.number(),
}),
defaultValue: { name: '', age: 0 },
isJson: true,
},
});
// Usage is synchronous with MemoryStorageAdapter
const userData = typedStorage.user.get();
typedStorage.user.set({ name: 'Jane', age: 25 });
You can specify a custom key for storage that's different from the object property:
import { z } from 'zod';
import { createTypedStorage, MemoryStorageAdapter } from '@nicholascostadev/typed-local-storage';
const typedStorage = createTypedStorage(new MemoryStorageAdapter(), {
user: {
key: 'app:user:data', // Custom storage key
schema: z.object({
name: z.string(),
age: z.number(),
}),
defaultValue: { name: '', age: 0 },
isJson: true,
},
});
// The data will be stored with the key 'app:user:data'
typedStorage.user.set({ name: 'Jane', age: 25 });
You can provide a custom default value when getting data:
const userData = await typedStorage.user.get({
defaultValue: { name: 'Guest', age: 0 }
});
The library provides several storage adapters:
LocalStorageAdapter
: Uses browser's localStorageMemoryStorageAdapter
: In-memory storage for testing or environments without localStorageAsyncStorageAdapter
: Adapter for React Native's AsyncStorage
You can create your own storage adapter by implementing the StorageAdapter
interface:
import { StorageAdapter } from '@nicholascostadev/typed-local-storage';
class CustomStorageAdapter implements StorageAdapter {
// Synchronous implementation example
getItem(key: string): string | null {
// Your implementation here
return yourCustomStorage.get(key);
}
setItem(key: string, value: string): void {
// Your implementation here
yourCustomStorage.set(key, value);
}
removeItem(key: string): void {
// Your implementation here
yourCustomStorage.delete(key);
}
clear(): void {
// Your implementation here
yourCustomStorage.clear();
}
}
// Asynchronous implementation example
class CustomAsyncStorageAdapter implements StorageAdapter {
async getItem(key: string): Promise<string | null> {
// Your implementation here
return await yourAsyncStorage.get(key);
}
async setItem(key: string, value: string): Promise<void> {
// Your implementation here
await yourAsyncStorage.set(key, value);
}
async removeItem(key: string): Promise<void> {
// Your implementation here
await yourAsyncStorage.delete(key);
}
async clear(): Promise<void> {
// Your implementation here
await yourAsyncStorage.clear();
}
}
// Usage with your custom adapter
const typedStorage = createTypedStorage(new CustomStorageAdapter(), {
// Your storage configuration
});
The return type of your adapter's methods determines whether the storage operations will be synchronous or asynchronous:
- If
getItem
returnsstring | null
, operations will be synchronous - If
getItem
returnsPromise<string | null>
, operations will be asynchronous (wrapped in Promises)
For complete working examples of custom storage adapters, check out the test files in the repository:
src/__tests__/adapters/customAdapter.test.ts
- Contains examples of:- Synchronous custom adapter using a Map
- Asynchronous custom adapter with simulated network delays
- Mock IndexedDB adapter implementation
- Mock WebSQL adapter implementation
src/adapters/memoryStorage.ts
- Simple in-memory storage implementationsrc/adapters/localStorage.ts
- Browser's localStorage adaptersrc/adapters/asyncStorage.ts
- React Native's AsyncStorage adapter
These examples demonstrate different approaches to implementing the StorageAdapter
interface for various storage backends.
Creates a typed storage object with getters and setters for each key.
Parameters:
adapter
: The storage adapter to useconfig
: Configuration object with keys and their schemas
Returns:
- An object with get, set, and remove methods for each key
- Type-safe storage operations
- Zod schema validation
- Support for multiple storages (localStorage, AsyncStorage, in-memory)
- JSON parsing/stringifying support
- Default value fallback
- Error handling with console logging
- TypeScript support with full type inference
- Automatic return type inference based on adapter (sync or async)
- Optional key property (falls back to object key if not provided)
The package includes built-in error handling:
- Returns default value if storage item doesn't exist
- Returns default value if parsing fails
- Returns default value if schema validation fails
- Logs errors to console for debugging in development mode
- Automatically sets default value when errors occur
Contributions are welcome! Please check out the Contributing Guide for detailed instructions on how to contribute to this project.
This project is open-sourced under the MIT License. See the LICENSE file for more details.
Nicholas Costa (nicholascostadev)