@prisma-next/target-sqlite 0.12.0 → 0.13.0-dev.10

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 (126) hide show
  1. package/dist/{codec-ids-CYwMu3-4.d.mts → codec-ids-BfPkjMmk.d.mts} +1 -1
  2. package/dist/{codec-ids-CYwMu3-4.d.mts.map → codec-ids-BfPkjMmk.d.mts.map} +1 -1
  3. package/dist/{codec-ids-CuUxYcd0.mjs → codec-ids-DSU7S2Li.mjs} +1 -1
  4. package/dist/{codec-ids-CuUxYcd0.mjs.map → codec-ids-DSU7S2Li.mjs.map} +1 -1
  5. package/dist/codec-ids.d.mts +1 -1
  6. package/dist/codec-ids.mjs +1 -1
  7. package/dist/{codec-types-DNauB5UT.d.mts → codec-types-izdPhp_9.d.mts} +6 -7
  8. package/dist/codec-types-izdPhp_9.d.mts.map +1 -0
  9. package/dist/codec-types.d.mts +3 -3
  10. package/dist/{codecs-BAlEiSeP.d.mts → codecs-BGeJavlQ.d.mts} +16 -18
  11. package/dist/codecs-BGeJavlQ.d.mts.map +1 -0
  12. package/dist/{codecs-DVnHtVWW.mjs → codecs-DsC4OGmU.mjs} +2 -2
  13. package/dist/{codecs-DVnHtVWW.mjs.map → codecs-DsC4OGmU.mjs.map} +1 -1
  14. package/dist/codecs.d.mts +2 -2
  15. package/dist/codecs.mjs +1 -1
  16. package/dist/contract-free.d.mts +55 -0
  17. package/dist/contract-free.d.mts.map +1 -0
  18. package/dist/contract-free.mjs +111 -0
  19. package/dist/contract-free.mjs.map +1 -0
  20. package/dist/control-tables-7KwMyJ6i.mjs +12 -0
  21. package/dist/control-tables-7KwMyJ6i.mjs.map +1 -0
  22. package/dist/control-tables.d.mts +11 -0
  23. package/dist/control-tables.d.mts.map +1 -0
  24. package/dist/control-tables.mjs +2 -0
  25. package/dist/control.d.mts +31 -15
  26. package/dist/control.d.mts.map +1 -1
  27. package/dist/control.mjs +66 -124
  28. package/dist/control.mjs.map +1 -1
  29. package/dist/ddl.d.mts +2 -0
  30. package/dist/ddl.mjs +2 -0
  31. package/dist/{default-normalizer-3Fccw7yw.mjs → default-normalizer-DuoHj9-O.mjs} +1 -1
  32. package/dist/{default-normalizer-3Fccw7yw.mjs.map → default-normalizer-DuoHj9-O.mjs.map} +1 -1
  33. package/dist/default-normalizer.mjs +1 -1
  34. package/dist/descriptor-meta-Dxx2A6PT.mjs +17 -0
  35. package/dist/descriptor-meta-Dxx2A6PT.mjs.map +1 -0
  36. package/dist/descriptor-meta-runtime-BkXK3OjD.mjs +12 -0
  37. package/dist/descriptor-meta-runtime-BkXK3OjD.mjs.map +1 -0
  38. package/dist/migration.d.mts +2 -2
  39. package/dist/migration.mjs +3 -3
  40. package/dist/{native-type-normalizer-BlN5XfD-.mjs → native-type-normalizer-CiSyVmMP.mjs} +1 -1
  41. package/dist/{native-type-normalizer-BlN5XfD-.mjs.map → native-type-normalizer-CiSyVmMP.mjs.map} +1 -1
  42. package/dist/native-type-normalizer.mjs +1 -1
  43. package/dist/nodes-D0k4z7NL.mjs +33 -0
  44. package/dist/nodes-D0k4z7NL.mjs.map +1 -0
  45. package/dist/nodes-VzaaeUTb.d.mts +29 -0
  46. package/dist/nodes-VzaaeUTb.d.mts.map +1 -0
  47. package/dist/{op-factory-call-BnPhI25-.mjs → op-factory-call-DymqdXQW.mjs} +2 -2
  48. package/dist/{op-factory-call-BnPhI25-.mjs.map → op-factory-call-DymqdXQW.mjs.map} +1 -1
  49. package/dist/op-factory-call.d.mts +2 -2
  50. package/dist/op-factory-call.d.mts.map +1 -1
  51. package/dist/op-factory-call.mjs +1 -1
  52. package/dist/pack.d.mts +9 -7
  53. package/dist/pack.d.mts.map +1 -1
  54. package/dist/pack.mjs +1 -1
  55. package/dist/{planner-CEKTRydl.mjs → planner-DSNDwQy9.mjs} +9 -9
  56. package/dist/planner-DSNDwQy9.mjs.map +1 -0
  57. package/dist/{planner-produced-sqlite-migration-CI9LdXPr.d.mts → planner-produced-sqlite-migration-C1yqJAiM.d.mts} +3 -3
  58. package/dist/{planner-produced-sqlite-migration-CI9LdXPr.d.mts.map → planner-produced-sqlite-migration-C1yqJAiM.d.mts.map} +1 -1
  59. package/dist/{planner-produced-sqlite-migration-DCsg3RDZ.mjs → planner-produced-sqlite-migration-DowV_vHw.mjs} +3 -3
  60. package/dist/{planner-produced-sqlite-migration-DCsg3RDZ.mjs.map → planner-produced-sqlite-migration-DowV_vHw.mjs.map} +1 -1
  61. package/dist/planner-produced-sqlite-migration.d.mts +1 -1
  62. package/dist/planner-produced-sqlite-migration.mjs +1 -1
  63. package/dist/{planner-target-details-Bm71XPKb.mjs → planner-target-details-H8z9TFDg.mjs} +1 -1
  64. package/dist/{planner-target-details-Bm71XPKb.mjs.map → planner-target-details-H8z9TFDg.mjs.map} +1 -1
  65. package/dist/{planner-target-details-vhvZDWK1.d.mts → planner-target-details-xR6UfIcz.d.mts} +1 -1
  66. package/dist/{planner-target-details-vhvZDWK1.d.mts.map → planner-target-details-xR6UfIcz.d.mts.map} +1 -1
  67. package/dist/planner-target-details.d.mts +1 -1
  68. package/dist/planner-target-details.mjs +1 -1
  69. package/dist/planner.d.mts +2 -2
  70. package/dist/planner.d.mts.map +1 -1
  71. package/dist/planner.mjs +1 -1
  72. package/dist/{render-ops-CSRDT4YL.mjs → render-ops-CFRbJ3Yb.mjs} +1 -1
  73. package/dist/{render-ops-CSRDT4YL.mjs.map → render-ops-CFRbJ3Yb.mjs.map} +1 -1
  74. package/dist/render-ops.d.mts +1 -1
  75. package/dist/render-ops.mjs +1 -1
  76. package/dist/runtime.d.mts +17 -1
  77. package/dist/runtime.d.mts.map +1 -1
  78. package/dist/runtime.mjs +4 -3
  79. package/dist/runtime.mjs.map +1 -1
  80. package/dist/{shared-qLsgTOZs.d.mts → shared-Dhc8mLK1.d.mts} +2 -2
  81. package/dist/{shared-qLsgTOZs.d.mts.map → shared-Dhc8mLK1.d.mts.map} +1 -1
  82. package/dist/{sql-utils-DhevMgef.mjs → sql-utils-CV8Bdgtc.mjs} +1 -1
  83. package/dist/{sql-utils-DhevMgef.mjs.map → sql-utils-CV8Bdgtc.mjs.map} +1 -1
  84. package/dist/sql-utils.mjs +1 -1
  85. package/dist/sqlite-contract-serializer-jcRu8aHh.mjs +101 -0
  86. package/dist/sqlite-contract-serializer-jcRu8aHh.mjs.map +1 -0
  87. package/dist/{sqlite-migration-BBJktVVw.mjs → sqlite-migration-CUqgmzQH.mjs} +1 -1
  88. package/dist/{sqlite-migration-BBJktVVw.mjs.map → sqlite-migration-CUqgmzQH.mjs.map} +1 -1
  89. package/dist/{sqlite-migration-DAb2NEX6.d.mts → sqlite-migration-D4XGYzgQ.d.mts} +2 -2
  90. package/dist/{sqlite-migration-DAb2NEX6.d.mts.map → sqlite-migration-D4XGYzgQ.d.mts.map} +1 -1
  91. package/dist/{tables-DGRRJasz.mjs → tables-CjB7vXCr.mjs} +5 -11
  92. package/dist/tables-CjB7vXCr.mjs.map +1 -0
  93. package/package.json +23 -21
  94. package/src/contract-free/columns.ts +44 -0
  95. package/src/contract-free/control-bootstrap.ts +54 -0
  96. package/src/contract-free/ddl.ts +26 -0
  97. package/src/core/authoring.ts +1 -1
  98. package/src/core/control-tables.ts +11 -0
  99. package/src/core/control-target.ts +4 -6
  100. package/src/core/ddl/nodes.ts +54 -0
  101. package/src/core/descriptor-meta-runtime.ts +28 -0
  102. package/src/core/descriptor-meta.ts +4 -6
  103. package/src/core/migrations/issue-planner.ts +1 -1
  104. package/src/core/migrations/operations/shared.ts +1 -8
  105. package/src/core/migrations/planner-strategies.ts +1 -1
  106. package/src/core/migrations/runner.ts +78 -83
  107. package/src/core/runtime-target.ts +2 -2
  108. package/src/core/sqlite-contract-serializer.ts +21 -9
  109. package/src/core/sqlite-unbound-database.ts +113 -26
  110. package/src/exports/contract-free.ts +6 -0
  111. package/src/exports/control-tables.ts +5 -0
  112. package/src/exports/ddl.ts +6 -0
  113. package/src/exports/runtime.ts +1 -0
  114. package/dist/codec-types-DNauB5UT.d.mts.map +0 -1
  115. package/dist/codecs-BAlEiSeP.d.mts.map +0 -1
  116. package/dist/descriptor-meta-CE2Kbn9b.mjs +0 -17
  117. package/dist/descriptor-meta-CE2Kbn9b.mjs.map +0 -1
  118. package/dist/planner-CEKTRydl.mjs.map +0 -1
  119. package/dist/statement-builders-Dne-LkAV.mjs +0 -158
  120. package/dist/statement-builders-Dne-LkAV.mjs.map +0 -1
  121. package/dist/statement-builders.d.mts +0 -68
  122. package/dist/statement-builders.d.mts.map +0 -1
  123. package/dist/statement-builders.mjs +0 -2
  124. package/dist/tables-DGRRJasz.mjs.map +0 -1
  125. package/src/core/migrations/statement-builders.ts +0 -212
  126. package/src/exports/statement-builders.ts +0 -12
@@ -1,9 +1,7 @@
1
1
  import type { ColumnDefault, Contract } from '@prisma-next/contract/types';
2
- import type {
3
- SqlControlFamilyInstance,
4
- SqlControlTargetDescriptor,
5
- } from '@prisma-next/family-sql/control';
2
+ import type { SqlControlTargetDescriptor } from '@prisma-next/family-sql/control';
6
3
  import { contractToSchemaIR } from '@prisma-next/family-sql/control';
4
+ import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
7
5
  import type {
8
6
  ControlTargetInstance,
9
7
  MigrationPlanner,
@@ -38,7 +36,7 @@ const sqliteControlTargetDescriptor: SqlControlTargetDescriptor<'sqlite', Sqlite
38
36
  contractSerializer: new SqliteContractSerializer(),
39
37
  schemaVerifier: new SqliteSchemaVerifier(),
40
38
  migrations: {
41
- createPlanner(_family: SqlControlFamilyInstance): MigrationPlanner<'sql', 'sqlite'> {
39
+ createPlanner(_adapter: SqlControlAdapter<'sqlite'>): MigrationPlanner<'sql', 'sqlite'> {
42
40
  return createSqliteMigrationPlanner();
43
41
  },
44
42
  createRunner(family) {
@@ -69,7 +67,7 @@ const sqliteControlTargetDescriptor: SqlControlTargetDescriptor<'sqlite', Sqlite
69
67
  targetId: 'sqlite',
70
68
  };
71
69
  },
72
- createPlanner(_family: SqlControlFamilyInstance) {
70
+ createPlanner(_adapter: SqlControlAdapter<'sqlite'>) {
73
71
  return createSqliteMigrationPlanner();
74
72
  },
75
73
  createRunner(family) {
@@ -0,0 +1,54 @@
1
+ import {
2
+ type DdlColumn,
3
+ DdlNode,
4
+ type DdlTableConstraint,
5
+ } from '@prisma-next/sql-relational-core/ast';
6
+
7
+ export interface SqliteDdlVisitor<R> {
8
+ createTable(node: SqliteCreateTable): R;
9
+ }
10
+
11
+ export abstract class SqliteDdlNode extends DdlNode {
12
+ abstract accept<R>(visitor: SqliteDdlVisitor<R>): R;
13
+ }
14
+
15
+ function freezeDdlColumns(columns: readonly DdlColumn[]): ReadonlyArray<DdlColumn> {
16
+ return Object.freeze([...columns]);
17
+ }
18
+
19
+ function freezeConstraints(
20
+ constraints: readonly DdlTableConstraint[] | undefined,
21
+ ): ReadonlyArray<DdlTableConstraint> | undefined {
22
+ return constraints ? Object.freeze([...constraints]) : undefined;
23
+ }
24
+
25
+ export class SqliteCreateTable extends SqliteDdlNode {
26
+ readonly kind = 'create-table' as const;
27
+ readonly table: string;
28
+ readonly schema: string | undefined;
29
+ readonly ifNotExists: boolean | undefined;
30
+ readonly columns: ReadonlyArray<DdlColumn>;
31
+ readonly constraints: ReadonlyArray<DdlTableConstraint> | undefined;
32
+
33
+ constructor(options: {
34
+ readonly table: string;
35
+ readonly schema?: string;
36
+ readonly ifNotExists?: boolean;
37
+ readonly columns: readonly DdlColumn[];
38
+ readonly constraints?: readonly DdlTableConstraint[];
39
+ }) {
40
+ super();
41
+ this.table = options.table;
42
+ this.schema = options.schema;
43
+ this.ifNotExists = options.ifNotExists;
44
+ this.columns = freezeDdlColumns(options.columns);
45
+ this.constraints = freezeConstraints(options.constraints);
46
+ this.freeze();
47
+ }
48
+
49
+ override accept<R>(visitor: SqliteDdlVisitor<R>): R {
50
+ return visitor.createTable(this);
51
+ }
52
+ }
53
+
54
+ export type AnySqliteDdlNode = SqliteCreateTable;
@@ -0,0 +1,28 @@
1
+ // Runtime-safe slice of the sqlite target descriptor metadata.
2
+ //
3
+ // This file exists separately from ./descriptor-meta on purpose: the runtime
4
+ // plane reads only `kind/familyId/targetId/id/version/capabilities` (plus the
5
+ // `__codecTypes` phantom). The `authoring` slot lives on the pack/control
6
+ // descriptor only, because authoring contributions are consumed at
7
+ // contract-construction time by `assembleAuthoringContributions` (control
8
+ // plane) and the PSL interpreter — never at runtime.
9
+ //
10
+ // Keeping the runtime closure free of the `./authoring` import is what lets
11
+ // the bundler tree-shake `@prisma-next/family-sql/control` (and its
12
+ // transitive `verify-sql-schema` chunk) out of the runtime entry. Do not
13
+ // add an `authoring` field here — if you need to, the pack/control meta in
14
+ // `./descriptor-meta` is the right place. See TML-2766 for context.
15
+ import type { CodecTypes } from '../exports/codec-types';
16
+
17
+ const sqliteTargetDescriptorMetaRuntimeBase = {
18
+ kind: 'target',
19
+ familyId: 'sql',
20
+ targetId: 'sqlite',
21
+ id: 'sqlite',
22
+ version: '0.0.1',
23
+ capabilities: {},
24
+ } as const;
25
+
26
+ export const sqliteTargetDescriptorMetaRuntime: typeof sqliteTargetDescriptorMetaRuntimeBase & {
27
+ readonly __codecTypes?: CodecTypes;
28
+ } = sqliteTargetDescriptorMetaRuntimeBase;
@@ -1,13 +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';
4
+ import { sqliteTargetDescriptorMetaRuntime } from './descriptor-meta-runtime';
3
5
 
4
6
  const sqliteTargetDescriptorMetaBase = {
5
- kind: 'target',
6
- familyId: 'sql',
7
- targetId: 'sqlite',
8
- id: 'sqlite',
9
- version: '0.0.1',
10
- capabilities: {},
7
+ ...sqliteTargetDescriptorMetaRuntime,
8
+ defaultNamespaceId: UNBOUND_NAMESPACE_ID,
11
9
  authoring: {
12
10
  field: sqliteAuthoringFieldPresets,
13
11
  },
@@ -28,6 +28,7 @@ import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
28
28
  import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
29
29
  import type { Result } from '@prisma-next/utils/result';
30
30
  import { notOk, ok } from '@prisma-next/utils/result';
31
+ import { CONTROL_TABLE_NAMES } from '../control-tables';
31
32
  import {
32
33
  AddColumnCall,
33
34
  CreateIndexCall,
@@ -55,7 +56,6 @@ import {
55
56
  sqlitePlannerStrategies,
56
57
  tableAt,
57
58
  } from './planner-strategies';
58
- import { CONTROL_TABLE_NAMES } from './statement-builders';
59
59
 
60
60
  export type { CallMigrationStrategy, StrategyContext };
61
61
 
@@ -1,4 +1,5 @@
1
1
  import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
2
+ import { REFERENTIAL_ACTION_SQL } from '@prisma-next/sql-contract/referential-action-sql';
2
3
  import type { ReferentialAction } from '@prisma-next/sql-contract/types';
3
4
  import { quoteIdentifier } from '../../sql-utils';
4
5
  import type { SqlitePlanTargetDetails } from '../planner-target-details';
@@ -75,14 +76,6 @@ export interface SqliteIndexSpec {
75
76
  readonly columns: readonly string[];
76
77
  }
77
78
 
78
- const REFERENTIAL_ACTION_SQL: Record<ReferentialAction, string> = {
79
- noAction: 'NO ACTION',
80
- restrict: 'RESTRICT',
81
- cascade: 'CASCADE',
82
- setNull: 'SET NULL',
83
- setDefault: 'SET DEFAULT',
84
- };
85
-
86
79
  /**
87
80
  * Renders a single column's inline DDL fragment within a `CREATE TABLE`
88
81
  * statement. Honours the `inlineAutoincrementPrimaryKey` flag — SQLite
@@ -61,7 +61,7 @@ export function tableAt(
61
61
  namespaceId: string,
62
62
  tableName: string,
63
63
  ): StorageTable | undefined {
64
- return storage.namespaces[namespaceId]?.tables[tableName] as StorageTable | undefined;
64
+ return storage.namespaces[namespaceId]?.entries.table[tableName] as StorageTable | undefined;
65
65
  }
66
66
 
67
67
  /**
@@ -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,
@@ -13,27 +13,17 @@ import type {
13
13
  } from '@prisma-next/family-sql/control';
14
14
  import { runnerFailure, runnerSuccess } from '@prisma-next/family-sql/control';
15
15
  import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
16
- import { type ContractMarkerRow, parseContractMarkerRow } from '@prisma-next/family-sql/verify';
17
- import type {
18
- ControlDriverInstance,
19
- MigrationRunnerResult,
20
- } from '@prisma-next/framework-components/control';
16
+ import type { MigrationRunnerResult } from '@prisma-next/framework-components/control';
21
17
  import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
18
+ import type { SqlControlDriverInstance, SqlStorage } from '@prisma-next/sql-contract/types';
19
+ import type { LoweredStatement } from '@prisma-next/sql-relational-core/ast';
22
20
  import { ifDefined } from '@prisma-next/utils/defined';
23
21
  import type { Result } from '@prisma-next/utils/result';
24
22
  import { notOk, ok, okVoid } from '@prisma-next/utils/result';
23
+ import { MARKER_TABLE_NAME } from '../control-tables';
25
24
  import { parseSqliteDefault } from '../default-normalizer';
26
25
  import { normalizeSqliteNativeType } from '../native-type-normalizer';
27
26
  import type { SqlitePlanTargetDetails } from './planner-target-details';
28
- import {
29
- buildLedgerInsertStatement,
30
- buildWriteMarkerStatements,
31
- ensureLedgerTableStatement,
32
- ensureMarkerTableStatement,
33
- MARKER_TABLE_NAME,
34
- readMarkerStatement,
35
- type SqlStatement,
36
- } from './statement-builders';
37
27
 
38
28
  export function createSqliteMigrationRunner(
39
29
  family: SqlControlFamilyInstance,
@@ -70,9 +60,9 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
70
60
  const policyCheck = this.enforcePolicyCompatibility(options.policy, options.plan.operations);
71
61
  if (!policyCheck.ok) return policyCheck;
72
62
 
73
- const ensureResult = await this.ensureControlTables(driver);
63
+ const ensureResult = await this.ensureControlTables(driver, options.destinationContract);
74
64
  if (!ensureResult.ok) return ensureResult;
75
- const existingMarker = await this.readMarker(driver, space);
65
+ const existingMarker = await this.family.readMarker({ driver, space });
76
66
 
77
67
  const markerCheck = this.ensureMarkerCompatibility(existingMarker, options.plan);
78
68
  if (!markerCheck.ok) return markerCheck;
@@ -126,8 +116,9 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
126
116
  const isSelfEdgeNoOp = isSelfEdge && operationsExecuted === 0 && incomingIsSubsetOfExisting;
127
117
 
128
118
  if (!isSelfEdgeNoOp) {
129
- await this.upsertMarker(driver, options, existingMarker, space);
130
- await this.recordLedgerEntry(driver, options, existingMarker, executedOperations);
119
+ const markerResult = await this.upsertMarker(driver, options, existingMarker, space);
120
+ if (!markerResult.ok) return markerResult;
121
+ await this.recordLedgerEntries(driver, options, executedOperations);
131
122
  }
132
123
 
133
124
  return runnerSuccess({
@@ -137,7 +128,7 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
137
128
  }
138
129
 
139
130
  async execute(options: {
140
- readonly driver: ControlDriverInstance<'sql', string>;
131
+ readonly driver: SqlControlDriverInstance<string>;
141
132
  readonly perSpaceOptions: ReadonlyArray<
142
133
  SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>
143
134
  >;
@@ -306,17 +297,16 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
306
297
 
307
298
  private async ensureControlTables(
308
299
  driver: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>['driver'],
300
+ contract: Contract<SqlStorage>,
309
301
  ): 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
302
  const legacyDetection = await this.detectLegacyMarkerShape(driver);
315
303
  if (!legacyDetection.ok) {
316
304
  return legacyDetection;
317
305
  }
318
- await this.executeStatement(driver, ensureMarkerTableStatement);
319
- await this.executeStatement(driver, ensureLedgerTableStatement);
306
+ const lowererContext = { contract };
307
+ for (const query of this.family.bootstrapControlTableQueries()) {
308
+ await this.executeStatement(driver, this.family.lowerAst(query, lowererContext));
309
+ }
320
310
  return okVoid();
321
311
  }
322
312
 
@@ -347,32 +337,6 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
347
337
  );
348
338
  }
349
339
 
350
- private async readMarker(
351
- driver: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>['driver'],
352
- space: string,
353
- ): Promise<ContractMarkerRecord | null> {
354
- const stmt = readMarkerStatement(space);
355
- try {
356
- const result = await driver.query<ContractMarkerRow>(stmt.sql, stmt.params);
357
- const row = result.rows[0];
358
- if (!row) return null;
359
- // SQLite stores arrays as JSON-encoded TEXT (no native array type), so
360
- // the driver returns `invariants` as a string. Decode before delegating
361
- // to the shared row schema, which expects `string[]`.
362
- const invariants =
363
- typeof row.invariants === 'string'
364
- ? (JSON.parse(row.invariants) as unknown)
365
- : row.invariants;
366
- return parseContractMarkerRow({ ...row, invariants });
367
- } catch (error) {
368
- // Table might not exist yet
369
- if (error instanceof Error && error.message.includes('no such table')) {
370
- return null;
371
- }
372
- throw error;
373
- }
374
- }
375
-
376
340
  private async runExpectationSteps(
377
341
  driver: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>['driver'],
378
342
  steps: readonly SqlMigrationPlanOperationStep[],
@@ -600,48 +564,79 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
600
564
  options: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>,
601
565
  existingMarker: ContractMarkerRecord | null,
602
566
  space: string,
603
- ): Promise<void> {
604
- // SQLite has no native array type, so we can't merge invariants in SQL
605
- // the way Postgres does. Merge client-side under the runner's
606
- // BEGIN EXCLUSIVE — sort + dedupe so the JSON-encoded value is stable.
607
- const merged = new Set<string>(existingMarker?.invariants ?? []);
608
- for (const inv of options.plan.providedInvariants) merged.add(inv);
609
- const invariants = Array.from(merged).sort();
610
- const writeStatements = buildWriteMarkerStatements({
611
- space,
567
+ ): Promise<Result<void, SqlMigrationRunnerFailure>> {
568
+ // Pass the plan's incoming invariants verbatim; `updateMarker` unions them
569
+ // with the stored set (TS-side, dialect-uniform) under the runner's
570
+ // BEGIN EXCLUSIVE — no client-side pre-merge here, so there is no
571
+ // double-merge with the SPI's internal accumulate-dedupe.
572
+ const destination = {
612
573
  storageHash: options.plan.destination.storageHash,
613
574
  profileHash:
614
575
  options.plan.destination.profileHash ??
615
576
  options.destinationContract.profileHash ??
616
577
  options.plan.destination.storageHash,
617
- contractJson: options.destinationContract,
618
- canonicalVersion: null,
619
- meta: {},
620
- invariants,
578
+ invariants: options.plan.providedInvariants ?? [],
579
+ };
580
+ if (!existingMarker) {
581
+ await this.family.initMarker({ driver, space, destination });
582
+ return okVoid();
583
+ }
584
+ const updated = await this.family.updateMarker({
585
+ driver,
586
+ space,
587
+ expectedFrom: existingMarker.storageHash,
588
+ destination,
621
589
  });
622
- const statement = existingMarker ? writeStatements.update : writeStatements.insert;
623
- await this.executeStatement(driver, statement);
590
+ if (!updated) {
591
+ return runnerFailure(
592
+ 'MARKER_CAS_FAILURE',
593
+ 'Marker was modified by another process during migration execution.',
594
+ {
595
+ meta: {
596
+ space,
597
+ expectedStorageHash: existingMarker.storageHash,
598
+ destinationStorageHash: options.plan.destination.storageHash,
599
+ },
600
+ },
601
+ );
602
+ }
603
+ return okVoid();
624
604
  }
625
605
 
626
- private async recordLedgerEntry(
606
+ private async recordLedgerEntries(
627
607
  driver: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>['driver'],
628
608
  options: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>,
629
- existingMarker: ContractMarkerRecord | null,
630
609
  executedOperations: readonly SqlMigrationPlanOperation<SqlitePlanTargetDetails>[],
631
610
  ): 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);
611
+ const plan = options.plan;
612
+ const space = plan.spaceId;
613
+ const edges = options.migrationEdges;
614
+ const totalEdgeOps = edges.reduce((sum, edge) => sum + edge.operationCount, 0);
615
+ if (totalEdgeOps !== plan.operations.length) {
616
+ throw new Error(
617
+ `Ledger write: plan.operations length (${plan.operations.length}) does not match sum of migrationEdges operationCount (${totalEdgeOps})`,
618
+ );
619
+ }
620
+ // The ledger records the operations as executed — idempotency-skipped ops
621
+ // are substituted with skip records (empty `execute`) by `applyPlan`, so the
622
+ // journal reflects what actually ran rather than the raw plan.
623
+ let offset = 0;
624
+ for (const edge of edges) {
625
+ const edgeOps = executedOperations.slice(offset, offset + edge.operationCount);
626
+ offset += edge.operationCount;
627
+ await this.family.writeLedgerEntry({
628
+ driver,
629
+ space,
630
+ entry: {
631
+ edgeId: `${edge.from}->${edge.to}`,
632
+ from: edge.from,
633
+ to: edge.to,
634
+ migrationName: edge.dirName,
635
+ migrationHash: edge.migrationHash,
636
+ operations: edgeOps,
637
+ },
638
+ });
639
+ }
645
640
  }
646
641
 
647
642
  private async beginExclusiveTransaction(
@@ -664,7 +659,7 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
664
659
 
665
660
  private async executeStatement(
666
661
  driver: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>['driver'],
667
- statement: SqlStatement,
662
+ statement: LoweredStatement,
668
663
  ): Promise<void> {
669
664
  if (statement.params.length > 0) {
670
665
  await driver.query(statement.sql, statement.params);
@@ -1,6 +1,6 @@
1
1
  import type { RuntimeTargetInstance } from '@prisma-next/framework-components/execution';
2
2
  import type { SqlRuntimeTargetDescriptor } from '@prisma-next/sql-runtime';
3
- import { sqliteTargetDescriptorMeta } from './descriptor-meta';
3
+ import { sqliteTargetDescriptorMetaRuntime } from './descriptor-meta-runtime';
4
4
 
5
5
  export interface SqliteRuntimeTargetInstance extends RuntimeTargetInstance<'sql', 'sqlite'> {}
6
6
 
@@ -8,7 +8,7 @@ const sqliteRuntimeTargetDescriptor: SqlRuntimeTargetDescriptor<
8
8
  'sqlite',
9
9
  SqliteRuntimeTargetInstance
10
10
  > = {
11
- ...sqliteTargetDescriptorMeta,
11
+ ...sqliteTargetDescriptorMetaRuntime,
12
12
  codecs: () => [],
13
13
  create(): SqliteRuntimeTargetInstance {
14
14
  return {
@@ -1,20 +1,32 @@
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 { type Namespace, NamespaceBase } from '@prisma-next/framework-components/ir';
4
+ import type { SqlNamespaceTablesInput, SqlStorage } from '@prisma-next/sql-contract/types';
5
+ import { blindCast } from '@prisma-next/utils/casts';
6
+ import { buildSqliteNamespace } from './sqlite-unbound-database';
4
7
 
5
8
  /**
6
9
  * 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.
10
+ * shape: inherits the full SQL-family deserialization pipeline and
11
+ * materialises namespace entries as SQLite database concretions that
12
+ * expose `qualifyTable()` for runtime SQL rendering.
15
13
  */
16
14
  export class SqliteContractSerializer extends SqlContractSerializerBase<Contract<SqlStorage>> {
17
15
  constructor() {
18
16
  super(new Map());
19
17
  }
18
+
19
+ protected override hydrateSqlNamespaceEntry(
20
+ nsId: string,
21
+ raw: Namespace | Record<string, unknown>,
22
+ ): Namespace | SqlNamespaceTablesInput {
23
+ if (raw instanceof NamespaceBase) {
24
+ return raw;
25
+ }
26
+ const hydrated = blindCast<
27
+ SqlNamespaceTablesInput,
28
+ 'super.hydrateSqlNamespaceEntry returns the tables form when raw is not a NamespaceBase'
29
+ >(super.hydrateSqlNamespaceEntry(nsId, raw));
30
+ return buildSqliteNamespace(hydrated);
31
+ }
20
32
  }
@@ -1,9 +1,81 @@
1
1
  import {
2
2
  freezeNode,
3
+ type Namespace,
3
4
  NamespaceBase,
4
5
  UNBOUND_NAMESPACE_ID,
5
6
  } from '@prisma-next/framework-components/ir';
6
- import type { StorageTable } from '@prisma-next/sql-contract/types';
7
+ import {
8
+ type SqlNamespaceTablesInput,
9
+ StorageTable,
10
+ type StorageTableInput,
11
+ } from '@prisma-next/sql-contract/types';
12
+ import { blindCast, castAs } from '@prisma-next/utils/casts';
13
+
14
+ export type SqliteDatabaseInput = {
15
+ readonly id: string;
16
+ readonly entries: {
17
+ readonly table: Readonly<Record<string, StorageTable | StorageTableInput>>;
18
+ };
19
+ };
20
+
21
+ const SQLITE_NAMESPACE_KIND = 'sqlite-namespace' as const;
22
+
23
+ function isMaterializedSqliteNamespace(
24
+ ns: Namespace | SqlNamespaceTablesInput,
25
+ ): ns is SqliteDatabase | SqliteUnboundDatabase {
26
+ if (typeof ns !== 'object' || ns === null) {
27
+ return false;
28
+ }
29
+ const proto = Object.getPrototypeOf(ns);
30
+ if (proto === Object.prototype || proto === null) {
31
+ return false;
32
+ }
33
+ return (ns as { kind?: unknown }).kind === SQLITE_NAMESPACE_KIND;
34
+ }
35
+
36
+ /**
37
+ * SQLite namespace concretion carrying table metadata under
38
+ * `entries.table` and unqualified `qualifyTable()` emission for runtime
39
+ * SQL rendering.
40
+ */
41
+ export class SqliteDatabase extends NamespaceBase {
42
+ declare readonly kind: string;
43
+
44
+ readonly id: string;
45
+ readonly entries: Readonly<{
46
+ readonly table: Readonly<Record<string, StorageTable>>;
47
+ }>;
48
+
49
+ constructor(input: SqliteDatabaseInput) {
50
+ super();
51
+ this.id = input.id;
52
+ this.entries = Object.freeze({
53
+ table: Object.freeze(
54
+ Object.fromEntries(
55
+ Object.entries(input.entries.table).map(([k, v]) => [
56
+ k,
57
+ v instanceof StorageTable ? v : new StorageTable(v as StorageTableInput),
58
+ ]),
59
+ ),
60
+ ),
61
+ });
62
+ Object.defineProperty(this, 'kind', {
63
+ value: SQLITE_NAMESPACE_KIND,
64
+ writable: false,
65
+ enumerable: false,
66
+ configurable: true,
67
+ });
68
+ freezeNode(this);
69
+ }
70
+
71
+ qualifier(): string {
72
+ return '';
73
+ }
74
+
75
+ qualifyTable(tableName: string): string {
76
+ return `"${tableName}"`;
77
+ }
78
+ }
7
79
 
8
80
  /**
9
81
  * SQLite target `Namespace` concretion. SQLite has no schema or
@@ -22,43 +94,58 @@ import type { StorageTable } from '@prisma-next/sql-contract/types';
22
94
  * `__unspecified__` AST bucket reaches the SQLite interpreter, which
23
95
  * lowers it to this singleton.
24
96
  */
25
- export class SqliteUnboundDatabase extends NamespaceBase {
97
+ export class SqliteUnboundDatabase extends SqliteDatabase {
26
98
  static readonly instance: SqliteUnboundDatabase = new SqliteUnboundDatabase();
27
99
 
28
- readonly kind = 'database' as const;
29
- readonly id = UNBOUND_NAMESPACE_ID;
30
- readonly tables: Readonly<Record<string, StorageTable>>;
31
-
32
100
  private constructor() {
33
- super();
34
- this.tables = Object.freeze({});
35
- freezeNode(this);
101
+ super({ id: UNBOUND_NAMESPACE_ID, entries: { table: {} } });
36
102
  }
103
+ }
37
104
 
38
- qualifier(): string {
39
- return '';
105
+ export function buildSqliteNamespace(
106
+ input: SqlNamespaceTablesInput,
107
+ ): SqliteDatabase | SqliteUnboundDatabase {
108
+ if (input.id !== UNBOUND_NAMESPACE_ID) {
109
+ throw new Error(
110
+ `buildSqliteNamespace: SQLite has no schema concept; the only valid namespace id is "${UNBOUND_NAMESPACE_ID}" (received "${input.id}").`,
111
+ );
40
112
  }
41
-
42
- qualifyTable(tableName: string): string {
43
- return `"${tableName}"`;
113
+ if (Object.keys(input.entries.table).length === 0) {
114
+ return castAs<SqliteUnboundDatabase>(SqliteUnboundDatabase.instance);
44
115
  }
116
+ return new SqliteDatabase({ id: input.id, entries: input.entries });
117
+ }
118
+
119
+ export function buildSqliteNamespaceMap(
120
+ namespaces: Readonly<Record<string, Namespace | SqlNamespaceTablesInput>>,
121
+ ): Readonly<Record<string, SqliteDatabase | SqliteUnboundDatabase>> {
122
+ return Object.fromEntries(
123
+ Object.entries(namespaces).map(([nsKey, ns]) => [
124
+ nsKey,
125
+ isMaterializedSqliteNamespace(ns)
126
+ ? ns
127
+ : buildSqliteNamespace(
128
+ blindCast<
129
+ SqlNamespaceTablesInput,
130
+ 'non-materialized SQLite namespace map entry is a SqlNamespaceTablesInput'
131
+ >(ns),
132
+ ),
133
+ ]),
134
+ );
45
135
  }
46
136
 
47
137
  /**
48
138
  * Target-supplied `Namespace` factory the SQLite target plumbs through
49
139
  * `defineContract({ createNamespace })`. SQLite has only one
50
140
  * effective namespace slot — the framework `UNBOUND_NAMESPACE_ID`
51
- * sentinel — so the factory always returns the singleton and rejects
52
- * any other coordinate. The SQL family's defensive validation in
53
- * `defineContract` already rejects user-declared SQLite namespaces, so
54
- * this throw is a structural safety net rather than a user-facing
55
- * surface.
141
+ * sentinel — so the factory always returns the singleton or a fresh
142
+ * `SqliteDatabase` for the unbound slot with tables. The SQL family's
143
+ * defensive validation in `defineContract` already rejects
144
+ * user-declared SQLite namespaces, so this throw is a structural
145
+ * safety net rather than a user-facing surface.
56
146
  */
57
- export function sqliteCreateNamespace(id: string): SqliteUnboundDatabase {
58
- if (id === UNBOUND_NAMESPACE_ID) {
59
- return SqliteUnboundDatabase.instance;
60
- }
61
- throw new Error(
62
- `sqliteCreateNamespace: SQLite has no schema concept; the only valid namespace id is "${UNBOUND_NAMESPACE_ID}" (received "${id}").`,
63
- );
147
+ export function sqliteCreateNamespace(
148
+ input: SqlNamespaceTablesInput,
149
+ ): SqliteDatabase | SqliteUnboundDatabase {
150
+ return buildSqliteNamespace(input);
64
151
  }
@@ -0,0 +1,6 @@
1
+ export { datetime, integer, jsonText, sqliteTable, text } from '../contract-free/columns';
2
+ export {
3
+ buildControlTableBootstrapQueries,
4
+ buildSignMarkerBootstrapQueries,
5
+ } from '../contract-free/control-bootstrap';
6
+ export { createTable } from '../contract-free/ddl';
@@ -0,0 +1,5 @@
1
+ export {
2
+ CONTROL_TABLE_NAMES,
3
+ LEDGER_TABLE_NAME,
4
+ MARKER_TABLE_NAME,
5
+ } from '../core/control-tables';