Database
@delightstack/database is a type-safe data layer for Cloudflare Durable Objects with built-in
full-text & vector search, runtime validation, automatic migrations, and a reactive Svelte 5 client.
Features
Section titled “Features”- Declarative schema — fluent TypeScript API; field types and validators inferred at compile time.
- Full-text & vector search — built-in Orama; mark fields
.searchable(). - Automatic migrations — new columns are added when you change the schema; no migration files.
- Zod validation — every
create()/update()is validated at runtime. - Transactions — batch operations atomically.
- Incremental sync —
sync()returns only changes since a timestamp for client mirroring. - Reactive client —
DatabaseClientgives reactive entity state, live search, IndexedDB caching, and optimistic updates, all off-thread in a SharedWorker.
Install
Section titled “Install”pnpm add @delightstack/database| Import | Use |
|---|---|
@delightstack/database | Database — the schema builder |
@delightstack/database/worker | DatabaseServer — the Durable Object class |
@delightstack/database/server | createDatabaseHandle, SqlServer (raw SQL) |
@delightstack/database/client | DatabaseClient — reactive Svelte 5 client |
1. Define your schema
Section titled “1. Define your schema”import { Database } from '@delightstack/database';
const postsTable = Database.table('post', (schema) => ({ id: schema.primaryKey(), title: schema.string().searchable(), body: schema.string().searchable(), author_id: schema.foreignKey({ type: 'string', table: 'user', column: 'id', on_delete: 'CASCADE' }), tags: schema.array(schema.string()).searchable().optional(), published: schema.boolean().default(false), created_at: schema.string().datetime(),}));Root-level scalars become real SQLite columns; nested objects/arrays are serialized into a JSON catch-all column and transparently rehydrated on read.
2. Register the Durable Object
Section titled “2. Register the Durable Object”Subclass DatabaseServer with your tables and register it in wrangler.toml
(see Architecture):
import { DatabaseServer } from '@delightstack/database/worker';
export class OrgDatabaseServer extends DatabaseServer { tables = [postsTable /* … */];}3. Query from the server
Section titled “3. Query from the server”const db = platform.env.DB.get(platform.env.DB.idFromName(org_id));
await db.create('post', { title: 'Hello', body: '…', author_id: 'u_1' });const results = await db.search('post', { term: 'hello' }); // full-textconst recent = await db.list('post', { limit: 20 });For declarative CRUD routes, mount createDatabaseHandle() in your SvelteKit handle.
4. The reactive client
Section titled “4. The reactive client”<script> import { DatabaseClient } from '@delightstack/database/client';
const db = new DatabaseClient({ tables, db_name: `app:${org_id}`, fetch }); const posts = db.entity('post'); // reactive $state</script>
{#each posts.list as post} <article>{post.title}</article>{/each}5. Entity-backed forms
Section titled “5. Entity-backed forms”Every entity carries its table’s form wiring (entity.form.field / entity.form.schema). Hand the
entity to the components Form and spread the field props — values, validation, saving, and
submit state are all handled:
<script> const post = $derived(db.entity('post', post_id)); // omit the id to create</script>
<Form entity={post} onsaved={() => goto('/posts')}> <Input {...post.form.field.title} /> <Button type="submit">Save</Button></Form>See Working with Forms for the full pattern.