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
최근 소스 활동
2026년 10월 17일 04:49
감지된 SKILL.md 언어
영어
스타
7,449
포크
681

설치 방법

기본적으로 소스를 먼저 확인하는 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에서 보기