COLLECTION()function
Create a Collection from a name, an identifier schema, and a data schema.
COLLECTION(name: K, id: Schema<I>, data: Schemas<T> | DataSchema<T>): Collection<K, I, T>
Typed database provider abstraction. Define your schema once as a Collection, then swap providers (in-memory, SQLite, Firestore, Cloudflare D1, …) without changing any call sites. Providers are composable wrappers — add validation, logging, or caching by stacking them in a chain.
A Collection is a declarative description of a database table or collection. It extends DataSchema and carries three things: a string name, an id schema (the identifier type), and a data schema (the shape of each record).
import { COLLECTION } from "shelving/db"
import { STRING, BOOLEAN } from "shelving/schema"
const POSTS = COLLECTION("posts", STRING, {
title: STRING,
body: STRING,
published: BOOLEAN,
})Collection is the single source of truth for both runtime validation and TypeScript types. All provider methods are generic over the collection so the compiler tracks id and data types automatically.
DBProvider is the abstract base class every backend implements. Its surface covers:
| Category | Methods |
|---|---|
| Single item | DBProvider.getItem(), DBProvider.requireItem(), DBProvider.addItem(), DBProvider.setItem(), DBProvider.updateItem(), DBProvider.deleteItem() |
| Single item (realtime) | DBProvider.getItemSequence() |
| Queries | DBProvider.getQuery(), DBProvider.countQuery(), DBProvider.setQuery(), DBProvider.updateQuery(), DBProvider.deleteQuery(), DBProvider.getFirst(), DBProvider.requireFirst() |
| Query (realtime) | DBProvider.getQuerySequence() |
.getItemSequence() and .getQuerySequence() return AsyncIterable — iterate them with for await...of to receive realtime updates as data changes.
Query objects use encoded key names alongside plain field names:
{ status: "active" } // equality
{ "!status": "banned" } // not-equal (!key)
{ "count>": 10 } // greater-than (key>)
{ "tags[]": "typescript" } // array-contains (key[])
{ $order: "name" } // order by field
{ $limit: 20 } // page sizeProviders are layered wrappers. Each takes a source and delegates to it, intercepting only what it needs.
MemoryDBProvider — fully in-memory, ideal for testing and as a lightweight standalone store.ValidationDBProvider — validates data in and out using the collection's schema. Throws ValueError on bad data from the backend.CacheDBProvider — keeps a MemoryDBProvider mirror in sync with a remote source so reads are synchronous after the first fetch. Primarily useful with the React integration.ThroughDBProvider — identity wrapper; extend this to intercept only specific methods (e.g. DebugDBProvider).SQLiteProvider / PostgreSQLProvider — SQL-backed abstract providers. Concrete subclasses bind them to a specific driver.Cloud providers live in the shelving/cloudflare and shelving/firestore/client sibling modules.
DBMigrator is an abstract base for schema migrations. SQL-backed providers ship SQLiteMigrator and PostgreSQLMigrator, which implement DBMigrator.migrate() to create or alter tables to match the current collection schemas.
import { COLLECTION, MemoryDBProvider, ValidationDBProvider } from "shelving/db"
import { STRING, BOOLEAN } from "shelving/schema"
const POSTS = COLLECTION("posts", STRING, {
title: STRING,
body: STRING,
published: BOOLEAN,
})
// In production replace MemoryDBProvider with a real backend (Firestore, D1, …).
const provider = new ValidationDBProvider(new MemoryDBProvider())// Add a new item — returns the generated id.
const id = await provider.addItem(POSTS, { title: "Hello world", body: "First post.", published: false })
// Read it back.
const post = await provider.getItem(POSTS, id)
// Partial update.
await provider.updateItem(POSTS, id, { published: true })
// Delete.
await provider.deleteItem(POSTS, id)const posts = await provider.getQuery(POSTS, {
published: true,
$order: "title",
$limit: 10,
})for await (const post of provider.getItemSequence(POSTS, id)) {
console.log(post) // emits whenever this item changes
}The shelving/react module's createDBContext() is the primary way to use a provider in a React app. It creates a context that wraps a provider (typically with a CacheDBProvider in the chain) and exposes typed hooks — DBContext.useItem() and DBContext.useQuery() — that return reactive Store instances and suspend automatically while loading.
import { createDBContext } from "shelving/react"
import { CacheDBProvider, ValidationDBProvider, MemoryDBProvider } from "shelving/db"
const provider = new CacheDBProvider(new ValidationDBProvider(new MemoryDBProvider()))
export const { DBContext, useItem, useQuery } = createDBContext(provider)See the shelving/react module for full usage.
Create a Collection from a name, an identifier schema, and a data schema.
COLLECTION(name: K, id: Schema<I>, data: Schemas<T> | DataSchema<T>): Collection<K, I, T>
Abstract database provider that implements CRUD and query operations by generating and executing SQL.
new SQLProvider<I, T>()
Provider with a fully asynchronous interface for database access.
new DBProvider<I, T>()
Database provider that logs every operation it performs to the console for debugging.
new DebugDBProvider<I, T>()
Abstract SQLite provider with JSON1 function support for nested keys, array containment, and array mutations.
new SQLiteProvider<I, T>()
Database provider that passes every operation straight through to a wrapped source provider.
new ThroughDBProvider<I, T>(source: DBProvider<I, T>)
Database provider that keeps a copy of asynchronous remote data in a local synchronous cache.
new CacheDBProvider<I, T>(source: DBProvider<I, T>, cache: MemoryDBProvider<I, T> = new MemoryDBProvider<I, T>())
Database provider that records every write it performs to its changes log.
new ChangesDBProvider<I, T>()
Synchronous in-memory database provider, storing each collection in a MemoryTable.
new MemoryDBProvider<I, T>()
In-memory table holding the items of a single collection for a MemoryDBProvider.
new MemoryTable<I, T>(collection: Collection<string, I, T>)
In-memory database provider that records every operation to its calls log, for testing.
new MockDBProvider<I, T>()
Abstract PostgreSQL provider with JSONB function support for nested keys, array containment, and array mutations.
new PostgreSQLProvider<I, T>()
Database provider that validates data flowing to and from an asynchronous source provider.
new ValidationDBProvider<I, T>()
Store that fetches and tracks a single item by ID in a collection from a database provider.
new ItemStore<I, T>(collection: Collection<string, I, T>, id: I, provider: DBProvider<I>, memory?: MemoryDBProvider<I>)
Store that runs a query against a collection from a database provider and tracks its matching items.
new QueryStore<I, T>(collection: Collection<string, I, T>, query: Query<Item<I, T>>, provider: DBProvider<I>, memory?: MemoryDBProvider<I>)
Declarative definition of a database collection/table.
new Collection<N, I, T>(name: N, id: Schema<I>, data: Schemas<T> | DataSchema<T>)
Cache of ItemStore and QueryStore objects for a single collection.
new CollectionCache<I, T>(collection: Collection<string, I, T>, provider: DBProvider<I>, memory?: MemoryDBProvider<I>)
Cache of CollectionCache objects for multiple collections.
new DBCache<I, T>(provider: DBProvider<I, T>)
Shared SQL migration logic that diffs collection schemas against live tables to produce migration statements.
new SQLMigrator<T>()
SQLite (and Cloudflare D1) migrator that uses sqlite_master as the schema source of truth.
new SQLiteMigrator<T>()
Base class for database schema migrators that bring a provider's storage into line with a set of collection schemas.
new DBMigrator<T>(provider: T)
PostgreSQL migrator that inspects the live schema via pg_catalog tables to diff and migrate columns.
new PostgreSQLMigrator<T>()
SQL fragment made from template strings plus embedded expressions, ready to be composed into a query.
{
readonly strings: ImmutableArray<string>;
readonly values: ImmutableArray<unknown>;
}Structured log entry recording a single database write performed through a ChangesDBProvider.
{
readonly action: "add" | "set" | "update" | "delete";
readonly collection: string;
readonly id?: I | undefined;
readonly query?: unknown;
readonly data?: unknown;
readonly updates?: unknown;
}Structured log entry recording one operation performed through a MockDBProvider.
{
readonly type:
| "getItem"
| "addItem"
| "setItem"
| "updateItem"
| "deleteItem"
| "countQuery"
| "getQuery"
| "setQuery"
| "updateQuery"
| "deleteQuery";
readonly collection: string;
readonly id?: Identifier | undefined;
readonly query?: unknown;
readonly data?: Data;
readonly updates?: unknown;
readonly result?: unknown;
}Extract the string name from a Collection instance.
C extends Collection<infer N, infer _I, infer _T> ? N : never
Extract the Identifier type from a Collection instance.
C extends Collection<infer _N, infer I, infer _T> ? I : never
Extract the Data type from a Collection instance.
C extends Collection<infer _N, infer _I, infer T> ? T : never
Extract the Item type from a Collection instance.
Item<CollectionIdentifier<C>, CollectionData<C>>
Extract the optional (possibly undefined) Item type from a Collection instance.
OptionalItem<CollectionIdentifier<C>, CollectionData<C>>
Extract the array of Item types from a Collection instance.
Items<CollectionIdentifier<C>, CollectionData<C>>
A readonly array of Collection instances, optionally narrowed to a standardised Identifier and Data type.
ImmutableArray<Collection<string, I, D>>
Extract the union of string collection names from a Collections type.
C[number]["name"]
Convert a Collections array type to a database-style object mapping in { name: data } format.
{
[E in C[number] as E extends Collection<infer N, Identifier, Data> ? N : never]: E extends Collection<string, Identifier, infer T>
? T
: never;
}Column definition in a live SQL table, pairing a column name with its raw definition statement.
{
readonly name: string;
readonly statement: string;
}Existing SQL table schema keyed by column name, as read back from the database.
{
readonly columns: Readonly<Record<string, SQLTableColumn>>;
readonly name: string;
readonly sql?: string | undefined;
}Generated SQL column mapped from a collection schema path, holding its column name, key, and JSON path.
{
readonly column: string;
readonly key: string;
readonly path: string;
}Default identifier schema (integer) used when a collection doesn't supply its own.