@prisma-next/target-sqlite 0.13.0-dev.2 → 0.13.0-dev.21

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 (62) hide show
  1. package/dist/contract-free.mjs +1 -20
  2. package/dist/contract-free.mjs.map +1 -1
  3. package/dist/control.mjs +17 -19
  4. package/dist/control.mjs.map +1 -1
  5. package/dist/ddl-CH8V_qcd.mjs +23 -0
  6. package/dist/ddl-CH8V_qcd.mjs.map +1 -0
  7. package/dist/migration.d.mts +3 -3
  8. package/dist/migration.d.mts.map +1 -1
  9. package/dist/migration.mjs +4 -3
  10. package/dist/migration.mjs.map +1 -1
  11. package/dist/{tables-CjB7vXCr.mjs → op-factory-call-BX69rHxs.mjs} +375 -17
  12. package/dist/op-factory-call-BX69rHxs.mjs.map +1 -0
  13. package/dist/op-factory-call.d.mts +16 -12
  14. package/dist/op-factory-call.d.mts.map +1 -1
  15. package/dist/op-factory-call.mjs +1 -1
  16. package/dist/{planner-DSNDwQy9.mjs → planner-iYg56pzJ.mjs} +78 -11
  17. package/dist/planner-iYg56pzJ.mjs.map +1 -0
  18. package/dist/{planner-produced-sqlite-migration-C1yqJAiM.d.mts → planner-produced-sqlite-migration-BWpnDmhM.d.mts} +5 -4
  19. package/dist/planner-produced-sqlite-migration-BWpnDmhM.d.mts.map +1 -0
  20. package/dist/{planner-produced-sqlite-migration-DowV_vHw.mjs → planner-produced-sqlite-migration-DwQSUgSk.mjs} +9 -5
  21. package/dist/planner-produced-sqlite-migration-DwQSUgSk.mjs.map +1 -0
  22. package/dist/planner-produced-sqlite-migration.d.mts +1 -1
  23. package/dist/planner-produced-sqlite-migration.mjs +1 -1
  24. package/dist/planner.d.mts +5 -2
  25. package/dist/planner.d.mts.map +1 -1
  26. package/dist/planner.mjs +1 -1
  27. package/dist/render-ops-BDW2tUeR.mjs +22 -0
  28. package/dist/render-ops-BDW2tUeR.mjs.map +1 -0
  29. package/dist/render-ops.d.mts +2 -1
  30. package/dist/render-ops.d.mts.map +1 -1
  31. package/dist/render-ops.mjs +1 -1
  32. package/dist/runtime.mjs +1 -1
  33. package/dist/{sqlite-contract-serializer-jcRu8aHh.mjs → sqlite-contract-serializer-C41PO7DT.mjs} +2 -2
  34. package/dist/{sqlite-contract-serializer-jcRu8aHh.mjs.map → sqlite-contract-serializer-C41PO7DT.mjs.map} +1 -1
  35. package/dist/sqlite-migration-CCYnBXZp.mjs +73 -0
  36. package/dist/sqlite-migration-CCYnBXZp.mjs.map +1 -0
  37. package/dist/sqlite-migration-CJrASAxf.d.mts +46 -0
  38. package/dist/sqlite-migration-CJrASAxf.d.mts.map +1 -0
  39. package/package.json +18 -18
  40. package/src/core/control-target.ts +4 -4
  41. package/src/core/errors.ts +28 -0
  42. package/src/core/migrations/issue-planner.ts +148 -4
  43. package/src/core/migrations/op-factory-call.ts +162 -23
  44. package/src/core/migrations/planner-ddl-builders.ts +1 -1
  45. package/src/core/migrations/planner-produced-sqlite-migration.ts +8 -2
  46. package/src/core/migrations/planner.ts +14 -2
  47. package/src/core/migrations/render-ops.ts +37 -9
  48. package/src/core/migrations/runner.ts +16 -12
  49. package/src/core/migrations/sqlite-migration.ts +51 -1
  50. package/src/exports/migration.ts +9 -1
  51. package/dist/op-factory-call-DymqdXQW.mjs +0 -279
  52. package/dist/op-factory-call-DymqdXQW.mjs.map +0 -1
  53. package/dist/planner-DSNDwQy9.mjs.map +0 -1
  54. package/dist/planner-produced-sqlite-migration-C1yqJAiM.d.mts.map +0 -1
  55. package/dist/planner-produced-sqlite-migration-DowV_vHw.mjs.map +0 -1
  56. package/dist/render-ops-CFRbJ3Yb.mjs +0 -8
  57. package/dist/render-ops-CFRbJ3Yb.mjs.map +0 -1
  58. package/dist/sqlite-migration-CUqgmzQH.mjs +0 -16
  59. package/dist/sqlite-migration-CUqgmzQH.mjs.map +0 -1
  60. package/dist/sqlite-migration-D4XGYzgQ.d.mts +0 -17
  61. package/dist/sqlite-migration-D4XGYzgQ.d.mts.map +0 -1
  62. package/dist/tables-CjB7vXCr.mjs.map +0 -1
@@ -2,13 +2,11 @@
2
2
  * SQLite migration IR: one concrete `*Call` class per pure factory under
3
3
  * `operations/`, plus a shared `SqliteOpFactoryCallNode` abstract base.
4
4
  *
5
- * Each call class carries fully-resolved literal arguments (flat
6
- * `SqliteColumnSpec` / `SqliteTableSpec` etc.) codec / `typeRef` / default
7
- * expansion happens upstream in the issue-planner / strategies, mirroring
8
- * the Postgres `ColumnSpec` pattern. As a result, `toOp()` and
9
- * `renderTypeScript()` both pass the same flat data through; the rendered
10
- * TypeScript scaffold is fully self-contained and does not need access to a
11
- * `storageTypes` map at runtime.
5
+ * Each call class carries fully-resolved literal arguments. `CreateTableCall`
6
+ * holds structured `DdlColumn[]` + `DdlTableConstraint[]` and lowers via the
7
+ * adapter's DDL path; other call classes carry flat SQL fragments. Codec /
8
+ * `typeRef` / default expansion happens upstream in the issue-planner /
9
+ * strategies, mirroring the Postgres `ColumnSpec` pattern.
12
10
  */
13
11
 
14
12
  import { errorUnfilledPlaceholder } from '@prisma-next/errors/migration';
@@ -16,13 +14,24 @@ import type {
16
14
  MigrationOperationClass,
17
15
  SqlMigrationPlanOperation,
18
16
  } from '@prisma-next/family-sql/control';
17
+ import type { ExecuteRequestLowerer, Lowerer } from '@prisma-next/family-sql/control-adapter';
19
18
  import type { OpFactoryCall as FrameworkOpFactoryCall } from '@prisma-next/framework-components/control';
19
+ import type {
20
+ AnyDdlColumnDefault,
21
+ DdlColumn,
22
+ DdlTableConstraint,
23
+ } from '@prisma-next/sql-relational-core/ast';
20
24
  import { type ImportRequirement, jsonToTsSource, TsExpression } from '@prisma-next/ts-render';
25
+ import { ifDefined } from '@prisma-next/utils/defined';
26
+ import * as contractFreeDdl from '../../contract-free/ddl';
27
+ import { escapeLiteral } from '../sql-utils';
21
28
  import { addColumn, dropColumn } from './operations/columns';
22
29
  import { createIndex, dropIndex } from './operations/indexes';
23
30
  import type { SqliteColumnSpec, SqliteIndexSpec, SqliteTableSpec } from './operations/shared';
24
- import { createTable, dropTable, recreateTable } from './operations/tables';
31
+ import { step } from './operations/shared';
32
+ import { dropTable, recreateTable } from './operations/tables';
25
33
  import type { SqlitePlanTargetDetails } from './planner-target-details';
34
+ import { buildTargetDetails } from './planner-target-details';
26
35
 
27
36
  type Op = SqlMigrationPlanOperation<SqlitePlanTargetDetails>;
28
37
 
@@ -32,7 +41,7 @@ abstract class SqliteOpFactoryCallNode extends TsExpression implements Framework
32
41
  abstract readonly factoryName: string;
33
42
  abstract readonly operationClass: MigrationOperationClass;
34
43
  abstract readonly label: string;
35
- abstract toOp(): Op;
44
+ abstract toOp(lowerer?: Lowerer): Op | Promise<Op>;
36
45
 
37
46
  importRequirements(): readonly ImportRequirement[] {
38
47
  return [{ moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: this.factoryName }];
@@ -47,27 +56,157 @@ abstract class SqliteOpFactoryCallNode extends TsExpression implements Framework
47
56
  // Table
48
57
  // ============================================================================
49
58
 
59
+ // ---------------------------------------------------------------------------
60
+ // TypeScript rendering helpers for DdlColumn / DdlTableConstraint
61
+ // ---------------------------------------------------------------------------
62
+
63
+ function renderDdlColumnDefault(def: AnyDdlColumnDefault | undefined): string {
64
+ if (!def) return '';
65
+ if (def.kind === 'literal') {
66
+ return `lit(${jsonToTsSource(def.value)})`;
67
+ }
68
+ return `fn(${jsonToTsSource(def.expression)})`;
69
+ }
70
+
71
+ function renderDdlColumnAsTsCall(column: DdlColumn): string {
72
+ const opts: string[] = [];
73
+ if (column.notNull) opts.push('notNull: true');
74
+ if (column.primaryKey) opts.push('primaryKey: true');
75
+ if (column.default) opts.push(`default: ${renderDdlColumnDefault(column.default)}`);
76
+ const optsStr = opts.length > 0 ? `, { ${opts.join(', ')} }` : '';
77
+ return `col(${jsonToTsSource(column.name)}, ${jsonToTsSource(column.type)}${optsStr})`;
78
+ }
79
+
80
+ function renderDdlConstraintAsTsCall(constraint: DdlTableConstraint): string {
81
+ switch (constraint.kind) {
82
+ case 'primary-key': {
83
+ const nameOpt = constraint.name ? `, { name: ${jsonToTsSource(constraint.name)} }` : '';
84
+ return `primaryKey(${jsonToTsSource(constraint.columns)}${nameOpt})`;
85
+ }
86
+ case 'foreign-key': {
87
+ const opts: string[] = [];
88
+ if (constraint.name) opts.push(`name: ${jsonToTsSource(constraint.name)}`);
89
+ if (constraint.onDelete) opts.push(`onDelete: ${jsonToTsSource(constraint.onDelete)}`);
90
+ if (constraint.onUpdate) opts.push(`onUpdate: ${jsonToTsSource(constraint.onUpdate)}`);
91
+ const optsStr = opts.length > 0 ? `, { ${opts.join(', ')} }` : '';
92
+ return `foreignKey(${jsonToTsSource(constraint.columns)}, ${jsonToTsSource(constraint.refTable)}, ${jsonToTsSource(constraint.refColumns)}${optsStr})`;
93
+ }
94
+ case 'unique': {
95
+ const nameOpt = constraint.name ? `, { name: ${jsonToTsSource(constraint.name)} }` : '';
96
+ return `unique(${jsonToTsSource(constraint.columns)}${nameOpt})`;
97
+ }
98
+ }
99
+ }
100
+
101
+ function constraintImportSymbols(constraints: readonly DdlTableConstraint[] | undefined): string[] {
102
+ if (!constraints || constraints.length === 0) return [];
103
+ const symbols = new Set<string>();
104
+ for (const c of constraints) {
105
+ if (c.kind === 'primary-key') symbols.add('primaryKey');
106
+ else if (c.kind === 'foreign-key') symbols.add('foreignKey');
107
+ else if (c.kind === 'unique') symbols.add('unique');
108
+ }
109
+ return [...symbols];
110
+ }
111
+
112
+ function defaultImportSymbols(columns: readonly DdlColumn[]): string[] {
113
+ const symbols = new Set<string>();
114
+ for (const col of columns) {
115
+ if (col.default?.kind === 'literal') symbols.add('lit');
116
+ else if (col.default?.kind === 'function') symbols.add('fn');
117
+ }
118
+ return [...symbols];
119
+ }
120
+
50
121
  export class CreateTableCall extends SqliteOpFactoryCallNode {
51
122
  readonly factoryName = 'createTable' as const;
52
123
  readonly operationClass = 'additive' as const;
53
124
  readonly tableName: string;
54
- readonly spec: SqliteTableSpec;
125
+ readonly columns: readonly DdlColumn[];
126
+ readonly constraints: readonly DdlTableConstraint[] | undefined;
55
127
  readonly label: string;
56
128
 
57
- constructor(tableName: string, spec: SqliteTableSpec) {
129
+ constructor(
130
+ tableName: string,
131
+ columns: readonly DdlColumn[],
132
+ constraints?: readonly DdlTableConstraint[],
133
+ ) {
58
134
  super();
59
135
  this.tableName = tableName;
60
- this.spec = spec;
136
+ this.columns = Object.freeze([...columns]);
137
+ this.constraints = constraints ? Object.freeze([...constraints]) : undefined;
61
138
  this.label = `Create table ${tableName}`;
62
139
  this.freeze();
63
140
  }
64
141
 
65
- toOp(): Op {
66
- return createTable(this.tableName, this.spec);
142
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
143
+ if (lowerer === undefined) {
144
+ throw new Error(
145
+ `CreateTableCall.toOp: a DDL lowerer is required on the SQLite planner path (table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
146
+ );
147
+ }
148
+ const ddlNode = contractFreeDdl.createTable({
149
+ table: this.tableName,
150
+ columns: this.columns,
151
+ ...ifDefined('constraints', this.constraints),
152
+ });
153
+ const statement = await lowerer.lowerToExecuteRequest(ddlNode);
154
+ const tableName = this.tableName;
155
+ const escapedName = escapeLiteral(tableName);
156
+ return {
157
+ id: `table.${tableName}`,
158
+ label: `Create table ${tableName}`,
159
+ summary: `Creates table ${tableName} with required columns`,
160
+ operationClass: 'additive',
161
+ target: { id: 'sqlite', details: buildTargetDetails('table', tableName) },
162
+ precheck: [
163
+ step(
164
+ `ensure table "${tableName}" does not exist`,
165
+ `SELECT COUNT(*) = 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapedName}'`,
166
+ ),
167
+ ],
168
+ execute: [
169
+ {
170
+ description: `create table "${tableName}"`,
171
+ sql: statement.sql,
172
+ params: statement.params ?? [],
173
+ },
174
+ ],
175
+ postcheck: [
176
+ step(
177
+ `verify table "${tableName}" exists`,
178
+ `SELECT COUNT(*) > 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapedName}'`,
179
+ ),
180
+ ],
181
+ };
67
182
  }
68
183
 
69
184
  renderTypeScript(): string {
70
- return `createTable(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.spec)})`;
185
+ const columnsList = this.columns.map(renderDdlColumnAsTsCall).join(', ');
186
+ const constraintsList = this.constraints
187
+ ? this.constraints.map(renderDdlConstraintAsTsCall).join(', ')
188
+ : undefined;
189
+
190
+ const opts: string[] = [];
191
+ opts.push(`table: ${jsonToTsSource(this.tableName)}`);
192
+ opts.push(`columns: [${columnsList}]`);
193
+ if (constraintsList) opts.push(`constraints: [${constraintsList}]`);
194
+
195
+ return `this.createTable({ ${opts.join(', ')} })`;
196
+ }
197
+
198
+ override importRequirements(): readonly ImportRequirement[] {
199
+ const req: ImportRequirement[] = [];
200
+ if (this.columns.length > 0) {
201
+ req.push({ moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: 'col' });
202
+ for (const sym of defaultImportSymbols(this.columns)) {
203
+ req.push({ moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: sym });
204
+ }
205
+ }
206
+ for (const sym of constraintImportSymbols(this.constraints)) {
207
+ req.push({ moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: sym });
208
+ }
209
+ return req;
71
210
  }
72
211
  }
73
212
 
@@ -84,7 +223,7 @@ export class DropTableCall extends SqliteOpFactoryCallNode {
84
223
  this.freeze();
85
224
  }
86
225
 
87
- toOp(): Op {
226
+ toOp(_lowerer?: Lowerer): Op {
88
227
  return dropTable(this.tableName);
89
228
  }
90
229
 
@@ -125,7 +264,7 @@ export class RecreateTableCall extends SqliteOpFactoryCallNode {
125
264
  this.freeze();
126
265
  }
127
266
 
128
- toOp(): Op {
267
+ toOp(_lowerer?: Lowerer): Op {
129
268
  return recreateTable({
130
269
  tableName: this.tableName,
131
270
  contractTable: this.contractTable,
@@ -172,7 +311,7 @@ export class AddColumnCall extends SqliteOpFactoryCallNode {
172
311
  this.freeze();
173
312
  }
174
313
 
175
- toOp(): Op {
314
+ toOp(_lowerer?: Lowerer): Op {
176
315
  return addColumn(this.tableName, this.column);
177
316
  }
178
317
 
@@ -196,7 +335,7 @@ export class DropColumnCall extends SqliteOpFactoryCallNode {
196
335
  this.freeze();
197
336
  }
198
337
 
199
- toOp(): Op {
338
+ toOp(_lowerer?: Lowerer): Op {
200
339
  return dropColumn(this.tableName, this.columnName);
201
340
  }
202
341
 
@@ -226,7 +365,7 @@ export class CreateIndexCall extends SqliteOpFactoryCallNode {
226
365
  this.freeze();
227
366
  }
228
367
 
229
- toOp(): Op {
368
+ toOp(_lowerer?: Lowerer): Op {
230
369
  return createIndex(this.tableName, this.indexName, this.columns);
231
370
  }
232
371
 
@@ -250,7 +389,7 @@ export class DropIndexCall extends SqliteOpFactoryCallNode {
250
389
  this.freeze();
251
390
  }
252
391
 
253
- toOp(): Op {
392
+ toOp(_lowerer?: Lowerer): Op {
254
393
  return dropIndex(this.tableName, this.indexName);
255
394
  }
256
395
 
@@ -292,7 +431,7 @@ export class DataTransformCall extends SqliteOpFactoryCallNode {
292
431
  this.freeze();
293
432
  }
294
433
 
295
- toOp(): Op {
434
+ toOp(_lowerer?: Lowerer): Op {
296
435
  throw errorUnfilledPlaceholder(this.label);
297
436
  }
298
437
 
@@ -344,7 +483,7 @@ export class RawSqlCall extends SqliteOpFactoryCallNode {
344
483
  this.freeze();
345
484
  }
346
485
 
347
- toOp(): Op {
486
+ toOp(_lowerer?: Lowerer): Op {
348
487
  return this.op;
349
488
  }
350
489
 
@@ -123,7 +123,7 @@ export function isInlineAutoincrementPrimaryKey(table: StorageTable, columnName:
123
123
 
124
124
  type ResolvedColumnTypeMetadata = Pick<StorageColumn, 'nativeType' | 'codecId' | 'typeParams'>;
125
125
 
126
- function resolveColumnTypeMetadata(
126
+ export function resolveColumnTypeMetadata(
127
127
  column: StorageColumn,
128
128
  storageTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
129
129
  ): ResolvedColumnTypeMetadata {
@@ -1,4 +1,5 @@
1
1
  import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
2
+ import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
2
3
  import type {
3
4
  MigrationPlanWithAuthoringSurface,
4
5
  OpFactoryCall,
@@ -24,22 +25,27 @@ export class TypeScriptRenderableSqliteMigration
24
25
  readonly #meta: MigrationMeta;
25
26
  readonly #destination: SqliteMigrationDestinationInfo;
26
27
  readonly #spaceId: string;
28
+ readonly #lowerer: ExecuteRequestLowerer | undefined;
29
+ #operationsCache: readonly (Op | Promise<Op>)[] | undefined;
27
30
 
28
31
  constructor(
29
32
  calls: readonly OpFactoryCall[],
30
33
  meta: MigrationMeta,
31
34
  spaceId: string,
32
35
  destination?: SqliteMigrationDestinationInfo,
36
+ lowerer?: ExecuteRequestLowerer,
33
37
  ) {
34
38
  super();
35
39
  this.#calls = calls;
36
40
  this.#meta = meta;
37
41
  this.#spaceId = spaceId;
38
42
  this.#destination = destination ?? { storageHash: meta.to };
43
+ this.#lowerer = lowerer;
39
44
  }
40
45
 
41
- override get operations(): readonly Op[] {
42
- return renderOps(this.#calls);
46
+ override get operations(): readonly (Op | Promise<Op>)[] {
47
+ this.#operationsCache ??= renderOps(this.#calls, this.#lowerer);
48
+ return this.#operationsCache;
43
49
  }
44
50
 
45
51
  override describe(): MigrationMeta {
@@ -10,6 +10,7 @@ import {
10
10
  planFieldEventOperations,
11
11
  plannerFailure,
12
12
  } from '@prisma-next/family-sql/control';
13
+ import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
13
14
  import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
14
15
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
15
16
  import type {
@@ -27,8 +28,10 @@ import {
27
28
  import { sqlitePlannerStrategies } from './planner-strategies';
28
29
  import type { SqlitePlanTargetDetails } from './planner-target-details';
29
30
 
30
- export function createSqliteMigrationPlanner(): SqliteMigrationPlanner {
31
- return new SqliteMigrationPlanner();
31
+ export function createSqliteMigrationPlanner(
32
+ lowerer: ExecuteRequestLowerer,
33
+ ): SqliteMigrationPlanner {
34
+ return new SqliteMigrationPlanner(lowerer);
32
35
  }
33
36
 
34
37
  export type SqlitePlanResult =
@@ -52,6 +55,12 @@ export type SqlitePlanResult =
52
55
  export class SqliteMigrationPlanner
53
56
  implements SqlMigrationPlanner<SqlitePlanTargetDetails>, MigrationPlanner<'sql', 'sqlite'>
54
57
  {
58
+ readonly #lowerer: ExecuteRequestLowerer;
59
+
60
+ constructor(lowerer: ExecuteRequestLowerer) {
61
+ this.#lowerer = lowerer;
62
+ }
63
+
55
64
  plan(options: {
56
65
  readonly contract: unknown;
57
66
  readonly schema: unknown;
@@ -89,6 +98,8 @@ export class SqliteMigrationPlanner
89
98
  to: context.toHash,
90
99
  },
91
100
  spaceId,
101
+ undefined,
102
+ this.#lowerer,
92
103
  );
93
104
  }
94
105
 
@@ -149,6 +160,7 @@ export class SqliteMigrationPlanner
149
160
  },
150
161
  options.spaceId,
151
162
  destination,
163
+ this.#lowerer,
152
164
  ),
153
165
  };
154
166
  }
@@ -1,15 +1,43 @@
1
1
  import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
2
- import type { OpFactoryCall } from '@prisma-next/framework-components/control';
2
+ import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
3
+ import type {
4
+ MigrationPlanOperation,
5
+ OpFactoryCall,
6
+ } from '@prisma-next/framework-components/control';
7
+ import { blindCast } from '@prisma-next/utils/casts';
8
+ import { isThenable } from '@prisma-next/utils/promise';
3
9
  import type { SqlitePlanTargetDetails } from './planner-target-details';
4
10
 
5
11
  type Op = SqlMigrationPlanOperation<SqlitePlanTargetDetails>;
6
12
 
7
- export function renderOps(calls: readonly OpFactoryCall[]): Op[] {
8
- // Each call's `toOp()` is typed as the framework `MigrationPlanOperation`;
9
- // every concrete Call class on the sqlite planner path produces an op
10
- // whose `target.details` is `SqlitePlanTargetDetails`-shaped (or whose
11
- // `target.details` is absent, which is structurally compatible). The
12
- // narrowing cast happens at this single integration boundary instead of
13
- // poisoning every caller's type.
14
- return calls.map((c) => c.toOp() as Op);
13
+ function assertSqliteOp(op: MigrationPlanOperation, callFactoryName: string): asserts op is Op {
14
+ const targetId = blindCast<
15
+ { target?: { id?: string } },
16
+ 'op.target is present on concrete SqlMigrationPlanOperation but absent on the framework MigrationPlanOperation base'
17
+ >(op).target?.id;
18
+ if (targetId !== 'sqlite') {
19
+ throw new Error(
20
+ `renderOps: expected sqlite op but got target.id="${String(targetId)}" for op.id="${op.id}" (factoryName="${callFactoryName}"). An OpFactoryCall produced an op for a different target on the sqlite planner path; check the call's target binding.`,
21
+ );
22
+ }
23
+ }
24
+
25
+ export function renderOps(
26
+ calls: readonly OpFactoryCall[],
27
+ lowerer?: ExecuteRequestLowerer,
28
+ ): (Op | Promise<Op>)[] {
29
+ return calls.map((c) => {
30
+ const opOrPromise = blindCast<
31
+ { toOp(lowerer?: ExecuteRequestLowerer): Op | Promise<Op> },
32
+ 'SQLite OpFactoryCall.toOp accepts an optional ExecuteRequestLowerer; the framework interface omits it because not all targets need a lowerer — the SQLite target overrides with this extended signature'
33
+ >(c).toOp(lowerer);
34
+ if (isThenable(opOrPromise)) {
35
+ return opOrPromise.then((op) => {
36
+ assertSqliteOp(op, c.factoryName);
37
+ return op;
38
+ });
39
+ }
40
+ assertSqliteOp(opOrPromise, c.factoryName);
41
+ return opOrPromise;
42
+ });
15
43
  }
@@ -16,7 +16,8 @@ import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
16
16
  import type { MigrationRunnerResult } from '@prisma-next/framework-components/control';
17
17
  import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
18
18
  import type { SqlControlDriverInstance, SqlStorage } from '@prisma-next/sql-contract/types';
19
- import type { LoweredStatement } from '@prisma-next/sql-relational-core/ast';
19
+ import type { SqlExecuteRequest } from '@prisma-next/sql-relational-core/ast';
20
+ import { blindCast } from '@prisma-next/utils/casts';
20
21
  import { ifDefined } from '@prisma-next/utils/defined';
21
22
  import type { Result } from '@prisma-next/utils/result';
22
23
  import { notOk, ok, okVoid } from '@prisma-next/utils/result';
@@ -51,13 +52,19 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
51
52
  }
52
53
  const space = options.plan.spaceId;
53
54
 
55
+ // Materialize any async ops before running checks or executing.
56
+ const planOps = blindCast<
57
+ readonly SqlMigrationPlanOperation<SqlitePlanTargetDetails>[],
58
+ 'ops were produced by the SQLite planner and are SqlMigrationPlanOperation<SqlitePlanTargetDetails>; MigrationPlan.operations uses the wider framework type to accommodate Promise covariance'
59
+ >(await Promise.all(options.plan.operations));
60
+
54
61
  const destinationCheck = this.ensurePlanMatchesDestinationContract(
55
62
  options.plan.destination,
56
63
  options.destinationContract,
57
64
  );
58
65
  if (!destinationCheck.ok) return destinationCheck;
59
66
 
60
- const policyCheck = this.enforcePolicyCompatibility(options.policy, options.plan.operations);
67
+ const policyCheck = this.enforcePolicyCompatibility(options.policy, planOps);
61
68
  if (!policyCheck.ok) return policyCheck;
62
69
 
63
70
  const ensureResult = await this.ensureControlTables(driver, options.destinationContract);
@@ -78,7 +85,7 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
78
85
  operationsExecuted = 0;
79
86
  executedOperations = [];
80
87
  } else {
81
- const applyResult = await this.applyPlan(driver, options);
88
+ const applyResult = await this.applyPlan(driver, options, planOps);
82
89
  if (!applyResult.ok) return applyResult;
83
90
  operationsExecuted = applyResult.value.operationsExecuted;
84
91
  executedOperations = applyResult.value.executedOperations;
@@ -122,7 +129,7 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
122
129
  }
123
130
 
124
131
  return runnerSuccess({
125
- operationsPlanned: options.plan.operations.length,
132
+ operationsPlanned: planOps.length,
126
133
  operationsExecuted,
127
134
  });
128
135
  }
@@ -226,6 +233,7 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
226
233
  private async applyPlan(
227
234
  driver: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>['driver'],
228
235
  options: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>,
236
+ ops: readonly SqlMigrationPlanOperation<SqlitePlanTargetDetails>[],
229
237
  ): Promise<
230
238
  Result<
231
239
  {
@@ -243,7 +251,7 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
243
251
  let operationsExecuted = 0;
244
252
  const executedOperations: Array<SqlMigrationPlanOperation<SqlitePlanTargetDetails>> = [];
245
253
 
246
- for (const operation of options.plan.operations) {
254
+ for (const operation of ops) {
247
255
  options.callbacks?.onOperationStart?.(operation);
248
256
  try {
249
257
  if (runPostchecks && runIdempotency) {
@@ -305,7 +313,7 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
305
313
  }
306
314
  const lowererContext = { contract };
307
315
  for (const query of this.family.bootstrapControlTableQueries()) {
308
- await this.executeStatement(driver, this.family.lowerAst(query, lowererContext));
316
+ await this.executeStatement(driver, await this.family.lowerAst(query, lowererContext));
309
317
  }
310
318
  return okVoid();
311
319
  }
@@ -659,12 +667,8 @@ class SqliteMigrationRunner implements SqlMigrationRunner<SqlitePlanTargetDetail
659
667
 
660
668
  private async executeStatement(
661
669
  driver: SqlMigrationRunnerExecuteOptions<SqlitePlanTargetDetails>['driver'],
662
- statement: LoweredStatement,
670
+ statement: SqlExecuteRequest,
663
671
  ): Promise<void> {
664
- if (statement.params.length > 0) {
665
- await driver.query(statement.sql, statement.params);
666
- return;
667
- }
668
- await driver.query(statement.sql);
672
+ await driver.query(statement.sql, statement.params);
669
673
  }
670
674
  }
@@ -1,4 +1,11 @@
1
+ import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
2
+ import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
1
3
  import { Migration as SqlMigration } from '@prisma-next/family-sql/migration';
4
+ import type { ControlStack } from '@prisma-next/framework-components/control';
5
+ import type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
6
+ import { blindCast } from '@prisma-next/utils/casts';
7
+ import { errorSqliteMigrationStackMissing } from '../errors';
8
+ import { CreateTableCall } from './op-factory-call';
2
9
  import type { SqlitePlanTargetDetails } from './planner-target-details';
3
10
 
4
11
  /**
@@ -7,7 +14,50 @@ import type { SqlitePlanTargetDetails } from './planner-target-details';
7
14
  * SQLite literal, so both user-authored migrations and renderer-generated
8
15
  * scaffolds can extend `SqliteMigration` directly without redeclaring
9
16
  * target-local identity.
17
+ *
18
+ * The constructor materializes a single SQLite `SqlControlAdapter` from
19
+ * `stack.adapter.create(stack)` and stores it; the protected `createTable`
20
+ * instance method forwards to `CreateTableCall` with that stored adapter,
21
+ * so user migrations can write `this.createTable({...})` without threading
22
+ * the adapter through every call.
10
23
  */
11
- export abstract class SqliteMigration extends SqlMigration<SqlitePlanTargetDetails> {
24
+ export abstract class SqliteMigration extends SqlMigration<SqlitePlanTargetDetails, 'sqlite'> {
12
25
  readonly targetId = 'sqlite' as const;
26
+
27
+ /**
28
+ * Materialized SQLite control adapter, created once per migration
29
+ * instance from the injected stack. `undefined` only when the migration
30
+ * was instantiated without a stack (test fixtures); `createTable`
31
+ * throws in that case to surface the misuse.
32
+ */
33
+ protected readonly controlAdapter: SqlControlAdapter<'sqlite'> | undefined;
34
+
35
+ constructor(stack?: ControlStack<'sql', 'sqlite'>) {
36
+ super(stack);
37
+ this.controlAdapter = stack?.adapter
38
+ ? blindCast<
39
+ SqlControlAdapter<'sqlite'>,
40
+ 'The SQLite descriptor create() returns SqlControlAdapter<sqlite>; typed as wider ControlAdapterInstance at the framework boundary'
41
+ >(stack.adapter.create(stack))
42
+ : undefined;
43
+ }
44
+
45
+ /**
46
+ * Emit a `CREATE TABLE` migration operation. Builds a typed DDL node from
47
+ * the supplied options and lowers it through the stored control adapter.
48
+ * Throws if no adapter is present (i.e. migration instantiated without a stack).
49
+ */
50
+ protected createTable(options: {
51
+ readonly table: string;
52
+ readonly ifNotExists?: boolean;
53
+ readonly columns: readonly DdlColumn[];
54
+ readonly constraints?: readonly DdlTableConstraint[];
55
+ }): Promise<SqlMigrationPlanOperation<SqlitePlanTargetDetails>> {
56
+ if (!this.controlAdapter) {
57
+ throw errorSqliteMigrationStackMissing();
58
+ }
59
+ return new CreateTableCall(options.table, options.columns, options.constraints).toOp(
60
+ this.controlAdapter,
61
+ );
62
+ }
13
63
  }
@@ -9,6 +9,14 @@ export { MigrationCLI } from '@prisma-next/cli/migration-cli';
9
9
  // `placeholder("…")` slots, instead of pulling in `@prisma-next/errors`
10
10
  // directly. The planner emits an import from this same module.
11
11
  export { placeholder } from '@prisma-next/errors/migration';
12
+ export {
13
+ col,
14
+ fn,
15
+ foreignKey,
16
+ lit,
17
+ primaryKey,
18
+ unique,
19
+ } from '@prisma-next/sql-relational-core/contract-free';
12
20
  export { addColumn, dropColumn } from '../core/migrations/operations/columns';
13
21
  export {
14
22
  type DataTransformOptions,
@@ -16,7 +24,7 @@ export {
16
24
  } from '../core/migrations/operations/data-transform';
17
25
  export { createIndex, dropIndex } from '../core/migrations/operations/indexes';
18
26
  export { rawSql } from '../core/migrations/operations/raw';
19
- export { createTable, dropTable, recreateTable } from '../core/migrations/operations/tables';
27
+ export { dropTable, recreateTable } from '../core/migrations/operations/tables';
20
28
  // Target-owned base class for migrations. Aliased to `Migration` so
21
29
  // user-edited migration.ts files (and the renderer's scaffold) read as
22
30
  // `class M extends Migration { … }` without having to thread the