storage-namespaced
v1.0.0
Published
A typescript library that maintains all items in a single object that is a value of a Storage implementation (localStorage, sessionStorage, etc.)
Maintainers
Readme
Storage Namespaced
A TypeScript library that provides a namespaced, Map-like API for any Storage implementation (like localStorage or sessionStorage). It stores typed JSON-compatible values within a single JSON object under a specific namespace key.
Features
- Typed Values: Store numbers, booleans, strings, arrays, objects, and null — no manual
JSON.stringify/JSON.parseneeded. - Namespacing: Keeps the global storage clean by grouping all items into a single JSON blob under one namespace key.
- Type Safe: TypeScript generic support for defining typed storage keys.
- Auto-Save: Automatically persist changes to the underlying storage or toggle it for performance.
- Cross-Tab Sync: Automatically syncs in-memory data across browser tabs via the
storageevent. No configuration needed. - Resilience: Gracefully handle or reset corrupted storage data.
Installation
npm install storage-namespacedQuick Start
Usage with Schema
import { StorageNamespaced } from 'storage-namespaced';
type MySchema = {
theme: string;
count: number;
tags: string[];
};
const storage = new StorageNamespaced<MySchema>({
namespace: 'my-app-settings',
storage: localStorage
});
storage.setItem('theme', 'dark');
storage.setItem('count', 42);
storage.setItem('tags', ['important', 'pinned']);
const theme = storage.getItem('theme'); // "dark"
const count = storage.getItem('count'); // 42 (number)
const tags = storage.getItem('tags'); // ['important', 'pinned']
console.log(storage.length); // 3
storage.removeItem('count');Schema values are constrained to JSON-compatible types — Function, Symbol, Map, and other non-serializable types will be rejected by TypeScript.
Usage without Schema
If you don't provide a type argument, the storage accepts any JSON-compatible key-value pairs:
const storage = new StorageNamespaced({
namespace: 'my-app-data',
storage: localStorage
});
storage.setItem('anything', 'value');
storage.setItem('count', 42);
storage.setItem('tags', ['a', 'b']);
const val = storage.getItem('count'); // JSONCompatible | nullWithout a schema, getItem returns JSONCompatible | null and keys are not constrained at compile time.
Usage with Partial Schema (Extra Keys Allowed)
If you want typed keys for some items while still allowing arbitrary extra keys, use an intersection with an index signature:
type MySchema = {
theme: string;
count: number;
tags: string[];
} & Record<string, any>;
const storage = new StorageNamespaced<MySchema>({
namespace: 'my-app-settings',
storage: localStorage
});
storage.getItem('theme'); // string | null
storage.getItem('count'); // number | null
storage.getItem('unknown-key'); // any | null (allowed)Known keys return their specific types. Any extra key compiles without error.
API Reference
new StorageNamespaced(options)
Creates a new namespaced storage instance. If an instance already exists for the given namespace and storage, that instance is returned and its configuration is updated to match the new options.
namespace: The unique key used to store the namespaced data.storage: The storage implementation to use (e.g.,localStorage,sessionStorage).autoSave(default:true): Automatically persist to storage on every change.- Performance Tip: Disable this for batch updates (e.g., in a loop) to avoid redundant
JSON.stringifyand disk write operations. Call.save()manually when finished.
- Performance Tip: Disable this for batch updates (e.g., in a loop) to avoid redundant
resetInvalidStorage(default:true): If existing data is corrupted, reset to an empty object instead of throwing.
.length
A read-only property that returns the number of items in the namespace.
.getItem(key)
Returns the typed value for the key, or null if the key does not exist.
.setItem(key, value)
Sets the value for the specified key and persists it (if autoSave is true). Values can be any JSON-compatible type: string, number, boolean, null, arrays, or plain objects. Non-serializable values (function, Symbol, bigint, undefined, NaN, Infinity, circular references, class instances) throw TypeError.
Performance Note: For batch operations (setting 50+ keys) or large storage blobs (>100KB), disable autoSave and call .save() manually once to avoid redundant JSON.stringify calls.
.removeItem(key)
Removes the specified element from storage.
.key(index)
Returns the name of the nth key in the namespace, or null if the index is out of bounds.
.clear()
Removes all elements from the namespace.
.toObject()
Returns a deep clone of the entire namespaced storage object as a null-prototype object.
.load()
Loads the data from the underlying storage into the in-memory cache.
.save()
Manually persists the current internal state to the storage implementation.
.enableAutoSave()
Enables automatic persistence to storage and immediately saves if there are pending changes.
.disableAutoSave()
Disables automatic persistence to storage. Useful for performing batch updates to avoid redundant write operations.
Differences from Native Storage
While StorageNamespaced provides a similar API to native Storage, there are key architectural differences:
| Feature | Native Storage (localStorage) | StorageNamespaced |
| :--- | :--- | :--- |
| Value Types | All values coerced to strings. | Typed JSON-compatible values (strings, numbers, booleans, arrays, objects, null). |
| Storage Structure | Multiple global keys in the storage area. | A single JSON blob under one namespace key. |
| Persistence | Synchronous and immediate. | Synchronous. Can be immediate (autoSave) or manual. |
| Shadowing Protection | Exotic: Methods and length cannot be shadowed. | Exotic: Mimics native parity (Interface-First). |
| Instance Registry | Globals always available. | Singleton: same instance per namespace/storage pair. |
| Memory | Handled by the browser engine. | Maintains an in-memory cache for performance. |
| Key Ordering | Integer-like keys sorted first. | Uses Map (insertion order). Numeric keys reordered after save+load cycle (JSON.parse Object.keys limitation). |
| __proto__ in Reflection | Included in Object.keys(), Object.getOwnPropertyNames(), Reflect.ownKeys(). | Excluded from Object.keys(), Object.getOwnPropertyNames(), Reflect.ownKeys() for prototype pollution safety. |
Cross-Tab Synchronization
When data is persisted to storage in one tab (via save() or autoSave), the browser fires a storage event in other tabs. StorageNamespaced listens for this event internally to keep its in-memory cache in sync. Note that with autoSave: false, changes are in-memory only until save() is called — other tabs won't see them until then. Sync is only available in browser environments (not Web Workers).
To react to changes from other tabs in your UI, listen for the storage event:
const NAMESPACE = 'my-app-settings';
window.addEventListener('storage', (e) => {
if (e.key === NAMESPACE) {
const theme = storage.getItem('theme') || 'dark';
applyTheme(theme);
}
});