@prisma-next/target-sqlite 0.14.0-dev.5 → 0.14.0-dev.50

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/contract-free.mjs +1 -1
  2. package/dist/control.d.mts +3 -4
  3. package/dist/control.d.mts.map +1 -1
  4. package/dist/control.mjs +16 -27
  5. package/dist/control.mjs.map +1 -1
  6. package/dist/{descriptor-meta-DxmEeTJ-.mjs → descriptor-meta-D1fap-5t.mjs} +4 -11
  7. package/dist/descriptor-meta-D1fap-5t.mjs.map +1 -0
  8. package/dist/descriptor-meta-runtime-BkXK3OjD.mjs +12 -0
  9. package/dist/descriptor-meta-runtime-BkXK3OjD.mjs.map +1 -0
  10. package/dist/migration.d.mts +2 -2
  11. package/dist/migration.mjs +2 -2
  12. package/dist/native-type-normalizer.mjs +13 -1
  13. package/dist/native-type-normalizer.mjs.map +1 -0
  14. package/dist/{op-factory-call-DmdfD1yd.mjs → op-factory-call-D_eYsHTp.mjs} +134 -116
  15. package/dist/op-factory-call-D_eYsHTp.mjs.map +1 -0
  16. package/dist/op-factory-call.d.mts +2 -2
  17. package/dist/op-factory-call.d.mts.map +1 -1
  18. package/dist/op-factory-call.mjs +1 -1
  19. package/dist/pack.mjs +1 -1
  20. package/dist/planner-DhvFrgPT.mjs +854 -0
  21. package/dist/planner-DhvFrgPT.mjs.map +1 -0
  22. package/dist/{planner-produced-sqlite-migration-CpgsY-M9.d.mts → planner-produced-sqlite-migration-B9UZVDPa.d.mts} +3 -3
  23. package/dist/{planner-produced-sqlite-migration-CpgsY-M9.d.mts.map → planner-produced-sqlite-migration-B9UZVDPa.d.mts.map} +1 -1
  24. package/dist/{planner-produced-sqlite-migration-0xPEm3R1.mjs → planner-produced-sqlite-migration-DyRmcteq.mjs} +46 -17
  25. package/dist/planner-produced-sqlite-migration-DyRmcteq.mjs.map +1 -0
  26. package/dist/planner-produced-sqlite-migration.d.mts +1 -1
  27. package/dist/planner-produced-sqlite-migration.mjs +1 -1
  28. package/dist/planner.d.mts +47 -11
  29. package/dist/planner.d.mts.map +1 -1
  30. package/dist/planner.mjs +1 -1
  31. package/dist/runtime.d.mts +6 -4
  32. package/dist/runtime.d.mts.map +1 -1
  33. package/dist/runtime.mjs +3 -3
  34. package/dist/{shared-Dhc8mLK1.d.mts → shared-C6sM-wel.d.mts} +2 -2
  35. package/dist/{shared-Dhc8mLK1.d.mts.map → shared-C6sM-wel.d.mts.map} +1 -1
  36. package/dist/{sqlite-contract-serializer--iaDgC8e.mjs → sqlite-contract-view-BEwi0mUC.mjs} +34 -9
  37. package/dist/sqlite-contract-view-BEwi0mUC.mjs.map +1 -0
  38. package/dist/sqlite-contract-view-J32K82Gi.d.mts +29 -0
  39. package/dist/sqlite-contract-view-J32K82Gi.d.mts.map +1 -0
  40. package/dist/{sqlite-migration-A0rwqPOG.mjs → sqlite-migration-D93wCdSD.mjs} +29 -2
  41. package/dist/sqlite-migration-D93wCdSD.mjs.map +1 -0
  42. package/dist/{sqlite-migration-DVfhQwN_.d.mts → sqlite-migration-_BhTcnIe.d.mts} +27 -4
  43. package/dist/sqlite-migration-_BhTcnIe.d.mts.map +1 -0
  44. package/package.json +18 -18
  45. package/src/core/control-target.ts +13 -18
  46. package/src/core/migrations/column-ddl-rendering.ts +177 -0
  47. package/src/core/migrations/diff-database-schema.ts +240 -0
  48. package/src/core/migrations/issue-planner.ts +359 -488
  49. package/src/core/migrations/op-factory-call.ts +5 -0
  50. package/src/core/migrations/operations/tables.ts +107 -67
  51. package/src/core/migrations/planner-strategies.ts +118 -137
  52. package/src/core/migrations/planner.ts +117 -30
  53. package/src/core/migrations/render-typescript.ts +42 -15
  54. package/src/core/migrations/runner.ts +4 -10
  55. package/src/core/migrations/sqlite-migration.ts +42 -1
  56. package/src/core/sqlite-contract-serializer.ts +29 -14
  57. package/src/core/sqlite-contract-view.ts +41 -0
  58. package/src/core/sqlite-schema-verifier.ts +6 -3
  59. package/src/core/sqlite-unbound-database.ts +8 -8
  60. package/src/exports/runtime.ts +1 -0
  61. package/dist/descriptor-meta-DxmEeTJ-.mjs.map +0 -1
  62. package/dist/native-type-normalizer-CiSyVmMP.mjs +0 -14
  63. package/dist/native-type-normalizer-CiSyVmMP.mjs.map +0 -1
  64. package/dist/op-factory-call-DmdfD1yd.mjs.map +0 -1
  65. package/dist/planner-Ciq8p_dL.mjs +0 -622
  66. package/dist/planner-Ciq8p_dL.mjs.map +0 -1
  67. package/dist/planner-produced-sqlite-migration-0xPEm3R1.mjs.map +0 -1
  68. package/dist/sqlite-contract-serializer--iaDgC8e.mjs.map +0 -1
  69. package/dist/sqlite-migration-A0rwqPOG.mjs.map +0 -1
  70. package/dist/sqlite-migration-DVfhQwN_.d.mts.map +0 -1
@@ -11,16 +11,17 @@ import {
11
11
  plannerFailure,
12
12
  } from '@prisma-next/family-sql/control';
13
13
  import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
14
- import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
15
14
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
16
15
  import type {
17
16
  MigrationPlanner,
18
17
  MigrationScaffoldContext,
19
- SchemaIssue,
18
+ SchemaDiffIssue,
19
+ SchemaOwnership,
20
20
  } from '@prisma-next/framework-components/control';
21
- import { parseSqliteDefault } from '../default-normalizer';
22
- import { normalizeSqliteNativeType } from '../native-type-normalizer';
23
- import { planIssues } from './issue-planner';
21
+ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
22
+ import { RelationalSchemaNodeKind, type SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
23
+ import { buildSqlitePlanDiff } from './diff-database-schema';
24
+ import { coalesceSubtreeIssues, issueNode, planIssues } from './issue-planner';
24
25
  import {
25
26
  type SqliteMigrationDestinationInfo,
26
27
  TypeScriptRenderableSqliteMigration,
@@ -41,16 +42,18 @@ export type SqlitePlanResult =
41
42
  /**
42
43
  * SQLite migration planner — a thin wrapper over `planIssues`.
43
44
  *
44
- * `plan()` verifies the live schema against the target contract (producing
45
- * `SchemaIssue[]`) and delegates to `planIssues` with the registered
46
- * strategies. Strategies absorb groups of related issues into composite
47
- * recipes (e.g. recreating a table to apply type/nullability/default/
48
- * constraint changes at once); anything not absorbed by a strategy flows
49
- * through `mapIssueToCall` in the issue planner as a one-off call.
45
+ * `plan()` diffs the live schema against the target contract via the one
46
+ * differ (`buildSqlitePlanDiff`, producing node-typed `SchemaDiffIssue[]`)
47
+ * and delegates to `planIssues` with the registered strategies. Strategies
48
+ * absorb groups of related issues into composite recipes (e.g. recreating a
49
+ * table to apply type/nullability/default/constraint changes at once);
50
+ * anything not absorbed by a strategy flows through `mapNodeIssueToCall` in
51
+ * the issue planner as a one-off call.
50
52
  *
51
- * FK-backing indexes are surfaced by `verifySqlSchema`'s index expansion
52
- * (see `verify-sql-schema.ts:459-469`), so `mapIssueToCall` handles them
53
- * uniformly alongside user-declared indexes.
53
+ * FK-backing indexes are already merged into the expected table node's
54
+ * `indexes` at derivation (`contractToSchemaIR`'s `convertTable`), so
55
+ * `mapNodeIssueToCall` handles them uniformly alongside user-declared
56
+ * indexes — no separate expansion step in the planner.
54
57
  */
55
58
  export class SqliteMigrationPlanner
56
59
  implements SqlMigrationPlanner<SqlitePlanTargetDetails>, MigrationPlanner<'sql', 'sqlite'>
@@ -83,6 +86,11 @@ export class SqliteMigrationPlanner
83
86
  * the marker row by the right space.
84
87
  */
85
88
  readonly spaceId: string;
89
+ /**
90
+ * Ownership oracle over the contract-space composition — see
91
+ * {@link SqlMigrationPlannerPlanOptions.ownership}.
92
+ */
93
+ readonly ownership?: SchemaOwnership;
86
94
  }): SqlitePlanResult {
87
95
  return this.planSql(options as SqlMigrationPlannerPlanOptions);
88
96
  }
@@ -107,17 +115,13 @@ export class SqliteMigrationPlanner
107
115
  const policyResult = this.ensureAdditivePolicy(options.policy);
108
116
  if (policyResult) return policyResult;
109
117
 
110
- const schemaIssues = this.collectSchemaIssues(options);
118
+ const { expected, actual, issues } = this.collectSchemaIssues(options);
111
119
  const codecHooks = extractCodecControlHooks(options.frameworkComponents);
112
- const storageTypes = options.contract.storage.types ?? {};
113
120
 
114
121
  const result = planIssues({
115
- issues: schemaIssues,
116
- toContract: options.contract,
117
- fromContract: options.fromContract,
118
- codecHooks,
119
- storageTypes,
120
- schema: options.schema,
122
+ issues,
123
+ expected,
124
+ actual,
121
125
  policy: options.policy,
122
126
  frameworkComponents: options.frameworkComponents,
123
127
  strategies: sqlitePlannerStrategies,
@@ -178,18 +182,101 @@ export class SqliteMigrationPlanner
178
182
  return null;
179
183
  }
180
184
 
181
- private collectSchemaIssues(options: SqlMigrationPlannerPlanOptions): readonly SchemaIssue[] {
185
+ /**
186
+ * Diffs the target contract against the live schema via the one differ
187
+ * (the same tree-building `diffSqliteSchema` uses, plus the
188
+ * op-render stamper) and prepares the issue list `planIssues` consumes.
189
+ *
190
+ * Three passes, in order:
191
+ * 1. Subtree coalescing — the differ is total (a missing/extra table also
192
+ * emits an issue for every column/constraint under it); those nested
193
+ * issues are redundant once the table-level `CreateTable`/`DropTable`
194
+ * call already accounts for the whole subtree. Runs FIRST, over the
195
+ * complete diff: a sibling-owned extra table's column issues must
196
+ * collapse into its one table-level issue before the ownership pass,
197
+ * because a bare column node carries no table reference — if coalescing
198
+ * ran after ownership dropped the table-level issue, the orphaned column
199
+ * issues would survive and the planner would emit drops against a
200
+ * sibling space's table.
201
+ * 2. Ownership — a live extra is only this plan's to drop when no contract
202
+ * space owns it. The differ ran against THIS space's contract, so a
203
+ * table a sibling owns surfaces here as `not-expected`; the planner asks
204
+ * the ownership oracle (the passive aggregate) whether any space declares
205
+ * it and, if so, leaves it alone. A table no space owns stays a genuine
206
+ * extra. Ownership lives in the aggregate; the planner only asks. No
207
+ * oracle (single-space) keeps every extra.
208
+ * 3. Strict-mode extras gating — `not-expected` (extra table/column/
209
+ * constraint) issues are dropped entirely outside strict mode, mirroring
210
+ * the retired coordinate walk's `if (strict) { ...extra_* } }` guards:
211
+ * an additive-only plan must never even consider dropping an unclaimed
212
+ * object, not just refuse to emit the drop.
213
+ */
214
+ private collectSchemaIssues(options: SqlMigrationPlannerPlanOptions): {
215
+ readonly expected: SqlSchemaIR;
216
+ readonly actual: SqlSchemaIR;
217
+ readonly issues: readonly SchemaDiffIssue[];
218
+ } {
182
219
  const allowed = options.policy.allowedOperationClasses;
183
220
  const strict = allowed.includes('widening') || allowed.includes('destructive');
184
- const verifyResult = verifySqlSchema({
221
+ const {
222
+ expected,
223
+ actual,
224
+ issues: rawIssues,
225
+ } = buildSqlitePlanDiff({
185
226
  contract: options.contract,
186
- schema: options.schema,
187
- strict,
188
- typeMetadataRegistry: new Map(),
227
+ actualSchema: options.schema,
189
228
  frameworkComponents: options.frameworkComponents,
190
- normalizeDefault: parseSqliteDefault,
191
- normalizeNativeType: normalizeSqliteNativeType,
192
229
  });
193
- return verifyResult.schema.issues;
230
+ const coalesced = coalesceSubtreeIssues(rawIssues);
231
+ const owned = retainUnownedExtras(coalesced, options.ownership);
232
+ const issues = strict ? owned : owned.filter((issue) => issue.reason !== 'not-expected');
233
+ return { expected, actual, issues };
194
234
  }
195
235
  }
236
+
237
+ /**
238
+ * Drops a `not-expected` issue when it is a whole extra TABLE that some
239
+ * contract space owns, asking the ownership oracle per node.
240
+ *
241
+ * The consultation applies ONLY to table-level extras — a live table this
242
+ * space's contract lacks — identified by asking the issue's own node
243
+ * (`nodeKind === table`), never by counting path segments. SQLite is a
244
+ * single-namespace target, so every coordinate is qualified with the shared
245
+ * `UNBOUND_NAMESPACE_ID` sentinel (the same one the aggregate's declared
246
+ * entities carry for this target). `entityKind` is the literal `'table'`:
247
+ * this function only ever asks about a node already confirmed to be
248
+ * `RelationalSchemaNodeKind.table` (checked just above), and that's a
249
+ * diff-tree `nodeKind` spelling (`'sql-table'`) distinct from the storage
250
+ * `entries` vocabulary `elementCoordinates` walks (`'table'`) — the literal
251
+ * is that storage-entries spelling. `declaresEntity` answers over the whole
252
+ * composition (self included), so a positive answer on such a table means
253
+ * another space owns it (a table this space owned would be in its expected
254
+ * tree, never an extra): leave it. A negative answer means no space owns
255
+ * it — a genuine orphan to drop.
256
+ *
257
+ * A DEEPER extra (an extra column/constraint on a table this space DOES own —
258
+ * only the child drifted, so the table is in the expected tree) is this
259
+ * space's own drift and is always kept for dropping; asking the oracle there
260
+ * would wrongly suppress it, because the owned table answers `true`. No oracle
261
+ * ⇒ every extra is kept (single-space plan).
262
+ */
263
+ function retainUnownedExtras(
264
+ issues: readonly SchemaDiffIssue[],
265
+ ownership: SchemaOwnership | undefined,
266
+ ): readonly SchemaDiffIssue[] {
267
+ if (ownership === undefined) return issues;
268
+ return issues.filter((issue) => {
269
+ if (issue.reason !== 'not-expected') return true;
270
+ const node = issueNode(issue);
271
+ if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.table) return true;
272
+ const tableName = issue.path[1];
273
+ return (
274
+ tableName === undefined ||
275
+ !ownership.declaresEntity({
276
+ namespaceId: UNBOUND_NAMESPACE_ID,
277
+ entityKind: 'table',
278
+ entityName: tableName,
279
+ })
280
+ );
281
+ });
282
+ }
@@ -35,15 +35,19 @@ export function renderCallsToTypeScript(
35
35
  calls: ReadonlyArray<OpFactoryCall>,
36
36
  meta: RenderMigrationMeta,
37
37
  ): string {
38
- const imports = buildImports(calls);
38
+ const imports = buildImports(calls, meta);
39
39
  const operationsBody = calls.map((c) => c.renderTypeScript()).join(',\n');
40
+ const hasStart = meta.from !== null;
41
+ const startField = hasStart ? [' override readonly startContractJson = startContract;'] : [];
40
42
 
41
43
  return [
42
44
  shebangLineFor(detectScaffoldRuntime()),
43
45
  imports,
44
46
  '',
45
- 'export default class M extends Migration {',
46
- buildDescribeMethod(meta),
47
+ `export default class M extends Migration<${hasStart ? 'Start' : 'never'}, End> {`,
48
+ ...startField,
49
+ ' override readonly endContractJson = endContract;',
50
+ '',
47
51
  ' override get operations() {',
48
52
  ' return [',
49
53
  indent(operationsBody, 6),
@@ -56,8 +60,8 @@ export function renderCallsToTypeScript(
56
60
  ].join('\n');
57
61
  }
58
62
 
59
- function buildImports(calls: ReadonlyArray<OpFactoryCall>): string {
60
- const requirements: ImportRequirement[] = [...BASE_IMPORTS];
63
+ function buildImports(calls: ReadonlyArray<OpFactoryCall>, meta: RenderMigrationMeta): string {
64
+ const requirements: ImportRequirement[] = [...BASE_IMPORTS, ...contractImports(meta)];
61
65
  for (const call of calls) {
62
66
  for (const req of call.importRequirements()) {
63
67
  requirements.push(req);
@@ -66,16 +70,39 @@ function buildImports(calls: ReadonlyArray<OpFactoryCall>): string {
66
70
  return renderImports(requirements);
67
71
  }
68
72
 
69
- function buildDescribeMethod(meta: RenderMigrationMeta): string {
70
- const lines: string[] = [];
71
- lines.push(' override describe() {');
72
- lines.push(' return {');
73
- lines.push(` from: ${JSON.stringify(meta.from)},`);
74
- lines.push(` to: ${JSON.stringify(meta.to)},`);
75
- lines.push(' };');
76
- lines.push(' }');
77
- lines.push('');
78
- return lines.join('\n');
73
+ /**
74
+ * The committed contract-JSON imports the scaffold reads its from/to identity
75
+ * from. `end-contract.json` is always present; `start-contract.json` is added
76
+ * only for a non-baseline migration (`meta.from !== null`). The matching
77
+ * `Contract` type imports (aliased `Start`/`End`) feed the
78
+ * `Migration<Start, End>` generics. Baseline emits `Migration<never, End>` with
79
+ * no start imports — `never` is the honest "no prior contract" Start.
80
+ */
81
+ function contractImports(meta: RenderMigrationMeta): readonly ImportRequirement[] {
82
+ const reqs: ImportRequirement[] = [
83
+ {
84
+ moduleSpecifier: './end-contract.json',
85
+ symbol: 'endContract',
86
+ kind: 'default',
87
+ attributes: { type: 'json' },
88
+ },
89
+ { moduleSpecifier: './end-contract', symbol: 'Contract', alias: 'End', typeOnly: true },
90
+ ];
91
+ if (meta.from !== null) {
92
+ reqs.push({
93
+ moduleSpecifier: './start-contract.json',
94
+ symbol: 'startContract',
95
+ kind: 'default',
96
+ attributes: { type: 'json' },
97
+ });
98
+ reqs.push({
99
+ moduleSpecifier: './start-contract',
100
+ symbol: 'Contract',
101
+ alias: 'Start',
102
+ typeOnly: true,
103
+ });
104
+ }
105
+ return reqs;
79
106
  }
80
107
 
81
108
  function indent(text: string, spaces: number): string {
@@ -12,7 +12,6 @@ import type {
12
12
  SqlMigrationRunnerSuccessValue,
13
13
  } from '@prisma-next/family-sql/control';
14
14
  import { runnerFailure, runnerSuccess } from '@prisma-next/family-sql/control';
15
- import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
16
15
  import type { MigrationRunnerResult } from '@prisma-next/framework-components/control';
17
16
  import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
18
17
  import type { SqlControlDriverInstance, SqlStorage } from '@prisma-next/sql-contract/types';
@@ -22,8 +21,7 @@ import { ifDefined } from '@prisma-next/utils/defined';
22
21
  import type { Result } from '@prisma-next/utils/result';
23
22
  import { notOk, ok, okVoid } from '@prisma-next/utils/result';
24
23
  import { MARKER_TABLE_NAME } from '../control-tables';
25
- import { parseSqliteDefault } from '../default-normalizer';
26
- import { normalizeSqliteNativeType } from '../native-type-normalizer';
24
+ import { verifySqliteDatabaseSchema } from './diff-database-schema';
27
25
  import type { SqlitePlanTargetDetails } from './planner-target-details';
28
26
 
29
27
  export function createSqliteMigrationRunner(
@@ -92,20 +90,16 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
92
90
  }
93
91
 
94
92
  if (space === APP_SPACE_ID) {
95
- const schemaIR = await this.family.introspect({
93
+ const schemaNode = await this.family.introspect({
96
94
  driver,
97
95
  contract: options.destinationContract,
98
96
  });
99
-
100
- const schemaVerifyResult = verifySqlSchema({
97
+ const schemaVerifyResult = verifySqliteDatabaseSchema({
101
98
  contract: options.destinationContract,
102
- schema: schemaIR,
99
+ actualSchema: schemaNode,
103
100
  strict: options.strictVerification ?? true,
104
- context: options.context ?? {},
105
101
  typeMetadataRegistry: this.family.typeMetadataRegistry,
106
102
  frameworkComponents: options.frameworkComponents,
107
- normalizeDefault: parseSqliteDefault,
108
- normalizeNativeType: normalizeSqliteNativeType,
109
103
  });
110
104
  if (!schemaVerifyResult.ok) {
111
105
  return runnerFailure('SCHEMA_VERIFY_FAILED', schemaVerifyResult.summary, {
@@ -1,3 +1,4 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
1
2
  import type {
2
3
  MigrationOperationClass,
3
4
  SqlMigrationPlanOperation,
@@ -5,9 +6,12 @@ import type {
5
6
  import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
6
7
  import { Migration as SqlMigration } from '@prisma-next/family-sql/migration';
7
8
  import type { ControlStack } from '@prisma-next/framework-components/control';
9
+ import { MigrationContractViews } from '@prisma-next/migration-tools/migration';
10
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
8
11
  import type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
9
12
  import { blindCast } from '@prisma-next/utils/casts';
10
13
  import { errorSqliteMigrationStackMissing } from '../errors';
14
+ import { SqliteContractView } from '../sqlite-contract-view';
11
15
  import {
12
16
  AddColumnCall,
13
17
  CreateIndexCall,
@@ -34,8 +38,19 @@ type Op = SqlMigrationPlanOperation<SqlitePlanTargetDetails>;
34
38
  * forward to the corresponding `*Call` with that stored adapter, so user
35
39
  * migrations can write `this.createTable({...})` without threading the adapter
36
40
  * through every call.
41
+ *
42
+ * Binds the framework base's `Start` / `End` contract generics so a subclass
43
+ * that assigns its `start-contract.json` / `end-contract.json` imports gets
44
+ * fully-typed view accessors: `this.endContract` is a `SqliteContractView<End>`
45
+ * (sole namespace unwrapped to the root — `this.endContract.table.<name>`),
46
+ * built lazily from the JSON fields via the shared `MigrationContractViews`
47
+ * helper. Mirrors `MongoMigration`'s view getters; the framework base derives
48
+ * `describe()` from the same JSON.
37
49
  */
38
- export abstract class SqliteMigration extends SqlMigration<SqlitePlanTargetDetails, 'sqlite'> {
50
+ export abstract class SqliteMigration<
51
+ Start extends Contract<SqlStorage> = Contract<SqlStorage>,
52
+ End extends Contract<SqlStorage> = Contract<SqlStorage>,
53
+ > extends SqlMigration<SqlitePlanTargetDetails, 'sqlite', Start, End> {
39
54
  readonly targetId = 'sqlite' as const;
40
55
 
41
56
  /**
@@ -46,6 +61,15 @@ export abstract class SqliteMigration extends SqlMigration<SqlitePlanTargetDetai
46
61
  */
47
62
  protected readonly controlAdapter: SqlControlAdapter<'sqlite'> | undefined;
48
63
 
64
+ #endView = new MigrationContractViews<SqliteContractView<End>>(this, 'SqliteMigration', (json) =>
65
+ SqliteContractView.fromJson<End>(json),
66
+ );
67
+ #startView = new MigrationContractViews<SqliteContractView<Start>>(
68
+ this,
69
+ 'SqliteMigration',
70
+ (json) => SqliteContractView.fromJson<Start>(json),
71
+ );
72
+
49
73
  constructor(stack?: ControlStack<'sql', 'sqlite'>) {
50
74
  super(stack);
51
75
  this.controlAdapter = stack?.adapter
@@ -56,6 +80,23 @@ export abstract class SqliteMigration extends SqlMigration<SqlitePlanTargetDetai
56
80
  : undefined;
57
81
  }
58
82
 
83
+ /**
84
+ * The typed SQLite view over this migration's end-state contract — sole
85
+ * namespace unwrapped to the root, so `this.endContract.table.<name>` etc.
86
+ * Throws if no `endContractJson` was provided.
87
+ */
88
+ get endContract(): SqliteContractView<End> {
89
+ return this.#endView.endContract;
90
+ }
91
+
92
+ /**
93
+ * The typed SQLite view over this migration's start-state contract, or
94
+ * `null` for a baseline migration (no `startContractJson`).
95
+ */
96
+ get startContract(): SqliteContractView<Start> | null {
97
+ return this.#startView.startContract;
98
+ }
99
+
59
100
  protected createTable(options: {
60
101
  readonly table: string;
61
102
  readonly ifNotExists?: boolean;
@@ -1,9 +1,9 @@
1
1
  import type { Contract } from '@prisma-next/contract/types';
2
2
  import { SqlContractSerializerBase } from '@prisma-next/family-sql/ir';
3
- import { type Namespace, NamespaceBase } from '@prisma-next/framework-components/ir';
4
- import type { SqlNamespaceTablesInput, SqlStorage } from '@prisma-next/sql-contract/types';
3
+ import type { Namespace } from '@prisma-next/framework-components/ir';
4
+ import type { SqlNamespaceInput, SqlStorage } from '@prisma-next/sql-contract/types';
5
5
  import { blindCast } from '@prisma-next/utils/casts';
6
- import { sqliteTargetDescriptorMeta } from './descriptor-meta';
6
+ import type { JsonObject } from '@prisma-next/utils/json';
7
7
  import { buildSqliteNamespace } from './sqlite-unbound-database';
8
8
 
9
9
  /**
@@ -17,21 +17,36 @@ export class SqliteContractSerializer extends SqlContractSerializerBase<Contract
17
17
  super(new Map());
18
18
  }
19
19
 
20
- protected override get defaultNamespaceId(): string {
21
- return sqliteTargetDescriptorMeta.defaultNamespaceId;
22
- }
23
-
24
20
  protected override hydrateSqlNamespaceEntry(
25
21
  nsId: string,
26
- raw: Namespace | Record<string, unknown>,
27
- ): Namespace | SqlNamespaceTablesInput {
28
- if (raw instanceof NamespaceBase) {
29
- return raw;
30
- }
22
+ raw: Record<string, unknown>,
23
+ ): Namespace | SqlNamespaceInput {
31
24
  const hydrated = blindCast<
32
- SqlNamespaceTablesInput,
33
- 'super.hydrateSqlNamespaceEntry returns the tables form when raw is not a NamespaceBase'
25
+ SqlNamespaceInput,
26
+ 'raw is always plain JSON, so super.hydrateSqlNamespaceEntry returns SqlNamespaceInput'
34
27
  >(super.hydrateSqlNamespaceEntry(nsId, raw));
35
28
  return buildSqliteNamespace(hydrated);
36
29
  }
30
+
31
+ override serializeContract(contract: Contract<SqlStorage>): JsonObject {
32
+ const { storage, ...rest } = contract;
33
+ const namespacesJson: Record<string, JsonObject> = {};
34
+ for (const [nsId, ns] of Object.entries(storage.namespaces)) {
35
+ namespacesJson[nsId] = {
36
+ id: ns.id,
37
+ entries: this.serializeNamespaceEntries(ns.entries),
38
+ };
39
+ }
40
+ return blindCast<
41
+ JsonObject,
42
+ 'rest + storage are serialized plain values; spread preserves JSON-clean contract envelope'
43
+ >({
44
+ ...rest,
45
+ storage: {
46
+ storageHash: String(storage.storageHash),
47
+ namespaces: namespacesJson,
48
+ ...(storage.types !== undefined ? { types: this.serializeJsonObject(storage.types) } : {}),
49
+ },
50
+ });
51
+ }
37
52
  }
@@ -0,0 +1,41 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
2
+ import {
3
+ buildSqlSingleNamespaceView,
4
+ type SqlSingleNamespaceView,
5
+ } from '@prisma-next/sql-contract/contract-view';
6
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
7
+ import { SqliteContractSerializer } from './sqlite-contract-serializer';
8
+
9
+ /**
10
+ * A SQLite contract view: the deserialized contract intersected with by-name
11
+ * accessors. It is substitutable for `Contract` (carries `storage`, `domain`,
12
+ * …) and also exposes the single default namespace unwrapped with the SQL
13
+ * built-in kinds promoted:
14
+ *
15
+ * ```ts
16
+ * const view = SqliteContractView.fromJson<Contract>(contractJson);
17
+ * view.table.users // typed table leaf
18
+ * view.entries.policy.X // pack-contributed kind (singular key)
19
+ * view.storage // the full contract is still present
20
+ * ```
21
+ *
22
+ * SQLite has `sql.enums: false`, so it never emits `valueSet` entries; the
23
+ * `valueSet` slot is therefore an empty map.
24
+ */
25
+ export type SqliteContractView<TContract extends Contract<SqlStorage> = Contract<SqlStorage>> =
26
+ SqlSingleNamespaceView<TContract>;
27
+
28
+ export const SqliteContractView = {
29
+ /** Wrap an already-deserialized SQLite contract in a view. */
30
+ from<TContract extends Contract<SqlStorage>>(contract: TContract): SqliteContractView<TContract> {
31
+ return buildSqlSingleNamespaceView(contract);
32
+ },
33
+
34
+ /** Deserialize a SQLite contract JSON envelope and wrap it in a view. */
35
+ fromJson<TContract extends Contract<SqlStorage> = Contract<SqlStorage>>(
36
+ json: unknown,
37
+ ): SqliteContractView<TContract> {
38
+ const contract = new SqliteContractSerializer().deserializeContract<TContract>(json);
39
+ return buildSqlSingleNamespaceView(contract);
40
+ },
41
+ };
@@ -1,6 +1,9 @@
1
1
  import type { Contract } from '@prisma-next/contract/types';
2
2
  import { SqlSchemaVerifierBase } from '@prisma-next/family-sql/ir';
3
- import type { SchemaIssue, SchemaVerifyOptions } from '@prisma-next/framework-components/control';
3
+ import type {
4
+ SchemaDiffIssue,
5
+ SchemaVerifyOptions,
6
+ } from '@prisma-next/framework-components/control';
4
7
  import type { SqlStorage } from '@prisma-next/sql-contract/types';
5
8
  import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
6
9
 
@@ -12,13 +15,13 @@ import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
12
15
  export class SqliteSchemaVerifier extends SqlSchemaVerifierBase<Contract<SqlStorage>, SqlSchemaIR> {
13
16
  protected verifyCommonSqlSchema(
14
17
  _options: SchemaVerifyOptions<Contract<SqlStorage>, SqlSchemaIR>,
15
- ): readonly SchemaIssue[] {
18
+ ): readonly SchemaDiffIssue[] {
16
19
  return [];
17
20
  }
18
21
 
19
22
  protected verifyTargetExtensions(
20
23
  _options: SchemaVerifyOptions<Contract<SqlStorage>, SqlSchemaIR>,
21
- ): readonly SchemaIssue[] {
24
+ ): readonly SchemaDiffIssue[] {
22
25
  return [];
23
26
  }
24
27
  }
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  freezeNode,
3
3
  hydrateNamespaceEntities,
4
- NamespaceBase,
5
4
  UNBOUND_NAMESPACE_ID,
6
5
  } from '@prisma-next/framework-components/ir';
7
6
  import { tableEntityKind } from '@prisma-next/sql-contract/entity-kinds';
8
- import type {
9
- SqlNamespaceEntries,
10
- SqlNamespaceTablesInput,
11
- StorageTable,
7
+ import {
8
+ SqlNamespaceBase,
9
+ type SqlNamespaceEntries,
10
+ type SqlNamespaceInput,
11
+ type StorageTable,
12
12
  } from '@prisma-next/sql-contract/types';
13
13
  import { blindCast } from '@prisma-next/utils/casts';
14
14
 
@@ -24,7 +24,7 @@ const SQLITE_NAMESPACE_KIND = 'sqlite-namespace' as const;
24
24
  * `entries.table` and unqualified `qualifyTable()` emission for runtime
25
25
  * SQL rendering.
26
26
  */
27
- export class SqliteDatabase extends NamespaceBase {
27
+ export class SqliteDatabase extends SqlNamespaceBase {
28
28
  declare readonly kind: string;
29
29
 
30
30
  readonly id: string;
@@ -94,7 +94,7 @@ export class SqliteUnboundDatabase extends SqliteDatabase {
94
94
  }
95
95
 
96
96
  export function buildSqliteNamespace(
97
- input: SqlNamespaceTablesInput,
97
+ input: SqlNamespaceInput,
98
98
  ): SqliteDatabase | SqliteUnboundDatabase {
99
99
  if (input.id !== UNBOUND_NAMESPACE_ID) {
100
100
  throw new Error(
@@ -121,7 +121,7 @@ export function buildSqliteNamespace(
121
121
  * safety net rather than a user-facing surface.
122
122
  */
123
123
  export function sqliteCreateNamespace(
124
- input: SqlNamespaceTablesInput,
124
+ input: SqlNamespaceInput,
125
125
  ): SqliteDatabase | SqliteUnboundDatabase {
126
126
  return buildSqliteNamespace(input);
127
127
  }
@@ -1,3 +1,4 @@
1
1
  export type { SqliteRuntimeTargetInstance } from '../core/runtime-target';
2
2
  export { default } from '../core/runtime-target';
3
3
  export { SqliteContractSerializer } from '../core/sqlite-contract-serializer';
4
+ export { SqliteContractView } from '../core/sqlite-contract-view';
@@ -1 +0,0 @@
1
- {"version":3,"file":"descriptor-meta-DxmEeTJ-.mjs","names":[],"sources":["../src/core/authoring.ts","../src/core/descriptor-meta-runtime.ts","../src/core/descriptor-meta.ts"],"sourcesContent":["import { temporalAuthoringPresets } from '@prisma-next/family-sql/control';\nimport type { AuthoringFieldNamespace } from '@prisma-next/framework-components/authoring';\n\nexport const sqliteAuthoringFieldPresets = {\n temporal: /* @__PURE__ */ temporalAuthoringPresets({\n codecId: 'sqlite/datetime@1',\n nativeType: 'text',\n }),\n} as const satisfies AuthoringFieldNamespace;\n","// Runtime-safe slice of the sqlite target descriptor metadata.\n//\n// This file exists separately from ./descriptor-meta on purpose: the runtime\n// plane reads only `kind/familyId/targetId/id/version/capabilities` (plus the\n// `__codecTypes` phantom). The `authoring` slot lives on the pack/control\n// descriptor only, because authoring contributions are consumed at\n// contract-construction time by `assembleAuthoringContributions` (control\n// plane) and the PSL interpreter — never at runtime.\n//\n// Keeping the runtime closure free of the `./authoring` import is what lets\n// the bundler tree-shake `@prisma-next/family-sql/control` (and its\n// transitive `verify-sql-schema` chunk) out of the runtime entry. Do not\n// add an `authoring` field here — if you need to, the pack/control meta in\n// `./descriptor-meta` is the right place. See TML-2766 for context.\nimport type { CodecTypes } from '../exports/codec-types';\n\nconst sqliteTargetDescriptorMetaRuntimeBase = {\n kind: 'target',\n familyId: 'sql',\n targetId: 'sqlite',\n id: 'sqlite',\n version: '0.0.1',\n capabilities: {},\n} as const;\n\nexport const sqliteTargetDescriptorMetaRuntime: typeof sqliteTargetDescriptorMetaRuntimeBase & {\n readonly __codecTypes?: CodecTypes;\n} = sqliteTargetDescriptorMetaRuntimeBase;\n","import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport type { CodecTypes } from '../exports/codec-types';\nimport { sqliteAuthoringFieldPresets } from './authoring';\nimport { sqliteTargetDescriptorMetaRuntime } from './descriptor-meta-runtime';\n\nconst sqliteTargetDescriptorMetaBase = {\n ...sqliteTargetDescriptorMetaRuntime,\n defaultNamespaceId: UNBOUND_NAMESPACE_ID,\n authoring: {\n field: sqliteAuthoringFieldPresets,\n },\n} as const;\n\nexport const sqliteTargetDescriptorMeta: typeof sqliteTargetDescriptorMetaBase & {\n readonly __codecTypes?: CodecTypes;\n} = sqliteTargetDescriptorMetaBase;\n"],"mappings":";;;AAGA,MAAa,8BAA8B,EACzC,UAA0B,yCAAyB;CACjD,SAAS;CACT,YAAY;AACd,CAAC,EACH;ACiBA,MAAa,oCAET;CAVF,MAAM;CACN,UAAU;CACV,UAAU;CACV,IAAI;CACJ,SAAS;CACT,cAAc,CAAC;AAKb;ACdJ,MAAa,6BAET;CATF,GAAG;CACH,oBAAoB;CACpB,WAAW,EACT,OAAO,4BACT;AAKE"}
@@ -1,14 +0,0 @@
1
- //#region src/core/native-type-normalizer.ts
2
- /**
3
- * Canonicalizes SQLite native-type tokens for verifier comparison.
4
- * Lives target-side so the planner / runner / adapter all share the same
5
- * normalization without crossing the `target-sqlite` ↔ `adapter-sqlite`
6
- * boundary.
7
- */
8
- function normalizeSqliteNativeType(nativeType) {
9
- return nativeType.trim().toLowerCase();
10
- }
11
- //#endregion
12
- export { normalizeSqliteNativeType as t };
13
-
14
- //# sourceMappingURL=native-type-normalizer-CiSyVmMP.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"native-type-normalizer-CiSyVmMP.mjs","names":[],"sources":["../src/core/native-type-normalizer.ts"],"sourcesContent":["/**\n * Canonicalizes SQLite native-type tokens for verifier comparison.\n * Lives target-side so the planner / runner / adapter all share the same\n * normalization without crossing the `target-sqlite` ↔ `adapter-sqlite`\n * boundary.\n */\nexport function normalizeSqliteNativeType(nativeType: string): string {\n return nativeType.trim().toLowerCase();\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,0BAA0B,YAA4B;CACpE,OAAO,WAAW,KAAK,CAAC,CAAC,YAAY;AACvC"}