# @harvmaster/storage Layered TypeScript storage that works the same in the browser and on the server: - **KV storage**: ordered keys and values, navigated as a tree (`db.child('users').get('1')`). In memory, IndexedDB, or over HTTP, with live updates over Server-Sent Events. - **Event storage**: append-only streams on top of any KV storage. - **Document storage**: JSON documents with queries, updates (`$set`, `$inc`, …) and indexes, on top of any KV storage, or event-sourced. - **Sync**: local replicas that work offline and stay up to date with a server (`KVStorageComposite`), end-to-end encryption (`KVStorageEncrypted`) and signed requests where the user's public key is their id. No runtime dependencies. Node 22+ and current browsers. Values are JSON plus `bigint` and `Uint8Array`. > **Status: experimental (0.x).** The API may change between minor versions. See > [DESIGN.md](./DESIGN.md) for how everything works and its known limitations. ## Install The package is published to a private registry. Add to your project's `.npmrc`: ```ini @harvmaster:registry=https://verdaccio.harvmaster.com/ ``` ```sh npm install @harvmaster/storage # latest release npm install @harvmaster/storage@next # `next` branch npm install @harvmaster/storage@dev-sync # a development branch (`dev-`) ``` ## KV storage Every store is a tree of nodes. `child(key)` returns a view rooted deeper (no I/O); methods take an optional key to act on a child instead of the node itself. ```ts import { KVStorageInMemory } from '@harvmaster/storage' const db = new KVStorageInMemory() const users = db.child('users') await users.set('1', { name: 'Ada' }) await users.child('1').get() // { name: 'Ada' } await users.get('1') // the same await users.setMany([ ['2', { name: 'Bob' }], ['3', { name: 'Cy' }], ]) await users.getMany(['1', 'missing']) // [{ name: 'Ada' }, undefined] for await (const key of users.keys({ limit: 10 })) console.log(key) // '1', '2', '3' for await (const [key, user] of users.entries({ reverse: true })) console.log(key, user) await users.delete('3') await users.deleteTree() // users and everything under it ``` A node's own value and its children are independent, so an object can also be stored field per key: ```ts const settings = db.child('settings') await settings.setFields({ theme: 'dark', fontSize: 14 }) await settings.get('theme') // 'dark' await settings.getFields() // { fontSize: 14, theme: 'dark' } ``` Keys sort as strings (`'10'` before `'9'`), and any string or number is a valid key: special characters are escaped. Every write emits a `change` event: ```ts db.on('change', ({ changes, origin }) => console.log(origin, changes)) // 'local', [{ key: 'users/1', value: … }] ``` ### Types Describe the tree once to get typed keys and values: ```ts import { KVStorageInMemory, type Many, type Fields } from '@harvmaster/storage' type User = { name: string } type Schema = { children: { users: Many; settings: { children: Fields<{ theme: string; fontSize: number }> } } } const typed = new KVStorageInMemory() await typed.child('users').set('1', { name: 'Ada' }) // checked const theme = await typed.child('settings').get('theme') // string | undefined ``` ### IndexedDB ```ts import { KVStorageIndexedDB } from '@harvmaster/storage' const local = new KVStorageIndexedDB({ name: 'my-app' }) ``` ## Event storage Append-only streams. Every node is a stream (its own events) and a parent of other streams. Appends never conflict: concurrent writers each add their own events, and every reader sees all of them in the same (time) order. ```ts import { EventStorageKV, KVStorageInMemory } from '@harvmaster/storage' const events = new EventStorageKV(new KVStorageInMemory()) const orders = events.child('orders') const [id] = await orders.append('o1', [{ type: 'created', total: 30 }]) await orders.child('o1').append([{ type: 'paid' }]) for await (const event of orders.read('o1')) console.log(event.id, event.at, event.data) for await (const event of orders.read('o1', { after: id })) console.log(event.data) // { type: 'paid' } for await (const key of orders.keys()) console.log(key) // 'o1' ``` Event ids are `