@prisma-next/target-sqlite 0.12.0-dev.3 → 0.12.0-dev.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/{codec-ids-C4UfWqAE.d.mts → codec-ids-yG2RRHnw.d.mts} +1 -1
  2. package/dist/{codec-ids-C4UfWqAE.d.mts.map → codec-ids-yG2RRHnw.d.mts.map} +1 -1
  3. package/dist/codec-ids.d.mts +1 -1
  4. package/dist/{codec-types-B2ci-b_W.d.mts → codec-types-CM7OkQLM.d.mts} +2 -2
  5. package/dist/{codec-types-B2ci-b_W.d.mts.map → codec-types-CM7OkQLM.d.mts.map} +1 -1
  6. package/dist/codec-types.d.mts +2 -2
  7. package/dist/{codecs-DqDWNIKq.d.mts → codecs-t6YnHZkK.d.mts} +2 -2
  8. package/dist/{codecs-DqDWNIKq.d.mts.map → codecs-t6YnHZkK.d.mts.map} +1 -1
  9. package/dist/codecs.d.mts +1 -1
  10. package/dist/contract-free.d.mts +27 -0
  11. package/dist/contract-free.d.mts.map +1 -0
  12. package/dist/contract-free.mjs +80 -0
  13. package/dist/contract-free.mjs.map +1 -0
  14. package/dist/control.d.mts +1 -1
  15. package/dist/control.d.mts.map +1 -1
  16. package/dist/control.mjs +37 -89
  17. package/dist/control.mjs.map +1 -1
  18. package/dist/ddl.d.mts +2 -0
  19. package/dist/ddl.mjs +2 -0
  20. package/dist/{descriptor-meta-DIbCXU65.mjs → descriptor-meta-rKforwQA.mjs} +3 -1
  21. package/dist/descriptor-meta-rKforwQA.mjs.map +1 -0
  22. package/dist/migration.d.mts +2 -2
  23. package/dist/nodes-09ul6DNV.mjs +28 -0
  24. package/dist/nodes-09ul6DNV.mjs.map +1 -0
  25. package/dist/nodes-D8LGV4Yk.d.mts +27 -0
  26. package/dist/nodes-D8LGV4Yk.d.mts.map +1 -0
  27. package/dist/op-factory-call.d.mts +2 -2
  28. package/dist/pack.d.mts +2 -1
  29. package/dist/pack.d.mts.map +1 -1
  30. package/dist/pack.mjs +1 -1
  31. package/dist/{planner-BBvwGy0f.mjs → planner-Cygvz9Wp.mjs} +3 -3
  32. package/dist/{planner-BBvwGy0f.mjs.map → planner-Cygvz9Wp.mjs.map} +1 -1
  33. package/dist/{planner-produced-sqlite-migration-D4FSvPE4.d.mts → planner-produced-sqlite-migration-BwFkd0br.d.mts} +3 -3
  34. package/dist/{planner-produced-sqlite-migration-D4FSvPE4.d.mts.map → planner-produced-sqlite-migration-BwFkd0br.d.mts.map} +1 -1
  35. package/dist/planner-produced-sqlite-migration.d.mts +1 -1
  36. package/dist/{planner-target-details-B-VJkUsu.d.mts → planner-target-details-RVPqF2ug.d.mts} +1 -1
  37. package/dist/{planner-target-details-B-VJkUsu.d.mts.map → planner-target-details-RVPqF2ug.d.mts.map} +1 -1
  38. package/dist/planner-target-details.d.mts +1 -1
  39. package/dist/planner.d.mts +2 -2
  40. package/dist/planner.mjs +1 -1
  41. package/dist/render-ops.d.mts +1 -1
  42. package/dist/runtime.d.mts +17 -1
  43. package/dist/runtime.d.mts.map +1 -1
  44. package/dist/runtime.mjs +2 -1
  45. package/dist/runtime.mjs.map +1 -1
  46. package/dist/{shared-DDOJntxk.d.mts → shared-Bw5G0I-c.d.mts} +2 -2
  47. package/dist/{shared-DDOJntxk.d.mts.map → shared-Bw5G0I-c.d.mts.map} +1 -1
  48. package/dist/sqlite-contract-serializer-Hy7WQPea.mjs +100 -0
  49. package/dist/sqlite-contract-serializer-Hy7WQPea.mjs.map +1 -0
  50. package/dist/{sqlite-migration-Feio34n3.d.mts → sqlite-migration-D-O3Glz1.d.mts} +2 -2
  51. package/dist/{sqlite-migration-Feio34n3.d.mts.map → sqlite-migration-D-O3Glz1.d.mts.map} +1 -1
  52. package/dist/{statement-builders-BiRmeyJM.mjs → statement-builders-D1Q_i4sb.mjs} +14 -2
  53. package/dist/statement-builders-D1Q_i4sb.mjs.map +1 -0
  54. package/dist/statement-builders.d.mts +3 -0
  55. package/dist/statement-builders.d.mts.map +1 -1
  56. package/dist/statement-builders.mjs +1 -1
  57. package/package.json +20 -18
  58. package/src/contract-free/control-bootstrap.ts +54 -0
  59. package/src/contract-free/ddl.ts +21 -0
  60. package/src/core/ddl/nodes.ts +41 -0
  61. package/src/core/descriptor-meta.ts +2 -0
  62. package/src/core/migrations/runner.ts +53 -25
  63. package/src/core/migrations/statement-builders.ts +16 -1
  64. package/src/core/sqlite-contract-serializer.ts +38 -9
  65. package/src/core/sqlite-unbound-database.ts +30 -0
  66. package/src/exports/contract-free.ts +5 -0
  67. package/src/exports/ddl.ts +6 -0
  68. package/src/exports/runtime.ts +1 -0
  69. package/dist/descriptor-meta-DIbCXU65.mjs.map +0 -1
  70. package/dist/statement-builders-BiRmeyJM.mjs.map +0 -1
@@ -0,0 +1,100 @@
1
+ import { NamespaceBase, UNBOUND_NAMESPACE_ID, freezeNode } from "@prisma-next/framework-components/ir";
2
+ import { SqlContractSerializerBase } from "@prisma-next/family-sql/ir";
3
+ //#region src/core/sqlite-unbound-database.ts
4
+ /**
5
+ * SQLite namespace concretion carrying table metadata and unqualified
6
+ * `qualifyTable()` emission for runtime SQL rendering.
7
+ */
8
+ var SqliteDatabase = class extends NamespaceBase {
9
+ kind = "database";
10
+ id;
11
+ tables;
12
+ constructor(input) {
13
+ super();
14
+ this.id = input.id;
15
+ this.tables = Object.freeze({ ...input.tables });
16
+ freezeNode(this);
17
+ }
18
+ qualifier() {
19
+ return "";
20
+ }
21
+ qualifyTable(tableName) {
22
+ return `"${tableName}"`;
23
+ }
24
+ };
25
+ /**
26
+ * SQLite target `Namespace` concretion. SQLite has no schema or
27
+ * database-namespacing concept at the SQL level — there is exactly one
28
+ * effective namespace per connection, so the target ships a single
29
+ * singleton bound to the framework's `UNBOUND_NAMESPACE_ID` slot.
30
+ *
31
+ * Qualifier emission elides the prefix entirely: rendered DDL and
32
+ * queries look unqualified (`CREATE TABLE "users" (...)`), matching
33
+ * SQLite's native dialect. Call sites stay polymorphic — they ask the
34
+ * namespace for its qualifier and consume the empty/unqualified result
35
+ * the same way Postgres consumes a `"schema"` prefix.
36
+ *
37
+ * The SQLite PSL interpreter rejects every explicit `namespace { … }`
38
+ * block with a diagnostic naming SQLite; only the implicit
39
+ * `__unspecified__` AST bucket reaches the SQLite interpreter, which
40
+ * lowers it to this singleton.
41
+ */
42
+ var SqliteUnboundDatabase = class SqliteUnboundDatabase extends NamespaceBase {
43
+ static instance = new SqliteUnboundDatabase();
44
+ kind = "database";
45
+ id = UNBOUND_NAMESPACE_ID;
46
+ tables;
47
+ constructor() {
48
+ super();
49
+ this.tables = Object.freeze({});
50
+ freezeNode(this);
51
+ }
52
+ qualifier() {
53
+ return "";
54
+ }
55
+ qualifyTable(tableName) {
56
+ return `"${tableName}"`;
57
+ }
58
+ };
59
+ /**
60
+ * Target-supplied `Namespace` factory the SQLite target plumbs through
61
+ * `defineContract({ createNamespace })`. SQLite has only one
62
+ * effective namespace slot — the framework `UNBOUND_NAMESPACE_ID`
63
+ * sentinel — so the factory always returns the singleton and rejects
64
+ * any other coordinate. The SQL family's defensive validation in
65
+ * `defineContract` already rejects user-declared SQLite namespaces, so
66
+ * this throw is a structural safety net rather than a user-facing
67
+ * surface.
68
+ */
69
+ function sqliteCreateNamespace(id) {
70
+ if (id === UNBOUND_NAMESPACE_ID) return SqliteUnboundDatabase.instance;
71
+ throw new Error(`sqliteCreateNamespace: SQLite has no schema concept; the only valid namespace id is "${UNBOUND_NAMESPACE_ID}" (received "${id}").`);
72
+ }
73
+ //#endregion
74
+ //#region src/core/sqlite-contract-serializer.ts
75
+ /**
76
+ * SQLite target `ContractSerializer` concretion. Mirrors the Postgres
77
+ * shape: inherits the full SQL-family deserialization pipeline and
78
+ * materialises namespace entries as SQLite database concretions that
79
+ * expose `qualifyTable()` for runtime SQL rendering.
80
+ */
81
+ var SqliteContractSerializer = class extends SqlContractSerializerBase {
82
+ constructor() {
83
+ super(/* @__PURE__ */ new Map());
84
+ }
85
+ hydrateSqlNamespaceEntry(nsId, raw) {
86
+ if (raw instanceof NamespaceBase) return raw;
87
+ const { id, tables } = super.hydrateSqlNamespaceEntry(nsId, raw);
88
+ const emptyTables = Object.keys(tables).length === 0;
89
+ if (id === UNBOUND_NAMESPACE_ID && emptyTables) return SqliteUnboundDatabase.instance;
90
+ if (id !== UNBOUND_NAMESPACE_ID) throw new Error(`SqliteContractSerializer: SQLite has no schema concept; the only valid namespace id is "${UNBOUND_NAMESPACE_ID}" (received "${id}").`);
91
+ return new SqliteDatabase({
92
+ id,
93
+ tables
94
+ });
95
+ }
96
+ };
97
+ //#endregion
98
+ export { SqliteUnboundDatabase as n, sqliteCreateNamespace as r, SqliteContractSerializer as t };
99
+
100
+ //# sourceMappingURL=sqlite-contract-serializer-Hy7WQPea.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-contract-serializer-Hy7WQPea.mjs","names":[],"sources":["../src/core/sqlite-unbound-database.ts","../src/core/sqlite-contract-serializer.ts"],"sourcesContent":["import {\n freezeNode,\n NamespaceBase,\n UNBOUND_NAMESPACE_ID,\n} from '@prisma-next/framework-components/ir';\nimport type { StorageTable } from '@prisma-next/sql-contract/types';\n\nexport type SqliteDatabaseInput = {\n readonly id: string;\n readonly tables: Readonly<Record<string, StorageTable>>;\n};\n\n/**\n * SQLite namespace concretion carrying table metadata and unqualified\n * `qualifyTable()` emission for runtime SQL rendering.\n */\nexport class SqliteDatabase extends NamespaceBase {\n readonly kind = 'database' as const;\n readonly id: string;\n readonly tables: Readonly<Record<string, StorageTable>>;\n\n constructor(input: SqliteDatabaseInput) {\n super();\n this.id = input.id;\n this.tables = Object.freeze({ ...input.tables });\n freezeNode(this);\n }\n\n qualifier(): string {\n return '';\n }\n\n qualifyTable(tableName: string): string {\n return `\"${tableName}\"`;\n }\n}\n\n/**\n * SQLite target `Namespace` concretion. SQLite has no schema or\n * database-namespacing concept at the SQL level — there is exactly one\n * effective namespace per connection, so the target ships a single\n * singleton bound to the framework's `UNBOUND_NAMESPACE_ID` slot.\n *\n * Qualifier emission elides the prefix entirely: rendered DDL and\n * queries look unqualified (`CREATE TABLE \"users\" (...)`), matching\n * SQLite's native dialect. Call sites stay polymorphic — they ask the\n * namespace for its qualifier and consume the empty/unqualified result\n * the same way Postgres consumes a `\"schema\"` prefix.\n *\n * The SQLite PSL interpreter rejects every explicit `namespace { … }`\n * block with a diagnostic naming SQLite; only the implicit\n * `__unspecified__` AST bucket reaches the SQLite interpreter, which\n * lowers it to this singleton.\n */\nexport class SqliteUnboundDatabase extends NamespaceBase {\n static readonly instance: SqliteUnboundDatabase = new SqliteUnboundDatabase();\n\n readonly kind = 'database' as const;\n readonly id = UNBOUND_NAMESPACE_ID;\n readonly tables: Readonly<Record<string, StorageTable>>;\n\n private constructor() {\n super();\n this.tables = Object.freeze({});\n freezeNode(this);\n }\n\n qualifier(): string {\n return '';\n }\n\n qualifyTable(tableName: string): string {\n return `\"${tableName}\"`;\n }\n}\n\n/**\n * Target-supplied `Namespace` factory the SQLite target plumbs through\n * `defineContract({ createNamespace })`. SQLite has only one\n * effective namespace slot — the framework `UNBOUND_NAMESPACE_ID`\n * sentinel — so the factory always returns the singleton and rejects\n * any other coordinate. The SQL family's defensive validation in\n * `defineContract` already rejects user-declared SQLite namespaces, so\n * this throw is a structural safety net rather than a user-facing\n * surface.\n */\nexport function sqliteCreateNamespace(id: string): SqliteUnboundDatabase {\n if (id === UNBOUND_NAMESPACE_ID) {\n return SqliteUnboundDatabase.instance;\n }\n throw new Error(\n `sqliteCreateNamespace: SQLite has no schema concept; the only valid namespace id is \"${UNBOUND_NAMESPACE_ID}\" (received \"${id}\").`,\n );\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport { SqlContractSerializerBase } from '@prisma-next/family-sql/ir';\nimport {\n type Namespace,\n NamespaceBase,\n UNBOUND_NAMESPACE_ID,\n} from '@prisma-next/framework-components/ir';\nimport type {\n SqlNamespaceTablesInput,\n SqlStorage,\n StorageTable,\n} from '@prisma-next/sql-contract/types';\nimport { SqliteDatabase, SqliteUnboundDatabase } from './sqlite-unbound-database';\n\n/**\n * SQLite target `ContractSerializer` concretion. Mirrors the Postgres\n * shape: inherits the full SQL-family deserialization pipeline and\n * materialises namespace entries as SQLite database concretions that\n * expose `qualifyTable()` for runtime SQL rendering.\n */\nexport class SqliteContractSerializer extends SqlContractSerializerBase<Contract<SqlStorage>> {\n constructor() {\n super(new Map());\n }\n\n protected override hydrateSqlNamespaceEntry(\n nsId: string,\n raw: Namespace | Record<string, unknown>,\n ): Namespace | SqlNamespaceTablesInput {\n if (raw instanceof NamespaceBase) {\n return raw;\n }\n const hydrated = super.hydrateSqlNamespaceEntry(nsId, raw) as {\n id: string;\n tables: Readonly<Record<string, StorageTable>>;\n };\n const { id, tables } = hydrated;\n const emptyTables = Object.keys(tables).length === 0;\n if (id === UNBOUND_NAMESPACE_ID && emptyTables) {\n return SqliteUnboundDatabase.instance;\n }\n if (id !== UNBOUND_NAMESPACE_ID) {\n throw new Error(\n `SqliteContractSerializer: SQLite has no schema concept; the only valid namespace id is \"${UNBOUND_NAMESPACE_ID}\" (received \"${id}\").`,\n );\n }\n return new SqliteDatabase({ id, tables });\n }\n}\n"],"mappings":";;;;;;;AAgBA,IAAa,iBAAb,cAAoC,cAAc;CAChD,OAAgB;CAChB;CACA;CAEA,YAAY,OAA4B;EACtC,MAAM;EACN,KAAK,KAAK,MAAM;EAChB,KAAK,SAAS,OAAO,OAAO,EAAE,GAAG,MAAM,OAAO,CAAC;EAC/C,WAAW,IAAI;CACjB;CAEA,YAAoB;EAClB,OAAO;CACT;CAEA,aAAa,WAA2B;EACtC,OAAO,IAAI,UAAU;CACvB;AACF;;;;;;;;;;;;;;;;;;AAmBA,IAAa,wBAAb,MAAa,8BAA8B,cAAc;CACvD,OAAgB,WAAkC,IAAI,sBAAsB;CAE5E,OAAgB;CAChB,KAAc;CACd;CAEA,cAAsB;EACpB,MAAM;EACN,KAAK,SAAS,OAAO,OAAO,CAAC,CAAC;EAC9B,WAAW,IAAI;CACjB;CAEA,YAAoB;EAClB,OAAO;CACT;CAEA,aAAa,WAA2B;EACtC,OAAO,IAAI,UAAU;CACvB;AACF;;;;;;;;;;;AAYA,SAAgB,sBAAsB,IAAmC;CACvE,IAAI,OAAO,sBACT,OAAO,sBAAsB;CAE/B,MAAM,IAAI,MACR,wFAAwF,qBAAqB,eAAe,GAAG,IACjI;AACF;;;;;;;;;ACzEA,IAAa,2BAAb,cAA8C,0BAAgD;CAC5F,cAAc;EACZ,sBAAM,IAAI,IAAI,CAAC;CACjB;CAEA,yBACE,MACA,KACqC;EACrC,IAAI,eAAe,eACjB,OAAO;EAMT,MAAM,EAAE,IAAI,WAJK,MAAM,yBAAyB,MAAM,GAIxB;EAC9B,MAAM,cAAc,OAAO,KAAK,MAAM,EAAE,WAAW;EACnD,IAAI,OAAO,wBAAwB,aACjC,OAAO,sBAAsB;EAE/B,IAAI,OAAO,sBACT,MAAM,IAAI,MACR,2FAA2F,qBAAqB,eAAe,GAAG,IACpI;EAEF,OAAO,IAAI,eAAe;GAAE;GAAI;EAAO,CAAC;CAC1C;AACF"}
@@ -1,4 +1,4 @@
1
- import { t as SqlitePlanTargetDetails } from "./planner-target-details-B-VJkUsu.mjs";
1
+ import { t as SqlitePlanTargetDetails } from "./planner-target-details-RVPqF2ug.mjs";
2
2
  import { Migration } from "@prisma-next/family-sql/migration";
3
3
 
4
4
  //#region src/core/migrations/sqlite-migration.d.ts
@@ -14,4 +14,4 @@ declare abstract class SqliteMigration extends Migration<SqlitePlanTargetDetails
14
14
  }
15
15
  //#endregion
16
16
  export { SqliteMigration as t };
17
- //# sourceMappingURL=sqlite-migration-Feio34n3.d.mts.map
17
+ //# sourceMappingURL=sqlite-migration-D-O3Glz1.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-migration-Feio34n3.d.mts","names":[],"sources":["../src/core/migrations/sqlite-migration.ts"],"mappings":";;;;;;AAUA;;;;;uBAAsB,eAAA,SAAwB,SAAY,CAAC,uBAAA;EAAA,SAChD,QAAA;AAAA"}
1
+ {"version":3,"file":"sqlite-migration-D-O3Glz1.d.mts","names":[],"sources":["../src/core/migrations/sqlite-migration.ts"],"mappings":";;;;;;AAUA;;;;;uBAAsB,eAAA,SAAwB,SAAY,CAAC,uBAAA;EAAA,SAChD,QAAA;AAAA"}
@@ -33,7 +33,10 @@ const ensureMarkerTableStatement = {
33
33
  const ensureLedgerTableStatement = {
34
34
  sql: `CREATE TABLE IF NOT EXISTS _prisma_ledger (
35
35
  id INTEGER PRIMARY KEY AUTOINCREMENT,
36
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
36
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
37
+ space TEXT NOT NULL,
38
+ migration_name TEXT NOT NULL,
39
+ migration_hash TEXT NOT NULL,
37
40
  origin_core_hash TEXT,
38
41
  origin_profile_hash TEXT,
39
42
  destination_core_hash TEXT NOT NULL,
@@ -122,6 +125,9 @@ function buildWriteMarkerStatements(input) {
122
125
  function buildLedgerInsertStatement(input) {
123
126
  return {
124
127
  sql: `INSERT INTO _prisma_ledger (
128
+ space,
129
+ migration_name,
130
+ migration_hash,
125
131
  origin_core_hash,
126
132
  origin_profile_hash,
127
133
  destination_core_hash,
@@ -136,9 +142,15 @@ function buildLedgerInsertStatement(input) {
136
142
  ?,
137
143
  ?,
138
144
  ?,
145
+ ?,
146
+ ?,
147
+ ?,
139
148
  ?
140
149
  )`,
141
150
  params: [
151
+ input.space,
152
+ input.migrationName,
153
+ input.migrationHash,
142
154
  input.originStorageHash ?? null,
143
155
  input.originProfileHash ?? null,
144
156
  input.destinationStorageHash,
@@ -155,4 +167,4 @@ function jsonParam(value) {
155
167
  //#endregion
156
168
  export { buildLedgerInsertStatement as a, ensureMarkerTableStatement as c, MARKER_TABLE_NAME as i, readMarkerStatement as l, CONTROL_TABLE_NAMES as n, buildWriteMarkerStatements as o, LEDGER_TABLE_NAME as r, ensureLedgerTableStatement as s, APP_SPACE_ID$1 as t };
157
169
 
158
- //# sourceMappingURL=statement-builders-BiRmeyJM.mjs.map
170
+ //# sourceMappingURL=statement-builders-D1Q_i4sb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statement-builders-D1Q_i4sb.mjs","names":["APP_SPACE_ID"],"sources":["../src/core/migrations/statement-builders.ts"],"sourcesContent":["import { APP_SPACE_ID } from '@prisma-next/framework-components/control';\n\nexport { APP_SPACE_ID };\n\nexport interface SqlStatement {\n readonly sql: string;\n readonly params: readonly unknown[];\n}\n\nexport const MARKER_TABLE_NAME = '_prisma_marker';\nexport const LEDGER_TABLE_NAME = '_prisma_ledger';\n\n/**\n * Control tables the runner creates/manages. The planner must not drop these\n * when reconciling \"extra\" tables against the contract.\n */\nexport const CONTROL_TABLE_NAMES: ReadonlySet<string> = new Set([\n MARKER_TABLE_NAME,\n LEDGER_TABLE_NAME,\n]);\n\n/**\n * Schema for `_prisma_marker`. The `space TEXT PRIMARY KEY` shape\n * supports one row per loaded contract space (`'app'`,\n * `'<extension-id>'`, …); brand-new databases create this shape\n * directly. The migration runner detects pre-1.0 single-row markers\n * (no `space` column) at boot and fails with a structured\n * `LEGACY_MARKER_SHAPE` error rather than auto-rebuilding the table —\n * see `specs/framework-mechanism.spec.md § 2`.\n */\nexport const ensureMarkerTableStatement: SqlStatement = {\n sql: `CREATE TABLE IF NOT EXISTS _prisma_marker (\n space TEXT NOT NULL PRIMARY KEY DEFAULT '${APP_SPACE_ID}',\n core_hash TEXT NOT NULL,\n profile_hash TEXT NOT NULL,\n contract_json TEXT,\n canonical_version INTEGER,\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n app_tag TEXT,\n meta TEXT NOT NULL DEFAULT '{}',\n invariants TEXT NOT NULL DEFAULT '[]'\n )`,\n params: [],\n};\n\nexport const ensureLedgerTableStatement: SqlStatement = {\n sql: `CREATE TABLE IF NOT EXISTS _prisma_ledger (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),\n space TEXT NOT NULL,\n migration_name TEXT NOT NULL,\n migration_hash TEXT NOT NULL,\n origin_core_hash TEXT,\n origin_profile_hash TEXT,\n destination_core_hash TEXT NOT NULL,\n destination_profile_hash TEXT,\n contract_json_before TEXT,\n contract_json_after TEXT,\n operations TEXT NOT NULL\n )`,\n params: [],\n};\n\nexport function readMarkerStatement(space: string): SqlStatement {\n return {\n sql: `SELECT\n core_hash,\n profile_hash,\n contract_json,\n canonical_version,\n updated_at,\n app_tag,\n meta,\n invariants\n FROM _prisma_marker\n WHERE space = ?`,\n params: [space],\n };\n}\n\nexport interface WriteMarkerInput {\n /**\n * Logical space identifier for this marker row. Required at every\n * call site so the type system surfaces every place that needs to\n * thread the value (rather than letting an `?? APP_SPACE_ID`\n * fall-through silently collapse per-space markers onto the\n * `'app'` row). App-plan callers pass {@link APP_SPACE_ID}\n * (`'app'`); per-extension callers pass the extension's space id.\n */\n readonly space: string;\n readonly storageHash: string;\n readonly profileHash: string;\n readonly contractJson?: unknown;\n readonly canonicalVersion?: number | null;\n readonly appTag?: string | null;\n readonly meta?: Record<string, unknown>;\n /**\n * Invariants to write into `marker.invariants`. Stored as a JSON-encoded\n * TEXT array — SQLite has no native array type. The runner is responsible\n * for merging with the existing column (no SQL-side merge here, unlike\n * Postgres) before passing them in: BEGIN EXCLUSIVE on the migration\n * transaction makes the read-then-merge-then-write sequence safe.\n */\n readonly invariants: readonly string[];\n}\n\nexport function buildWriteMarkerStatements(input: WriteMarkerInput): {\n readonly insert: SqlStatement;\n readonly update: SqlStatement;\n} {\n const params: readonly unknown[] = [\n input.space,\n input.storageHash,\n input.profileHash,\n jsonParam(input.contractJson),\n input.canonicalVersion ?? null,\n input.appTag ?? null,\n jsonParam(input.meta ?? {}),\n jsonParam(input.invariants),\n ];\n\n return {\n insert: {\n sql: `INSERT INTO _prisma_marker (\n space,\n core_hash,\n profile_hash,\n contract_json,\n canonical_version,\n updated_at,\n app_tag,\n meta,\n invariants\n ) VALUES (\n ?,\n ?,\n ?,\n ?,\n ?,\n datetime('now'),\n ?,\n ?,\n ?\n )`,\n params,\n },\n update: {\n sql: `UPDATE _prisma_marker SET\n core_hash = ?,\n profile_hash = ?,\n contract_json = ?,\n canonical_version = ?,\n updated_at = datetime('now'),\n app_tag = ?,\n meta = ?,\n invariants = ?\n WHERE space = ?`,\n params: [\n input.storageHash,\n input.profileHash,\n jsonParam(input.contractJson),\n input.canonicalVersion ?? null,\n input.appTag ?? null,\n jsonParam(input.meta ?? {}),\n jsonParam(input.invariants),\n input.space,\n ],\n },\n };\n}\n\nexport interface LedgerInsertInput {\n readonly space: string;\n readonly migrationName: string;\n readonly migrationHash: string;\n readonly originStorageHash?: string | null;\n readonly originProfileHash?: string | null;\n readonly destinationStorageHash: string;\n readonly destinationProfileHash?: string | null;\n readonly contractJsonBefore?: unknown;\n readonly contractJsonAfter?: unknown;\n readonly operations: unknown;\n}\n\nexport function buildLedgerInsertStatement(input: LedgerInsertInput): SqlStatement {\n return {\n sql: `INSERT INTO _prisma_ledger (\n space,\n migration_name,\n migration_hash,\n origin_core_hash,\n origin_profile_hash,\n destination_core_hash,\n destination_profile_hash,\n contract_json_before,\n contract_json_after,\n operations\n ) VALUES (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )`,\n params: [\n input.space,\n input.migrationName,\n input.migrationHash,\n input.originStorageHash ?? null,\n input.originProfileHash ?? null,\n input.destinationStorageHash,\n input.destinationProfileHash ?? null,\n jsonParam(input.contractJsonBefore),\n jsonParam(input.contractJsonAfter),\n jsonParam(input.operations),\n ],\n };\n}\n\nfunction jsonParam(value: unknown): string {\n return JSON.stringify(value ?? null);\n}\n"],"mappings":";;AASA,MAAa,oBAAoB;AACjC,MAAa,oBAAoB;;;;;AAMjC,MAAa,sBAA2C,IAAI,IAAI,CAC9D,mBACA,iBACF,CAAC;;;;;;;;;;AAWD,MAAa,6BAA2C;CACtD,KAAK;+CACwCA,eAAa;;;;;;;;;;CAU1D,QAAQ,CAAC;AACX;AAEA,MAAa,6BAA2C;CACtD,KAAK;;;;;;;;;;;;;;CAcL,QAAQ,CAAC;AACX;AAEA,SAAgB,oBAAoB,OAA6B;CAC/D,OAAO;EACL,KAAK;;;;;;;;;;;EAWL,QAAQ,CAAC,KAAK;CAChB;AACF;AA4BA,SAAgB,2BAA2B,OAGzC;CAYA,OAAO;EACL,QAAQ;GACN,KAAK;;;;;;;;;;;;;;;;;;;;;GAqBL,QAAA;IAjCF,MAAM;IACN,MAAM;IACN,MAAM;IACN,UAAU,MAAM,YAAY;IAC5B,MAAM,oBAAoB;IAC1B,MAAM,UAAU;IAChB,UAAU,MAAM,QAAQ,CAAC,CAAC;IAC1B,UAAU,MAAM,UAAU;GA0BnB;EACP;EACA,QAAQ;GACN,KAAK;;;;;;;;;;GAUL,QAAQ;IACN,MAAM;IACN,MAAM;IACN,UAAU,MAAM,YAAY;IAC5B,MAAM,oBAAoB;IAC1B,MAAM,UAAU;IAChB,UAAU,MAAM,QAAQ,CAAC,CAAC;IAC1B,UAAU,MAAM,UAAU;IAC1B,MAAM;GACR;EACF;CACF;AACF;AAeA,SAAgB,2BAA2B,OAAwC;CACjF,OAAO;EACL,KAAK;;;;;;;;;;;;;;;;;;;;;;;EAuBL,QAAQ;GACN,MAAM;GACN,MAAM;GACN,MAAM;GACN,MAAM,qBAAqB;GAC3B,MAAM,qBAAqB;GAC3B,MAAM;GACN,MAAM,0BAA0B;GAChC,UAAU,MAAM,kBAAkB;GAClC,UAAU,MAAM,iBAAiB;GACjC,UAAU,MAAM,UAAU;EAC5B;CACF;AACF;AAEA,SAAS,UAAU,OAAwB;CACzC,OAAO,KAAK,UAAU,SAAS,IAAI;AACrC"}
@@ -54,6 +54,9 @@ declare function buildWriteMarkerStatements(input: WriteMarkerInput): {
54
54
  readonly update: SqlStatement;
55
55
  };
56
56
  interface LedgerInsertInput {
57
+ readonly space: string;
58
+ readonly migrationName: string;
59
+ readonly migrationHash: string;
57
60
  readonly originStorageHash?: string | null;
58
61
  readonly originProfileHash?: string | null;
59
62
  readonly destinationStorageHash: string;
@@ -1 +1 @@
1
- {"version":3,"file":"statement-builders.d.mts","names":[],"sources":["../src/core/migrations/statement-builders.ts"],"mappings":";;;UAIiB,YAAA;EAAA,SACN,GAAA;EAAA,SACA,MAAM;AAAA;AAAA,cAGJ,iBAAA;AAAA,cACA,iBAAA;AADb;;;;AAAA,cAOa,mBAAA,EAAqB,WAAW;AAN7C;;;;AAA8B;AAM9B;;;;AANA,cAoBa,0BAAA,EAA4B,YAaxC;AAAA,cAEY,0BAAA,EAA4B,YAaxC;AAAA,iBAEe,mBAAA,CAAoB,KAAA,WAAgB,YAAY;AAAA,UAiB/C,gBAAA;EAlChB;AAAA;AAED;;;;AAaC;AAED;EAjBC,SA2CU,KAAA;EAAA,SACA,WAAA;EAAA,SACA,WAAA;EAAA,SACA,YAAA;EAAA,SACA,gBAAA;EAAA,SACA,MAAA;EAAA,SACA,IAAA,GAAO,MAAM;EAAA;;;;;;;EAAA,SAQb,UAAA;AAAA;AAAA,iBAGK,0BAAA,CAA2B,KAAA,EAAO,gBAAA;EAAA,SACvC,MAAA,EAAQ,YAAA;EAAA,SACR,MAAA,EAAQ,YAAA;AAAA;AAAA,UA+DF,iBAAA;EAAA,SACN,iBAAA;EAAA,SACA,iBAAA;EAAA,SACA,sBAAA;EAAA,SACA,sBAAA;EAAA,SACA,kBAAA;EAAA,SACA,iBAAA;EAAA,SACA,UAAA;AAAA;AAAA,iBAGK,0BAAA,CAA2B,KAAA,EAAO,iBAAA,GAAoB,YAAY"}
1
+ {"version":3,"file":"statement-builders.d.mts","names":[],"sources":["../src/core/migrations/statement-builders.ts"],"mappings":";;;UAIiB,YAAA;EAAA,SACN,GAAA;EAAA,SACA,MAAM;AAAA;AAAA,cAGJ,iBAAA;AAAA,cACA,iBAAA;AADb;;;;AAAA,cAOa,mBAAA,EAAqB,WAAW;AAN7C;;;;AAA8B;AAM9B;;;;AANA,cAoBa,0BAAA,EAA4B,YAaxC;AAAA,cAEY,0BAAA,EAA4B,YAgBxC;AAAA,iBAEe,mBAAA,CAAoB,KAAA,WAAgB,YAAY;AAAA,UAiB/C,gBAAA;EArChB;AAAA;AAED;;;;AAgBC;AAED;EApBC,SA8CU,KAAA;EAAA,SACA,WAAA;EAAA,SACA,WAAA;EAAA,SACA,YAAA;EAAA,SACA,gBAAA;EAAA,SACA,MAAA;EAAA,SACA,IAAA,GAAO,MAAM;EAAA;;;;;;;EAAA,SAQb,UAAA;AAAA;AAAA,iBAGK,0BAAA,CAA2B,KAAA,EAAO,gBAAA;EAAA,SACvC,MAAA,EAAQ,YAAA;EAAA,SACR,MAAA,EAAQ,YAAA;AAAA;AAAA,UA+DF,iBAAA;EAAA,SACN,KAAA;EAAA,SACA,aAAA;EAAA,SACA,aAAA;EAAA,SACA,iBAAA;EAAA,SACA,iBAAA;EAAA,SACA,sBAAA;EAAA,SACA,sBAAA;EAAA,SACA,kBAAA;EAAA,SACA,iBAAA;EAAA,SACA,UAAA;AAAA;AAAA,iBAGK,0BAAA,CAA2B,KAAA,EAAO,iBAAA,GAAoB,YAAY"}
@@ -1,2 +1,2 @@
1
- import { a as buildLedgerInsertStatement, c as ensureMarkerTableStatement, i as MARKER_TABLE_NAME, l as readMarkerStatement, n as CONTROL_TABLE_NAMES, o as buildWriteMarkerStatements, r as LEDGER_TABLE_NAME, s as ensureLedgerTableStatement, t as APP_SPACE_ID } from "./statement-builders-BiRmeyJM.mjs";
1
+ import { a as buildLedgerInsertStatement, c as ensureMarkerTableStatement, i as MARKER_TABLE_NAME, l as readMarkerStatement, n as CONTROL_TABLE_NAMES, o as buildWriteMarkerStatements, r as LEDGER_TABLE_NAME, s as ensureLedgerTableStatement, t as APP_SPACE_ID } from "./statement-builders-D1Q_i4sb.mjs";
2
2
  export { APP_SPACE_ID, CONTROL_TABLE_NAMES, LEDGER_TABLE_NAME, MARKER_TABLE_NAME, buildLedgerInsertStatement, buildWriteMarkerStatements, ensureLedgerTableStatement, ensureMarkerTableStatement, readMarkerStatement };
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
1
  {
2
2
  "name": "@prisma-next/target-sqlite",
3
- "version": "0.12.0-dev.3",
3
+ "version": "0.12.0-dev.30",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "dependencies": {
8
- "@prisma-next/cli": "0.12.0-dev.3",
9
- "@prisma-next/contract": "0.12.0-dev.3",
10
- "@prisma-next/errors": "0.12.0-dev.3",
11
- "@prisma-next/family-sql": "0.12.0-dev.3",
12
- "@prisma-next/framework-components": "0.12.0-dev.3",
13
- "@prisma-next/migration-tools": "0.12.0-dev.3",
14
- "@prisma-next/sql-contract": "0.12.0-dev.3",
15
- "@prisma-next/sql-errors": "0.12.0-dev.3",
16
- "@prisma-next/sql-relational-core": "0.12.0-dev.3",
17
- "@prisma-next/sql-runtime": "0.12.0-dev.3",
18
- "@prisma-next/sql-schema-ir": "0.12.0-dev.3",
19
- "@prisma-next/ts-render": "0.12.0-dev.3",
20
- "@prisma-next/utils": "0.12.0-dev.3",
8
+ "@prisma-next/cli": "0.12.0-dev.30",
9
+ "@prisma-next/contract": "0.12.0-dev.30",
10
+ "@prisma-next/errors": "0.12.0-dev.30",
11
+ "@prisma-next/family-sql": "0.12.0-dev.30",
12
+ "@prisma-next/framework-components": "0.12.0-dev.30",
13
+ "@prisma-next/migration-tools": "0.12.0-dev.30",
14
+ "@prisma-next/sql-contract": "0.12.0-dev.30",
15
+ "@prisma-next/sql-errors": "0.12.0-dev.30",
16
+ "@prisma-next/sql-relational-core": "0.12.0-dev.30",
17
+ "@prisma-next/sql-runtime": "0.12.0-dev.30",
18
+ "@prisma-next/sql-schema-ir": "0.12.0-dev.30",
19
+ "@prisma-next/ts-render": "0.12.0-dev.30",
20
+ "@prisma-next/utils": "0.12.0-dev.30",
21
21
  "@standard-schema/spec": "1.1.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@prisma-next/driver-sqlite": "0.12.0-dev.3",
25
- "@prisma-next/test-utils": "0.12.0-dev.3",
26
- "@prisma-next/tsconfig": "0.12.0-dev.3",
27
- "@prisma-next/tsdown": "0.12.0-dev.3",
24
+ "@prisma-next/driver-sqlite": "0.12.0-dev.30",
25
+ "@prisma-next/test-utils": "0.12.0-dev.30",
26
+ "@prisma-next/tsconfig": "0.12.0-dev.30",
27
+ "@prisma-next/tsdown": "0.12.0-dev.30",
28
28
  "tsdown": "0.22.0",
29
29
  "typescript": "5.9.3",
30
30
  "vitest": "4.1.6"
@@ -45,7 +45,9 @@
45
45
  "./codec-ids": "./dist/codec-ids.mjs",
46
46
  "./codec-types": "./dist/codec-types.mjs",
47
47
  "./codecs": "./dist/codecs.mjs",
48
+ "./contract-free": "./dist/contract-free.mjs",
48
49
  "./control": "./dist/control.mjs",
50
+ "./ddl": "./dist/ddl.mjs",
49
51
  "./default-normalizer": "./dist/default-normalizer.mjs",
50
52
  "./migration": "./dist/migration.mjs",
51
53
  "./native-type-normalizer": "./dist/native-type-normalizer.mjs",
@@ -0,0 +1,54 @@
1
+ import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
2
+ import type { DdlNode } from '@prisma-next/sql-relational-core/ast';
3
+ import { col, fn, lit } from '@prisma-next/sql-relational-core/contract-free';
4
+ import { createTable } from './ddl';
5
+
6
+ const markerColumns = [
7
+ col('space', 'TEXT', { notNull: true, primaryKey: true, default: lit(APP_SPACE_ID) }),
8
+ col('core_hash', 'TEXT', { notNull: true }),
9
+ col('profile_hash', 'TEXT', { notNull: true }),
10
+ col('contract_json', 'TEXT'),
11
+ col('canonical_version', 'INTEGER'),
12
+ col('updated_at', 'TEXT', { notNull: true, default: fn("datetime('now')") }),
13
+ col('app_tag', 'TEXT'),
14
+ col('meta', 'TEXT', { notNull: true, default: lit('{}') }),
15
+ col('invariants', 'TEXT', { notNull: true, default: lit('[]') }),
16
+ ] as const;
17
+
18
+ const ledgerColumns = [
19
+ col('id', 'INTEGER PRIMARY KEY AUTOINCREMENT'),
20
+ col('created_at', 'TEXT', {
21
+ notNull: true,
22
+ default: fn("strftime('%Y-%m-%dT%H:%M:%fZ','now')"),
23
+ }),
24
+ col('space', 'TEXT', { notNull: true }),
25
+ col('migration_name', 'TEXT', { notNull: true }),
26
+ col('migration_hash', 'TEXT', { notNull: true }),
27
+ col('origin_core_hash', 'TEXT'),
28
+ col('origin_profile_hash', 'TEXT'),
29
+ col('destination_core_hash', 'TEXT', { notNull: true }),
30
+ col('destination_profile_hash', 'TEXT'),
31
+ col('contract_json_before', 'TEXT'),
32
+ col('contract_json_after', 'TEXT'),
33
+ col('operations', 'TEXT', { notNull: true }),
34
+ ] as const;
35
+
36
+ const markerTable = createTable({
37
+ table: '_prisma_marker',
38
+ ifNotExists: true,
39
+ columns: markerColumns,
40
+ });
41
+
42
+ const ledgerTable = createTable({
43
+ table: '_prisma_ledger',
44
+ ifNotExists: true,
45
+ columns: ledgerColumns,
46
+ });
47
+
48
+ export function buildSignMarkerBootstrapQueries(): readonly DdlNode[] {
49
+ return [markerTable];
50
+ }
51
+
52
+ export function buildControlTableBootstrapQueries(): readonly DdlNode[] {
53
+ return [markerTable, ledgerTable];
54
+ }
@@ -0,0 +1,21 @@
1
+ import type { DdlColumn } from '@prisma-next/sql-relational-core/ast';
2
+ import { SqliteCreateTable } from '../core/ddl/nodes';
3
+
4
+ /**
5
+ * Build a SQLite `CREATE TABLE` query node.
6
+ *
7
+ * Precondition: identifiers (`table`, column names/types) are emitted to SQL
8
+ * verbatim — they are not quoted or escaped, so callers must pass pre-trusted
9
+ * values (e.g. fixed control-plane identifiers). String-literal default values,
10
+ * by contrast, are single-quote-escaped (embedded `'` doubled) by the renderer.
11
+ * Identifier quoting for untrusted identifiers is added when the migration
12
+ * planner adopts this lowering path.
13
+ */
14
+ export function createTable(options: {
15
+ readonly table: string;
16
+ readonly schema?: string;
17
+ readonly ifNotExists?: boolean;
18
+ readonly columns: readonly DdlColumn[];
19
+ }): SqliteCreateTable {
20
+ return new SqliteCreateTable(options);
21
+ }
@@ -0,0 +1,41 @@
1
+ import { type DdlColumn, DdlNode } from '@prisma-next/sql-relational-core/ast';
2
+
3
+ export interface SqliteDdlVisitor<R> {
4
+ createTable(node: SqliteCreateTable): R;
5
+ }
6
+
7
+ export abstract class SqliteDdlNode extends DdlNode {
8
+ abstract accept<R>(visitor: SqliteDdlVisitor<R>): R;
9
+ }
10
+
11
+ function freezeDdlColumns(columns: readonly DdlColumn[]): ReadonlyArray<DdlColumn> {
12
+ return Object.freeze([...columns]);
13
+ }
14
+
15
+ export class SqliteCreateTable extends SqliteDdlNode {
16
+ readonly kind = 'create-table' as const;
17
+ readonly table: string;
18
+ readonly schema: string | undefined;
19
+ readonly ifNotExists: boolean | undefined;
20
+ readonly columns: ReadonlyArray<DdlColumn>;
21
+
22
+ constructor(options: {
23
+ readonly table: string;
24
+ readonly schema?: string;
25
+ readonly ifNotExists?: boolean;
26
+ readonly columns: readonly DdlColumn[];
27
+ }) {
28
+ super();
29
+ this.table = options.table;
30
+ this.schema = options.schema;
31
+ this.ifNotExists = options.ifNotExists;
32
+ this.columns = freezeDdlColumns(options.columns);
33
+ this.freeze();
34
+ }
35
+
36
+ override accept<R>(visitor: SqliteDdlVisitor<R>): R {
37
+ return visitor.createTable(this);
38
+ }
39
+ }
40
+
41
+ export type AnySqliteDdlNode = SqliteCreateTable;
@@ -1,9 +1,11 @@
1
+ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
1
2
  import type { CodecTypes } from '../exports/codec-types';
2
3
  import { sqliteAuthoringFieldPresets } from './authoring';
3
4
  import { sqliteTargetDescriptorMetaRuntime } from './descriptor-meta-runtime';
4
5
 
5
6
  const sqliteTargetDescriptorMetaBase = {
6
7
  ...sqliteTargetDescriptorMetaRuntime,
8
+ defaultNamespaceId: UNBOUND_NAMESPACE_ID,
7
9
  authoring: {
8
10
  field: sqliteAuthoringFieldPresets,
9
11
  },
@@ -1,4 +1,4 @@
1
- import type { ContractMarkerRecord } from '@prisma-next/contract/types';
1
+ import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
2
2
  import type {
3
3
  MigrationOperationPolicy,
4
4
  SqlControlFamilyInstance,
@@ -19,6 +19,7 @@ import type {
19
19
  MigrationRunnerResult,
20
20
  } from '@prisma-next/framework-components/control';
21
21
  import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
22
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
22
23
  import { ifDefined } from '@prisma-next/utils/defined';
23
24
  import type { Result } from '@prisma-next/utils/result';
24
25
  import { notOk, ok, okVoid } from '@prisma-next/utils/result';
@@ -28,8 +29,6 @@ import type { SqlitePlanTargetDetails } from './planner-target-details';
28
29
  import {
29
30
  buildLedgerInsertStatement,
30
31
  buildWriteMarkerStatements,
31
- ensureLedgerTableStatement,
32
- ensureMarkerTableStatement,
33
32
  MARKER_TABLE_NAME,
34
33
  readMarkerStatement,
35
34
  type SqlStatement,
@@ -70,7 +69,7 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
70
69
  const policyCheck = this.enforcePolicyCompatibility(options.policy, options.plan.operations);
71
70
  if (!policyCheck.ok) return policyCheck;
72
71
 
73
- const ensureResult = await this.ensureControlTables(driver);
72
+ const ensureResult = await this.ensureControlTables(driver, options.destinationContract);
74
73
  if (!ensureResult.ok) return ensureResult;
75
74
  const existingMarker = await this.readMarker(driver, space);
76
75
 
@@ -127,7 +126,7 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
127
126
 
128
127
  if (!isSelfEdgeNoOp) {
129
128
  await this.upsertMarker(driver, options, existingMarker, space);
130
- await this.recordLedgerEntry(driver, options, existingMarker, executedOperations);
129
+ await this.recordLedgerEntries(driver, options, existingMarker, executedOperations);
131
130
  }
132
131
 
133
132
  return runnerSuccess({
@@ -306,17 +305,16 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
306
305
 
307
306
  private async ensureControlTables(
308
307
  driver: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>['driver'],
308
+ contract: Contract<SqlStorage>,
309
309
  ): Promise<Result<void, SqlMigrationRunnerFailure>> {
310
- // Pre-1.0 zero-range guardrail: detect a pre-cleanup single-row
311
- // marker table (no `space` column) and surface a structured failure
312
- // rather than silently rebuilding the table into the per-space
313
- // shape. See `specs/framework-mechanism.spec.md § 2`.
314
310
  const legacyDetection = await this.detectLegacyMarkerShape(driver);
315
311
  if (!legacyDetection.ok) {
316
312
  return legacyDetection;
317
313
  }
318
- await this.executeStatement(driver, ensureMarkerTableStatement);
319
- await this.executeStatement(driver, ensureLedgerTableStatement);
314
+ const lowererContext = { contract };
315
+ for (const query of this.family.bootstrapControlTableQueries()) {
316
+ await this.executeStatement(driver, this.family.lowerAst(query, lowererContext));
317
+ }
320
318
  return okVoid();
321
319
  }
322
320
 
@@ -623,25 +621,55 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
623
621
  await this.executeStatement(driver, statement);
624
622
  }
625
623
 
626
- private async recordLedgerEntry(
624
+ private async recordLedgerEntries(
627
625
  driver: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>['driver'],
628
626
  options: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>,
629
627
  existingMarker: ContractMarkerRecord | null,
630
628
  executedOperations: readonly SqlMigrationPlanOperation<SqlitePlanTargetDetails>[],
631
629
  ): Promise<void> {
632
- const ledgerStatement = buildLedgerInsertStatement({
633
- originStorageHash: existingMarker?.storageHash ?? null,
634
- originProfileHash: existingMarker?.profileHash ?? null,
635
- destinationStorageHash: options.plan.destination.storageHash,
636
- destinationProfileHash:
637
- options.plan.destination.profileHash ??
638
- options.destinationContract.profileHash ??
639
- options.plan.destination.storageHash,
640
- contractJsonBefore: existingMarker?.contractJson ?? null,
641
- contractJsonAfter: options.destinationContract,
642
- operations: executedOperations,
643
- });
644
- await this.executeStatement(driver, ledgerStatement);
630
+ const plan = options.plan;
631
+ const space = plan.spaceId;
632
+ const destinationProfileHash =
633
+ plan.destination.profileHash ??
634
+ options.destinationContract.profileHash ??
635
+ plan.destination.storageHash;
636
+ const edges = options.migrationEdges;
637
+ const totalEdgeOps = edges.reduce((sum, edge) => sum + edge.operationCount, 0);
638
+ if (totalEdgeOps !== plan.operations.length) {
639
+ throw new Error(
640
+ `Ledger write: plan.operations length (${plan.operations.length}) does not match sum of migrationEdges operationCount (${totalEdgeOps})`,
641
+ );
642
+ }
643
+ // The ledger records the operations as executed — idempotency-skipped ops
644
+ // are substituted with skip records (empty `execute`) by `applyPlan`, so the
645
+ // journal reflects what actually ran rather than the raw plan.
646
+ let offset = 0;
647
+ const lastIndex = edges.length - 1;
648
+ for (const [i, edge] of edges.entries()) {
649
+ const edgeOps = executedOperations.slice(offset, offset + edge.operationCount);
650
+ offset += edge.operationCount;
651
+ const isFirst = i === 0;
652
+ const isLast = i === lastIndex;
653
+ await this.executeStatement(
654
+ driver,
655
+ buildLedgerInsertStatement({
656
+ space,
657
+ migrationName: edge.dirName,
658
+ migrationHash: edge.migrationHash,
659
+ originStorageHash: edge.from === '' ? null : edge.from,
660
+ originProfileHash:
661
+ isFirst && existingMarker?.storageHash === edge.from
662
+ ? (existingMarker.profileHash ?? null)
663
+ : null,
664
+ destinationStorageHash: edge.to,
665
+ destinationProfileHash:
666
+ isLast && edge.to === plan.destination.storageHash ? destinationProfileHash : null,
667
+ contractJsonBefore: isFirst ? (existingMarker?.contractJson ?? null) : null,
668
+ contractJsonAfter: isLast ? options.destinationContract : null,
669
+ operations: edgeOps,
670
+ }),
671
+ );
672
+ }
645
673
  }
646
674
 
647
675
  private async beginExclusiveTransaction(
@@ -46,7 +46,10 @@ export const ensureMarkerTableStatement: SqlStatement = {
46
46
  export const ensureLedgerTableStatement: SqlStatement = {
47
47
  sql: `CREATE TABLE IF NOT EXISTS _prisma_ledger (
48
48
  id INTEGER PRIMARY KEY AUTOINCREMENT,
49
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
49
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
50
+ space TEXT NOT NULL,
51
+ migration_name TEXT NOT NULL,
52
+ migration_hash TEXT NOT NULL,
50
53
  origin_core_hash TEXT,
51
54
  origin_profile_hash TEXT,
52
55
  destination_core_hash TEXT NOT NULL,
@@ -167,6 +170,9 @@ export function buildWriteMarkerStatements(input: WriteMarkerInput): {
167
170
  }
168
171
 
169
172
  export interface LedgerInsertInput {
173
+ readonly space: string;
174
+ readonly migrationName: string;
175
+ readonly migrationHash: string;
170
176
  readonly originStorageHash?: string | null;
171
177
  readonly originProfileHash?: string | null;
172
178
  readonly destinationStorageHash: string;
@@ -179,6 +185,9 @@ export interface LedgerInsertInput {
179
185
  export function buildLedgerInsertStatement(input: LedgerInsertInput): SqlStatement {
180
186
  return {
181
187
  sql: `INSERT INTO _prisma_ledger (
188
+ space,
189
+ migration_name,
190
+ migration_hash,
182
191
  origin_core_hash,
183
192
  origin_profile_hash,
184
193
  destination_core_hash,
@@ -193,9 +202,15 @@ export function buildLedgerInsertStatement(input: LedgerInsertInput): SqlStateme
193
202
  ?,
194
203
  ?,
195
204
  ?,
205
+ ?,
206
+ ?,
207
+ ?,
196
208
  ?
197
209
  )`,
198
210
  params: [
211
+ input.space,
212
+ input.migrationName,
213
+ input.migrationHash,
199
214
  input.originStorageHash ?? null,
200
215
  input.originProfileHash ?? null,
201
216
  input.destinationStorageHash,
@@ -1,20 +1,49 @@
1
1
  import type { Contract } from '@prisma-next/contract/types';
2
2
  import { SqlContractSerializerBase } from '@prisma-next/family-sql/ir';
3
- import type { SqlStorage } from '@prisma-next/sql-contract/types';
3
+ import {
4
+ type Namespace,
5
+ NamespaceBase,
6
+ UNBOUND_NAMESPACE_ID,
7
+ } from '@prisma-next/framework-components/ir';
8
+ import type {
9
+ SqlNamespaceTablesInput,
10
+ SqlStorage,
11
+ StorageTable,
12
+ } from '@prisma-next/sql-contract/types';
13
+ import { SqliteDatabase, SqliteUnboundDatabase } from './sqlite-unbound-database';
4
14
 
5
15
  /**
6
16
  * SQLite target `ContractSerializer` concretion. Mirrors the Postgres
7
- * shape: inherits the full SQL-family deserialization pipeline. Today's
8
- * SQLite contract shape is the family-shared shape; no target-specific
9
- * polymorphic `storage.types` factories are registered yet.
10
- *
11
- * `serializeContract` falls through to the family-base default —
12
- * SQLite's contract is JSON-clean today. Once target-only fields land
13
- * (e.g. per-target derived storage fields) this is the home for
14
- * stripping them from the persisted envelope.
17
+ * shape: inherits the full SQL-family deserialization pipeline and
18
+ * materialises namespace entries as SQLite database concretions that
19
+ * expose `qualifyTable()` for runtime SQL rendering.
15
20
  */
16
21
  export class SqliteContractSerializer extends SqlContractSerializerBase<Contract<SqlStorage>> {
17
22
  constructor() {
18
23
  super(new Map());
19
24
  }
25
+
26
+ protected override hydrateSqlNamespaceEntry(
27
+ nsId: string,
28
+ raw: Namespace | Record<string, unknown>,
29
+ ): Namespace | SqlNamespaceTablesInput {
30
+ if (raw instanceof NamespaceBase) {
31
+ return raw;
32
+ }
33
+ const hydrated = super.hydrateSqlNamespaceEntry(nsId, raw) as {
34
+ id: string;
35
+ tables: Readonly<Record<string, StorageTable>>;
36
+ };
37
+ const { id, tables } = hydrated;
38
+ const emptyTables = Object.keys(tables).length === 0;
39
+ if (id === UNBOUND_NAMESPACE_ID && emptyTables) {
40
+ return SqliteUnboundDatabase.instance;
41
+ }
42
+ if (id !== UNBOUND_NAMESPACE_ID) {
43
+ throw new Error(
44
+ `SqliteContractSerializer: SQLite has no schema concept; the only valid namespace id is "${UNBOUND_NAMESPACE_ID}" (received "${id}").`,
45
+ );
46
+ }
47
+ return new SqliteDatabase({ id, tables });
48
+ }
20
49
  }