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.

معلومات المصدر

المستودع
BuilderIO/agent-native
آخر نشاط في المصدر
١٧ أكتوبر ٢٠٢٦ في ٠٤:٤٩
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٧٬٤٤٩
التفرعات
٦٨١

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
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.
عرض على GitHub