/** * The single entry point for framework-owned schema. * * A store declares its tables as an ordered list of named migrations with * `defineStore()`. The release step applies them once per database and records * each in `_an_store_migrations`; request runtimes that do not own the schema * only verify that ledger — one query per process, shared by every store — and * never issue DDL. See docs/plans/2026-09-28-store-registry-schema-ownership.md. * * Stores are discovered by `scripts/gen-store-registry.ts`, which writes the * explicit import list in `store-registry.generated.ts`. Registration is never a * module side effect: `package.json#sideEffects` lets a bundler drop an import * kept only for that. */ import { type DbExec } from "./client.js"; import { type MigrationEntry } from "./migrations.js"; export declare const STORE_MIGRATIONS_TABLE = "_an_store_migrations"; export interface StoreMigration { /** Stable, never renamed: the ledger records it. */ readonly name: string; /** * `data` for backfills and seeds. A schema snapshot cannot stand in for a * data migration, so Phase 2 snapshot installs still run these. */ readonly kind?: "schema" | "data"; readonly sql?: string | readonly string[]; readonly run?: (exec: DbExec) => Promise; /** * The versioned `runMigrations` entry this migration replaces. A database * whose old bookkeeping table already records that entry gets a ledger row * (`source = 'legacy'`) instead of running it again. */ readonly legacy?: LegacyMigrationRef; } export interface LegacyMigrationRef { /** The `runMigrations` table, e.g. `_chat_thread_schema_migrations`. */ readonly table: string; readonly version: number; /** Set for named entries, which were gated by name, not version. */ readonly name?: string; /** * Where the list ran on main. A release-only list (the default) keeps * running only in the release step. A list that also ran at plugin boot * runs everywhere: local runtimes import its history once, and hosted apps * without release migrations keep running it through `runMigrations` and * its old table, exactly as before. */ readonly ranAtBoot?: boolean; /** The original entry, for that hosted `runMigrations` path. */ readonly entry?: MigrationEntry; } export interface StoreDefinition { readonly id: string; /** * Stores whose migrations must run first at release, for a migration that * reads or alters another store's tables. Registry order is otherwise by id. */ readonly after?: readonly string[]; /** * Never provision from a request, even for hosted apps that do not migrate * at release: the release step (or a versioned list) is the only creator. */ readonly releaseOnly?: boolean; /** * Append-only. Every entry must be idempotent: first runs can race. A * migration may only touch this store's own tables: stores apply in registry * order, which is by id, not by dependency. */ readonly migrations: readonly StoreMigration[]; } export interface Store extends StoreDefinition { /** Resolves once this store's schema is usable in the current runtime. */ ready(): Promise; reset(): void; } export declare class SchemaNotMigratedError extends Error { readonly storeId: string; readonly missing: readonly string[]; constructor(storeId: string, missing: readonly string[], reason: string); } /** * Hosted functions of an app that migrates at release do no schema work at * all: the release step applied and verified every store before they were * published. Same predicate `ddl-guard` uses, so the runner and the guards * cannot disagree. */ export declare function schemaIsReleaseOwned(): boolean; /** Stable across bundlers; `run` bodies are not hashed because minifiers rewrite them. */ export declare function migrationChecksum(migration: StoreMigration): string | null; /** * Registry order with each store's `after` dependencies first. Fails on an * unknown id or a cycle rather than guessing an order. */ export declare function orderStoresForRelease(stores: readonly T[]): T[]; /** * The framework schema as of the last `pnpm gen:schema-snapshot`: every * statement a fresh release produced, the framework tables it created, and * the schema migrations it recorded. Data migrations are not covered; they * run after the snapshot like any migration added since. */ export interface SchemaSnapshot { readonly statements: readonly string[]; readonly tables: readonly string[]; readonly migrations: ReadonlyArray; /** Covered entries of lists that ran at boot, as their old tables record them. */ readonly bootLists: ReadonlyArray; } /** Tests and the snapshot generator run the step-by-step path with `null`. */ export declare function __setSchemaSnapshotForTests(snapshot: SchemaSnapshot | null | undefined): void; /** * Every registered store's migrations that the ledger does not list. The * release step calls this after applying, so a deploy whose ledger disagrees * with the code it is about to publish fails there instead of in a request. */ export declare function findUnappliedStoreMigrations(stores: readonly StoreDefinition[]): Promise>; /** * Serializes release steps against one database (a beta and production * pointed at the same schema, or a retried deploy) for the whole release, not * just the store pass. A lease row, not `pg_advisory_lock`: session locks do * not survive transaction poolers such as Neon's, where the lock and unlock * can land on different connections. The holder renews the lease while it * runs; a crashed holder's lease expires. Reentrant within a process, so the * store pass can take it inside a release that already holds it. */ export declare function withStoreMigrationLock(run: () => Promise, options?: { leaseMs?: number; pollMs?: number; timeoutMs?: number; }): Promise; export declare function defineStore(definition: StoreDefinition): Store; export declare function __resetStoreLedgerSnapshotForTests(): void;