@openzeppelin/ui-storage
v1.0.0
Published
Universal, app-agnostic storage abstraction built on Dexie.js for IndexedDB.
Readme
@openzeppelin/ui-storage
A React-first storage abstraction built on Dexie.js for IndexedDB.
Installation
# Using npm
npm install @openzeppelin/ui-storage
# Using yarn
yarn add @openzeppelin/ui-storage
# Using pnpm
pnpm add @openzeppelin/ui-storagePeer Dependencies
pnpm add reactOverview
This package provides a generic storage infrastructure built on top of IndexedDB using Dexie.js. It includes base classes for creating type-safe storage services, a React live-query hook, database creation utilities, and helpful abstractions. Each consuming application defines its own database name, schema, and domain-specific storage classes.
Features
- Entity Storage:
EntityStorageclass for creating entity/record storage services - Key-Value Storage:
KeyValueStorageclass for settings, preferences, and configuration stores - React Support:
useLiveQueryre-exported for convenience - App-Agnostic Schemas: Each app defines its own DB name and stores
- Type Safety: Full TypeScript support with generics
- Performance: Optimized for large datasets with configurable limits
- CRUD Operations: Create, Read, Update, Delete helpers
- Bulk Operations: Efficient bulk add/put/delete
- Index Queries: Query by indexed fields
- Quota Handling: Cross-browser quota exceeded error detection
Quick Start
1. Define Your Database Schema
import { createDexieDatabase } from '@openzeppelin/ui-storage';
export const db = createDexieDatabase('MyApp', [
{
version: 1,
stores: {
items: '++id, name, createdAt, updatedAt',
settings: '&key',
},
},
{
version: 2,
stores: {
items: '++id, name, createdAt, updatedAt, category',
settings: '&key',
},
upgrade: async (trans) => {
// Migration logic for version 2
},
},
]);2. Define Your Record Types
import type { BaseRecord } from '@openzeppelin/ui-storage';
export interface ItemRecord extends BaseRecord {
name: string;
category?: string;
}3. Create Storage Services
import { EntityStorage, useLiveQuery } from '@openzeppelin/ui-storage';
import { db } from './database';
import type { ItemRecord } from './types';
export class ItemStorage extends EntityStorage<ItemRecord> {
constructor() {
super(db, 'items');
}
async getByName(name: string): Promise<ItemRecord | undefined> {
return await this.findByIndex('name', name).then((results) => results[0]);
}
async getByCategory(category: string): Promise<ItemRecord[]> {
return await this.findByIndex('category', category);
}
}
export const itemStorage = new ItemStorage();
export function useItems() {
const items = useLiveQuery(() => itemStorage.getAll());
const isLoading = items === undefined;
return { items, isLoading };
}API Reference
createDexieDatabase(name, versions)
Creates a configured Dexie database instance with versioned stores.
Parameters:
name: string- Database name (e.g., 'UIBuilder', 'RoleManager')versions: DbVersion[]- Array of version definitions
Returns: Dexie - Configured database instance
EntityStorage<T>
Abstract base class for entity storage. Use this for collections of records with auto-generated IDs.
| Use Case | Base Class | Schema |
| -------------------------------------------- | -------------------- | ----------- |
| Entity collections (users, items, contracts) | EntityStorage<T> | ++id, ... |
| Settings, preferences, config | KeyValueStorage<V> | &key |
Methods:
save(record): Create a new recordupdate(id, updates): Update an existing recorddelete(id): Delete a recordget(id): Get a record by IDgetAll(): Get all recordshas(id): Check if record existscount(): Count recordsclear(): Delete all recordsbulkAdd(records): Add multiple recordsbulkPut(records): Upsert multiple recordsbulkDelete(ids): Delete multiple recordsfindByIndex(index, value): Query by index
KeyValueStorage<V>
Abstract base class for key-value storage. Use for settings, preferences, and configuration.
Methods:
set(key, value): Set a value (upsert)get<T>(key): Get a value with type castinggetOrDefault<T>(key, defaultValue): Get with fallbackdelete(key): Delete a keyhas(key): Check if key existskeys(): Get all keysgetAll(): Get all recordsclear(): Clear all entriescount(): Count entriessetMany(entries): Bulk setgetMany(keys): Bulk getdeleteMany(keys): Bulk delete
BaseRecord
interface BaseRecord {
id: string;
createdAt: Date;
updatedAt: Date;
}Examples
Entity Storage
import { createDexieDatabase, EntityStorage } from '@openzeppelin/ui-storage';
export const db = createDexieDatabase('MyApp', [
{
version: 1,
stores: {
items: '++id, title, createdAt, updatedAt',
},
},
]);
export class ItemStorage extends EntityStorage<ItemRecord> {
constructor() {
super(db, 'items', { maxRecordSizeBytes: 50 * 1024 * 1024 });
}
}Key-Value Storage
import { createDexieDatabase, KeyValueStorage } from '@openzeppelin/ui-storage';
export const db = createDexieDatabase('MyApp', [
{
version: 1,
stores: {
settings: '&key',
},
},
]);
class SettingsStorage extends KeyValueStorage<unknown> {
constructor() {
super(db, 'settings');
}
async getTheme(): Promise<'light' | 'dark'> {
return (await this.get<'light' | 'dark'>('theme')) ?? 'light';
}
async setTheme(theme: 'light' | 'dark'): Promise<void> {
await this.set('theme', theme);
}
}
export const settingsStorage = new SettingsStorage();
// Usage
await settingsStorage.set('theme', 'dark');
await settingsStorage.set('language', 'en');
const theme = await settingsStorage.get<string>('theme'); // 'dark'Development
# Build the package
pnpm build
# Run tests
pnpm test
# Lint
pnpm lint