shelving/dbmodule

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.

Concepts

Collection

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).

ts
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

DBProvider is the abstract base class every backend implements. Its surface covers:

CategoryMethods
Single itemDBProvider.getItem(), DBProvider.requireItem(), DBProvider.addItem(), DBProvider.setItem(), DBProvider.updateItem(), DBProvider.deleteItem()
Single item (realtime)DBProvider.getItemSequence()
QueriesDBProvider.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 syntax

Query objects use encoded key names alongside plain field names:

ts
{ 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 size

Provider composition

Providers are layered wrappers. Each takes a source and delegates to it, intercepting only what it needs.

Cloud providers live in the shelving/cloudflare and shelving/firestore/client sibling modules.

Migrations

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.

Usage

Define a collection and build a provider chain

ts
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())

Read and write items

ts
// 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)

Query items

ts
const posts = await provider.getQuery(POSTS, {
  published: true,
  $order: "title",
  $limit: 10,
})

Realtime subscriptions

ts
for await (const post of provider.getItemSequence(POSTS, id)) {
  console.log(post) // emits whenever this item changes
}

React integration

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.

ts
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.

Functions

Go

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>

Classes

Go

SQLProviderclass

Abstract database provider that implements CRUD and query operations by generating and executing SQL.

new SQLProvider<I, T>()
Go

DBProviderclass

Provider with a fully asynchronous interface for database access.

new DBProvider<I, T>()
Go

DebugDBProviderclass

Database provider that logs every operation it performs to the console for debugging.

new DebugDBProvider<I, T>()
Go

SQLiteProviderclass

Abstract SQLite provider with JSON1 function support for nested keys, array containment, and array mutations.

new SQLiteProvider<I, T>()
Go

MemoryDBProviderclass

Synchronous in-memory database provider, storing each collection in a MemoryTable.

new MemoryDBProvider<I, T>()
Go

MemoryTableclass

In-memory table holding the items of a single collection for a MemoryDBProvider.

new MemoryTable<I, T>(collection: Collection<string, I, T>)
Go

MockDBProviderclass

In-memory database provider that records every operation to its calls log, for testing.

new MockDBProvider<I, T>()
Go

PostgreSQLProviderclass

Abstract PostgreSQL provider with JSONB function support for nested keys, array containment, and array mutations.

new PostgreSQLProvider<I, T>()
Go

ValidationDBProviderclass

Database provider that validates data flowing to and from an asynchronous source provider.

new ValidationDBProvider<I, T>()
Go

ItemStoreclass

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>)
Go

QueryStoreclass

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>)
Go

Collectionclass

Declarative definition of a database collection/table.

new Collection<N, I, T>(name: N, id: Schema<I>, data: Schemas<T> | DataSchema<T>)
Go

CollectionCacheclass

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>)
Go

DBCacheclass

Cache of CollectionCache objects for multiple collections.

new DBCache<I, T>(provider: DBProvider<I, T>)
Go

SQLMigratorclass

Shared SQL migration logic that diffs collection schemas against live tables to produce migration statements.

new SQLMigrator<T>()
Go

SQLiteMigratorclass

SQLite (and Cloudflare D1) migrator that uses sqlite_master as the schema source of truth.

new SQLiteMigrator<T>()
Go

DBMigratorclass

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)
Go

PostgreSQLMigratorclass

PostgreSQL migrator that inspects the live schema via pg_catalog tables to diff and migrate columns.

new PostgreSQLMigrator<T>()

Interfaces

Go

SQLFragmentinterface

SQL fragment made from template strings plus embedded expressions, ready to be composed into a query.

{
	readonly strings: ImmutableArray<string>;
	readonly values: ImmutableArray<unknown>;
}

Types

Go

DBChangetype

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;
}
Go

MockDBCalltype

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;
}
Go

CollectionNametype

Extract the string name from a Collection instance.

C extends Collection<infer N, infer _I, infer _T> ? N : never
Go

CollectionIdentifiertype

Extract the Identifier type from a Collection instance.

C extends Collection<infer _N, infer I, infer _T> ? I : never
Go

CollectionDatatype

Extract the Data type from a Collection instance.

C extends Collection<infer _N, infer _I, infer T> ? T : never
Go

CollectionItemtype

Extract the Item type from a Collection instance.

Item<CollectionIdentifier<C>, CollectionData<C>>
Go

OptionalCollectionItemtype

Extract the optional (possibly undefined) Item type from a Collection instance.

OptionalItem<CollectionIdentifier<C>, CollectionData<C>>
Go

CollectionItemstype

Extract the array of Item types from a Collection instance.

Items<CollectionIdentifier<C>, CollectionData<C>>
Go

Collectionstype

A readonly array of Collection instances, optionally narrowed to a standardised Identifier and Data type.

ImmutableArray<Collection<string, I, D>>
Go

CollectionNamestype

Extract the union of string collection names from a Collections type.

C[number]["name"]
Go

CollectionsDatabasetype

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;
}
Go

SQLTableColumntype

Column definition in a live SQL table, pairing a column name with its raw definition statement.

{
	readonly name: string;
	readonly statement: string;
}
Go

SQLTabletype

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;
}
Go

SQLColumntype

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;
}

Constants

Go

IDconstant

Default identifier schema (integer) used when a collection doesn't supply its own.