Skip to main content

framework-stores

How framework-owned tables in packages/core declare and change their schema with defineStore(). Use before adding a table, column, index, backfill or seed to a core store, before writing an ensure*Table() function, and when guard:release-schema-complete or SchemaNotMigratedError fires.

Informações da origem

Repositório
BuilderIO/agent-native
Última atividade na origem
17 de outubro de 2026 às 04:49
Idioma detectado do SKILL.md
inglês
Estrelas
7.449
Forks
681

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
framework-stores
description
How framework-owned tables in packages/core declare and change their schema with defineStore(). Use before adding a table, column, index, backfill or seed to a core store, before writing an ensure*Table() function, and when guard:release-schema-complete or SchemaNotMigratedError fires.
scope
dev
metadata
{"internal":true}
# Framework stores Every framework table in `packages/core/src` is owned by exactly one `defineStore()` from `src/db/store-registry.ts`. The release step applies a store's migrations once per database and records each in `_an_store_migrations`, then verifies the ledger covers every registered store. Hosted functions of release-owned apps do no schema work at all. Design and rollout: `docs/plans/2026-09-28-store-registry-schema-ownership.md`. App-owned template tables are not covered here; they use `runMigrations` or Drizzle (see `storing-data`). ## Rules - **Schema lives in a store's migration list, nowhere else.** No `let _initPromise` memo, no `ensureTableExists` / `ensureColumnExists` / `ensureIndexExists` and no `CREATE`/`ALTER` outside a `defineStore()` migration. Keep the module's exported `ensure*()` as `return xStore.ready();` so callers do not change. - **Append, never edit.** A database records a migration by name and never runs it again, so editing, renaming, reordering or deleting one never reaches databases that already applied it. To change a table, add a new named migration at the end. `store-migrations.lock.json` makes this a CI failure. - **Baselines converge old tables.** A database created by any older core runs the baseline against whatever shape it has, so keep `CREATE TABLE IF NOT EXISTS` plus `ensureColumnExists` for every column ever added. Never squash a baseline into today's `CREATE TABLE`. - **Idempotent and additive.** First runs can race and old functions keep serving during a release. New columns are nullable or defaulted. - **One store's tables only, or say so.** Stores apply in registry order (by id). A migration that reads or alters another store's tables needs `after: ["other_store_id"]` on its store; the release pass fails on an unknown id or a cycle. - **Folding a `runMigrations` list.** Append it whole with `...fromMigrationEntries(TABLE, LIST, { data: [...] })` to the store that owns its tables. Each entry keeps its old identity, so databases that applied it import it instead of re-running it. A release-only list keeps running only in the release step; pass `{ ranAtBoot: true }` for a list that also ran at plugin boot, so local runtimes import its history and hosted apps without release migrations keep its old bookkeeping. Never rename or reorder the entries. - **Mark data migrations.** Backfills and seeds get `kind: "data"`; a schema snapshot cannot replace them. - **Never swallow a failure inside a migration.** The ledger would record it as applied and it would never retry. Rethrow at release (`isMigrationAuthorizedRuntime()`), as `widenIntColumnsToBigInt` does. - **`releaseOnly: true`** for a store a request must never create, even in hosted apps that do not migrate at release. - **Literal declaration.** Write `export const xStore = defineStore({ id: "snake_case_id", migrations: [...] })`; the generator only reads that form, and the guard fails on any other. Ids are permanent. ## Adding or changing a store ```ts export const widgetsStore = defineStore({ id: "widgets", migrations: [ { name: "baseline", run: async () => { await ensureTableExists("widgets", CREATE_WIDGETS_SQL); await ensureColumnExists("widgets", "owner", `ALTER TABLE widgets ADD COLUMN IF NOT EXISTS owner TEXT`); }, }, { name: "owner-index", sql: `CREATE INDEX IF NOT EXISTS widgets_owner_idx ON widgets (owner)` }, { name: "backfill-owner", kind: "data", sql: `UPDATE widgets SET owner = created_by WHERE owner IS NULL` }, ], }); export function ensureWidgetsTable(): Promise<void> { return widgetsStore.ready(); } ``` Then run, in this order: 1. `pnpm gen:store-registry` — rewrites `store-registry.generated.ts` and `store-migrations.lock.json`. Then `pnpm gen:schema-snapshot` — rewrites `schema-snapshot.generated.ts`, the schema fresh databases install in one step. Commit all three; `release-schema.snapshot.spec.ts` fails when the snapshot no longer covers every schema migration. 2. `pnpm guard:release-schema-complete` and `pnpm guard:additive-migrations`. 3. The store's own spec, plus `src/db/store-registry.spec.ts`, `src/server/release-schema.cold-start.spec.ts` and `src/server/release-schema.upgrade.spec.ts` from `packages/core`. The upgrade spec replays an older release's schema and fails when your change does not reach existing databases (for example a column added only to a `CREATE TABLE`). ## Readiness modes `ready()` picks a mode from who owns the schema: - **apply** — the release step (`withMigrationRuntime`, under a lease in `_an_store_migration_lock`, verifying the ledger against the registry afterwards), plus local dev, tests and self-hosted Node. Each migration runs once per database. Specs that mock `getDbExec` answer ledger statements with `db/store-ledger.test-helpers.ts`. - **trust** — hosted function of an app that migrates at release. No schema work at all, not even a ledger read. - **hosted-replay** — hosted apps without release migrations. Runs migrations idempotently, as before, and never writes the ledger: previews share production's database and request writes can roll back. `SchemaNotMigratedError` is raised by the release step when the ledger does not cover the code being shipped. It fails that deploy; fix the migration or the deploy, never catch it. Code that catches everything around a store query must fail closed for security reads (revocation, budgets, access): a missing table must never read as allowed or empty. ## Reviewed exceptions `// guard:allow-unreleased-schema — <reason>` exempts a module from schema placement checks. Current exceptions: `sharing/indexes.ts` (indexes app and framework share tables at release) and `triggers/event-queue.ts` (owned by a versioned list until those fold into stores). A new one needs a reason a reviewer can check.
Ver no GitHub