@prisma-next/target-sqlite 0.5.0-dev.8 → 0.5.0-dev.81

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 (134) hide show
  1. package/dist/codec-ids-CYwMu3-4.d.mts +13 -0
  2. package/dist/codec-ids-CYwMu3-4.d.mts.map +1 -0
  3. package/dist/codec-ids-CuUxYcd0.mjs +13 -0
  4. package/dist/codec-ids-CuUxYcd0.mjs.map +1 -0
  5. package/dist/codec-ids.d.mts +2 -0
  6. package/dist/codec-ids.mjs +2 -0
  7. package/dist/codec-types-DNauB5UT.d.mts +23 -0
  8. package/dist/codec-types-DNauB5UT.d.mts.map +1 -0
  9. package/dist/codec-types.d.mts +3 -0
  10. package/dist/codec-types.mjs +2 -0
  11. package/dist/codecs-BAlEiSeP.d.mts +126 -0
  12. package/dist/codecs-BAlEiSeP.d.mts.map +1 -0
  13. package/dist/codecs-DVnHtVWW.mjs +220 -0
  14. package/dist/codecs-DVnHtVWW.mjs.map +1 -0
  15. package/dist/codecs.d.mts +2 -0
  16. package/dist/codecs.mjs +13 -0
  17. package/dist/codecs.mjs.map +1 -0
  18. package/dist/control.d.mts +4 -3
  19. package/dist/control.d.mts.map +1 -1
  20. package/dist/control.mjs +428 -5
  21. package/dist/control.mjs.map +1 -1
  22. package/dist/default-normalizer-3Fccw7yw.mjs +69 -0
  23. package/dist/default-normalizer-3Fccw7yw.mjs.map +1 -0
  24. package/dist/default-normalizer.d.mts +7 -0
  25. package/dist/default-normalizer.d.mts.map +1 -0
  26. package/dist/default-normalizer.mjs +2 -0
  27. package/dist/descriptor-meta-CE2Kbn9b.mjs +17 -0
  28. package/dist/descriptor-meta-CE2Kbn9b.mjs.map +1 -0
  29. package/dist/migration.d.mts +85 -0
  30. package/dist/migration.d.mts.map +1 -0
  31. package/dist/migration.mjs +49 -0
  32. package/dist/migration.mjs.map +1 -0
  33. package/dist/native-type-normalizer-BlN5XfD-.mjs +14 -0
  34. package/dist/native-type-normalizer-BlN5XfD-.mjs.map +1 -0
  35. package/dist/native-type-normalizer.d.mts +11 -0
  36. package/dist/native-type-normalizer.d.mts.map +1 -0
  37. package/dist/native-type-normalizer.mjs +2 -0
  38. package/dist/op-factory-call-DRKKURAO.mjs +279 -0
  39. package/dist/op-factory-call-DRKKURAO.mjs.map +1 -0
  40. package/dist/op-factory-call.d.mts +151 -0
  41. package/dist/op-factory-call.d.mts.map +1 -0
  42. package/dist/op-factory-call.mjs +2 -0
  43. package/dist/pack.d.mts +35 -1
  44. package/dist/pack.d.mts.map +1 -1
  45. package/dist/pack.mjs +2 -3
  46. package/dist/planner-CdCU0v1B.mjs +525 -0
  47. package/dist/planner-CdCU0v1B.mjs.map +1 -0
  48. package/dist/planner-produced-sqlite-migration-CI9LdXPr.d.mts +29 -0
  49. package/dist/planner-produced-sqlite-migration-CI9LdXPr.d.mts.map +1 -0
  50. package/dist/planner-produced-sqlite-migration-C_TzWbT0.mjs +110 -0
  51. package/dist/planner-produced-sqlite-migration-C_TzWbT0.mjs.map +1 -0
  52. package/dist/planner-produced-sqlite-migration.d.mts +2 -0
  53. package/dist/planner-produced-sqlite-migration.mjs +2 -0
  54. package/dist/planner-target-details-Bm71XPKb.mjs +15 -0
  55. package/dist/planner-target-details-Bm71XPKb.mjs.map +1 -0
  56. package/dist/planner-target-details-vhvZDWK1.d.mts +12 -0
  57. package/dist/planner-target-details-vhvZDWK1.d.mts.map +1 -0
  58. package/dist/planner-target-details.d.mts +2 -0
  59. package/dist/planner-target-details.mjs +2 -0
  60. package/dist/planner.d.mts +59 -0
  61. package/dist/planner.d.mts.map +1 -0
  62. package/dist/planner.mjs +2 -0
  63. package/dist/render-ops-CSRDT4YL.mjs +8 -0
  64. package/dist/render-ops-CSRDT4YL.mjs.map +1 -0
  65. package/dist/render-ops.d.mts +10 -0
  66. package/dist/render-ops.d.mts.map +1 -0
  67. package/dist/render-ops.mjs +2 -0
  68. package/dist/runtime.d.mts.map +1 -1
  69. package/dist/runtime.mjs +4 -8
  70. package/dist/runtime.mjs.map +1 -1
  71. package/dist/shared-qLsgTOZs.d.mts +69 -0
  72. package/dist/shared-qLsgTOZs.d.mts.map +1 -0
  73. package/dist/sql-utils-DhevMgef.mjs +35 -0
  74. package/dist/sql-utils-DhevMgef.mjs.map +1 -0
  75. package/dist/sql-utils.d.mts +22 -0
  76. package/dist/sql-utils.d.mts.map +1 -0
  77. package/dist/sql-utils.mjs +2 -0
  78. package/dist/sqlite-migration-BBJktVVw.mjs +16 -0
  79. package/dist/sqlite-migration-BBJktVVw.mjs.map +1 -0
  80. package/dist/sqlite-migration-DAb2NEX6.d.mts +17 -0
  81. package/dist/sqlite-migration-DAb2NEX6.d.mts.map +1 -0
  82. package/dist/statement-builders-Dne-LkAV.mjs +158 -0
  83. package/dist/statement-builders-Dne-LkAV.mjs.map +1 -0
  84. package/dist/statement-builders.d.mts +68 -0
  85. package/dist/statement-builders.d.mts.map +1 -0
  86. package/dist/statement-builders.mjs +2 -0
  87. package/dist/tables-D84zfPZI.mjs +403 -0
  88. package/dist/tables-D84zfPZI.mjs.map +1 -0
  89. package/package.json +33 -11
  90. package/src/core/authoring.ts +9 -0
  91. package/src/core/codec-helpers.ts +11 -0
  92. package/src/core/codec-ids.ts +13 -0
  93. package/src/core/codecs.ts +337 -0
  94. package/src/core/control-target.ts +54 -11
  95. package/src/core/default-normalizer.ts +92 -0
  96. package/src/core/descriptor-meta.ts +5 -1
  97. package/src/core/migrations/issue-planner.ts +586 -0
  98. package/src/core/migrations/op-factory-call.ts +369 -0
  99. package/src/core/migrations/operations/columns.ts +62 -0
  100. package/src/core/migrations/operations/data-transform.ts +51 -0
  101. package/src/core/migrations/operations/indexes.ts +52 -0
  102. package/src/core/migrations/operations/raw.ts +12 -0
  103. package/src/core/migrations/operations/shared.ts +120 -0
  104. package/src/core/migrations/operations/tables.ts +388 -0
  105. package/src/core/migrations/planner-ddl-builders.ts +142 -0
  106. package/src/core/migrations/planner-produced-sqlite-migration.ts +70 -0
  107. package/src/core/migrations/planner-strategies.ts +231 -0
  108. package/src/core/migrations/planner-target-details.ts +33 -0
  109. package/src/core/migrations/planner.ts +183 -0
  110. package/src/core/migrations/render-ops.ts +15 -0
  111. package/src/core/migrations/render-typescript.ts +91 -0
  112. package/src/core/migrations/runner.ts +724 -0
  113. package/src/core/migrations/sqlite-migration.ts +13 -0
  114. package/src/core/migrations/statement-builders.ts +212 -0
  115. package/src/core/native-type-normalizer.ts +9 -0
  116. package/src/core/registry.ts +11 -0
  117. package/src/core/runtime-target.ts +1 -3
  118. package/src/core/sql-utils.ts +47 -0
  119. package/src/exports/codec-ids.ts +13 -0
  120. package/src/exports/codec-types.ts +43 -0
  121. package/src/exports/codecs.ts +20 -0
  122. package/src/exports/control.ts +1 -0
  123. package/src/exports/default-normalizer.ts +1 -0
  124. package/src/exports/migration.ts +24 -0
  125. package/src/exports/native-type-normalizer.ts +1 -0
  126. package/src/exports/op-factory-call.ts +12 -0
  127. package/src/exports/planner-produced-sqlite-migration.ts +4 -0
  128. package/src/exports/planner-target-details.ts +2 -0
  129. package/src/exports/planner.ts +2 -0
  130. package/src/exports/render-ops.ts +1 -0
  131. package/src/exports/sql-utils.ts +1 -0
  132. package/src/exports/statement-builders.ts +12 -0
  133. package/dist/descriptor-meta-DbuuziYA.mjs +0 -14
  134. package/dist/descriptor-meta-DbuuziYA.mjs.map +0 -1
@@ -0,0 +1,369 @@
1
+ /**
2
+ * SQLite migration IR: one concrete `*Call` class per pure factory under
3
+ * `operations/`, plus a shared `SqliteOpFactoryCallNode` abstract base.
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.
12
+ */
13
+
14
+ import { errorUnfilledPlaceholder } from '@prisma-next/errors/migration';
15
+ import type {
16
+ MigrationOperationClass,
17
+ SqlMigrationPlanOperation,
18
+ } from '@prisma-next/family-sql/control';
19
+ import type { OpFactoryCall as FrameworkOpFactoryCall } from '@prisma-next/framework-components/control';
20
+ import { type ImportRequirement, jsonToTsSource, TsExpression } from '@prisma-next/ts-render';
21
+ import { addColumn, dropColumn } from './operations/columns';
22
+ import { createIndex, dropIndex } from './operations/indexes';
23
+ import type { SqliteColumnSpec, SqliteIndexSpec, SqliteTableSpec } from './operations/shared';
24
+ import { createTable, dropTable, recreateTable } from './operations/tables';
25
+ import type { SqlitePlanTargetDetails } from './planner-target-details';
26
+
27
+ type Op = SqlMigrationPlanOperation<SqlitePlanTargetDetails>;
28
+
29
+ const TARGET_MIGRATION_MODULE = '@prisma-next/target-sqlite/migration';
30
+
31
+ abstract class SqliteOpFactoryCallNode extends TsExpression implements FrameworkOpFactoryCall {
32
+ abstract readonly factoryName: string;
33
+ abstract readonly operationClass: MigrationOperationClass;
34
+ abstract readonly label: string;
35
+ abstract toOp(): Op;
36
+
37
+ importRequirements(): readonly ImportRequirement[] {
38
+ return [{ moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: this.factoryName }];
39
+ }
40
+
41
+ protected freeze(): void {
42
+ Object.freeze(this);
43
+ }
44
+ }
45
+
46
+ // ============================================================================
47
+ // Table
48
+ // ============================================================================
49
+
50
+ export class CreateTableCall extends SqliteOpFactoryCallNode {
51
+ readonly factoryName = 'createTable' as const;
52
+ readonly operationClass = 'additive' as const;
53
+ readonly tableName: string;
54
+ readonly spec: SqliteTableSpec;
55
+ readonly label: string;
56
+
57
+ constructor(tableName: string, spec: SqliteTableSpec) {
58
+ super();
59
+ this.tableName = tableName;
60
+ this.spec = spec;
61
+ this.label = `Create table ${tableName}`;
62
+ this.freeze();
63
+ }
64
+
65
+ toOp(): Op {
66
+ return createTable(this.tableName, this.spec);
67
+ }
68
+
69
+ renderTypeScript(): string {
70
+ return `createTable(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.spec)})`;
71
+ }
72
+ }
73
+
74
+ export class DropTableCall extends SqliteOpFactoryCallNode {
75
+ readonly factoryName = 'dropTable' as const;
76
+ readonly operationClass = 'destructive' as const;
77
+ readonly tableName: string;
78
+ readonly label: string;
79
+
80
+ constructor(tableName: string) {
81
+ super();
82
+ this.tableName = tableName;
83
+ this.label = `Drop table ${tableName}`;
84
+ this.freeze();
85
+ }
86
+
87
+ toOp(): Op {
88
+ return dropTable(this.tableName);
89
+ }
90
+
91
+ renderTypeScript(): string {
92
+ return `dropTable(${jsonToTsSource(this.tableName)})`;
93
+ }
94
+ }
95
+
96
+ export class RecreateTableCall extends SqliteOpFactoryCallNode {
97
+ readonly factoryName = 'recreateTable' as const;
98
+ readonly operationClass: MigrationOperationClass;
99
+ readonly tableName: string;
100
+ readonly contractTable: SqliteTableSpec;
101
+ readonly schemaColumnNames: readonly string[];
102
+ readonly indexes: readonly SqliteIndexSpec[];
103
+ readonly summary: string;
104
+ readonly postchecks: readonly { readonly description: string; readonly sql: string }[];
105
+ readonly label: string;
106
+
107
+ constructor(args: {
108
+ tableName: string;
109
+ contractTable: SqliteTableSpec;
110
+ schemaColumnNames: readonly string[];
111
+ indexes: readonly SqliteIndexSpec[];
112
+ summary: string;
113
+ postchecks: readonly { readonly description: string; readonly sql: string }[];
114
+ operationClass: MigrationOperationClass;
115
+ }) {
116
+ super();
117
+ this.tableName = args.tableName;
118
+ this.contractTable = args.contractTable;
119
+ this.schemaColumnNames = args.schemaColumnNames;
120
+ this.indexes = args.indexes;
121
+ this.summary = args.summary;
122
+ this.postchecks = args.postchecks;
123
+ this.operationClass = args.operationClass;
124
+ this.label = `Recreate table ${args.tableName}`;
125
+ this.freeze();
126
+ }
127
+
128
+ toOp(): Op {
129
+ return recreateTable({
130
+ tableName: this.tableName,
131
+ contractTable: this.contractTable,
132
+ schemaColumnNames: this.schemaColumnNames,
133
+ indexes: this.indexes,
134
+ summary: this.summary,
135
+ postchecks: this.postchecks,
136
+ operationClass: this.operationClass,
137
+ });
138
+ }
139
+
140
+ renderTypeScript(): string {
141
+ const args = {
142
+ tableName: this.tableName,
143
+ contractTable: this.contractTable,
144
+ schemaColumnNames: this.schemaColumnNames,
145
+ indexes: this.indexes,
146
+ summary: this.summary,
147
+ postchecks: this.postchecks,
148
+ operationClass: this.operationClass,
149
+ };
150
+ return `recreateTable(${jsonToTsSource(args)})`;
151
+ }
152
+ }
153
+
154
+ // ============================================================================
155
+ // Column
156
+ // ============================================================================
157
+
158
+ export class AddColumnCall extends SqliteOpFactoryCallNode {
159
+ readonly factoryName = 'addColumn' as const;
160
+ readonly operationClass = 'additive' as const;
161
+ readonly tableName: string;
162
+ readonly columnName: string;
163
+ readonly column: SqliteColumnSpec;
164
+ readonly label: string;
165
+
166
+ constructor(tableName: string, column: SqliteColumnSpec) {
167
+ super();
168
+ this.tableName = tableName;
169
+ this.columnName = column.name;
170
+ this.column = column;
171
+ this.label = `Add column ${column.name} on ${tableName}`;
172
+ this.freeze();
173
+ }
174
+
175
+ toOp(): Op {
176
+ return addColumn(this.tableName, this.column);
177
+ }
178
+
179
+ renderTypeScript(): string {
180
+ return `addColumn(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.column)})`;
181
+ }
182
+ }
183
+
184
+ export class DropColumnCall extends SqliteOpFactoryCallNode {
185
+ readonly factoryName = 'dropColumn' as const;
186
+ readonly operationClass = 'destructive' as const;
187
+ readonly tableName: string;
188
+ readonly columnName: string;
189
+ readonly label: string;
190
+
191
+ constructor(tableName: string, columnName: string) {
192
+ super();
193
+ this.tableName = tableName;
194
+ this.columnName = columnName;
195
+ this.label = `Drop column ${columnName} on ${tableName}`;
196
+ this.freeze();
197
+ }
198
+
199
+ toOp(): Op {
200
+ return dropColumn(this.tableName, this.columnName);
201
+ }
202
+
203
+ renderTypeScript(): string {
204
+ return `dropColumn(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.columnName)})`;
205
+ }
206
+ }
207
+
208
+ // ============================================================================
209
+ // Index
210
+ // ============================================================================
211
+
212
+ export class CreateIndexCall extends SqliteOpFactoryCallNode {
213
+ readonly factoryName = 'createIndex' as const;
214
+ readonly operationClass = 'additive' as const;
215
+ readonly tableName: string;
216
+ readonly indexName: string;
217
+ readonly columns: readonly string[];
218
+ readonly label: string;
219
+
220
+ constructor(tableName: string, indexName: string, columns: readonly string[]) {
221
+ super();
222
+ this.tableName = tableName;
223
+ this.indexName = indexName;
224
+ this.columns = columns;
225
+ this.label = `Create index ${indexName} on ${tableName}`;
226
+ this.freeze();
227
+ }
228
+
229
+ toOp(): Op {
230
+ return createIndex(this.tableName, this.indexName, this.columns);
231
+ }
232
+
233
+ renderTypeScript(): string {
234
+ return `createIndex(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.indexName)}, ${jsonToTsSource(this.columns)})`;
235
+ }
236
+ }
237
+
238
+ export class DropIndexCall extends SqliteOpFactoryCallNode {
239
+ readonly factoryName = 'dropIndex' as const;
240
+ readonly operationClass = 'destructive' as const;
241
+ readonly tableName: string;
242
+ readonly indexName: string;
243
+ readonly label: string;
244
+
245
+ constructor(tableName: string, indexName: string) {
246
+ super();
247
+ this.tableName = tableName;
248
+ this.indexName = indexName;
249
+ this.label = `Drop index ${indexName} on ${tableName}`;
250
+ this.freeze();
251
+ }
252
+
253
+ toOp(): Op {
254
+ return dropIndex(this.tableName, this.indexName);
255
+ }
256
+
257
+ renderTypeScript(): string {
258
+ return `dropIndex(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.indexName)})`;
259
+ }
260
+ }
261
+
262
+ // ============================================================================
263
+ // Data transform
264
+ // ============================================================================
265
+
266
+ /**
267
+ * A planner-generated data-transform stub. The current default strategy
268
+ * (`nullabilityTighteningBackfillStrategy`) emits one of these with a
269
+ * backfill-flavored `id`/`label` when the policy allows `'data'` and the
270
+ * contract tightens a column's nullability, but the op itself is generic —
271
+ * any future strategy that needs a placeholder data step can construct one
272
+ * with its own id/label.
273
+ *
274
+ * `toOp()` always throws `PN-MIG-2001`: the planner cannot lower a stubbed
275
+ * transform to a runtime op — the user must edit the rendered
276
+ * `migration.ts` and re-emit.
277
+ */
278
+ export class DataTransformCall extends SqliteOpFactoryCallNode {
279
+ readonly factoryName = 'dataTransform' as const;
280
+ readonly operationClass = 'data' as const;
281
+ readonly id: string;
282
+ readonly label: string;
283
+ readonly tableName: string;
284
+ readonly columnName: string;
285
+
286
+ constructor(id: string, label: string, tableName: string, columnName: string) {
287
+ super();
288
+ this.id = id;
289
+ this.label = label;
290
+ this.tableName = tableName;
291
+ this.columnName = columnName;
292
+ this.freeze();
293
+ }
294
+
295
+ toOp(): Op {
296
+ throw errorUnfilledPlaceholder(this.label);
297
+ }
298
+
299
+ renderTypeScript(): string {
300
+ const slot = `${this.tableName}-${this.columnName}-backfill-sql`;
301
+ return [
302
+ 'dataTransform({',
303
+ ` id: ${jsonToTsSource(this.id)},`,
304
+ ` label: ${jsonToTsSource(this.label)},`,
305
+ ` table: ${jsonToTsSource(this.tableName)},`,
306
+ ` description: ${jsonToTsSource(`Backfill NULL ${this.columnName} values in ${this.tableName}`)},`,
307
+ ` run: () => placeholder(${jsonToTsSource(slot)}),`,
308
+ '})',
309
+ ].join('\n');
310
+ }
311
+
312
+ override importRequirements(): readonly ImportRequirement[] {
313
+ return [
314
+ { moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: this.factoryName },
315
+ { moduleSpecifier: TARGET_MIGRATION_MODULE, symbol: 'placeholder' },
316
+ ];
317
+ }
318
+ }
319
+
320
+ // ============================================================================
321
+ // Raw SQL
322
+ // ============================================================================
323
+
324
+ /**
325
+ * Laundered pre-built operation. Mirrors Postgres's `RawSqlCall`: wraps an
326
+ * already-materialized `SqlMigrationPlanOperation` (typically produced by a
327
+ * SQL-family helper or a codec lifecycle hook) so the planner can carry it
328
+ * alongside structured call IR. `toOp()` returns the stored op unchanged;
329
+ * `renderTypeScript()` emits `rawSql({...})` with the op serialized as a
330
+ * JSON literal — round-tripping requires every field on the op to be
331
+ * JSON-serializable (no closures).
332
+ */
333
+ export class RawSqlCall extends SqliteOpFactoryCallNode {
334
+ readonly factoryName = 'rawSql' as const;
335
+ readonly operationClass: MigrationOperationClass;
336
+ readonly label: string;
337
+ readonly op: Op;
338
+
339
+ constructor(op: Op) {
340
+ super();
341
+ this.op = op;
342
+ this.label = op.label;
343
+ this.operationClass = op.operationClass;
344
+ this.freeze();
345
+ }
346
+
347
+ toOp(): Op {
348
+ return this.op;
349
+ }
350
+
351
+ renderTypeScript(): string {
352
+ return `rawSql(${jsonToTsSource(this.op)})`;
353
+ }
354
+ }
355
+
356
+ // ============================================================================
357
+ // Union
358
+ // ============================================================================
359
+
360
+ export type SqliteOpFactoryCall =
361
+ | CreateTableCall
362
+ | DropTableCall
363
+ | RecreateTableCall
364
+ | AddColumnCall
365
+ | DropColumnCall
366
+ | CreateIndexCall
367
+ | DropIndexCall
368
+ | DataTransformCall
369
+ | RawSqlCall;
@@ -0,0 +1,62 @@
1
+ import { escapeLiteral, quoteIdentifier } from '../../sql-utils';
2
+ import { buildTargetDetails } from '../planner-target-details';
3
+ import { type Op, type SqliteColumnSpec, step } from './shared';
4
+
5
+ export function addColumn(tableName: string, column: SqliteColumnSpec): Op {
6
+ const parts = [
7
+ `ALTER TABLE ${quoteIdentifier(tableName)}`,
8
+ `ADD COLUMN ${quoteIdentifier(column.name)} ${column.typeSql}`,
9
+ column.defaultSql,
10
+ column.nullable ? '' : 'NOT NULL',
11
+ ].filter(Boolean);
12
+ const addSql = parts.join(' ');
13
+
14
+ return {
15
+ id: `column.${tableName}.${column.name}`,
16
+ label: `Add column ${column.name} on ${tableName}`,
17
+ summary: `Adds column ${column.name} on ${tableName}`,
18
+ operationClass: 'additive',
19
+ target: { id: 'sqlite', details: buildTargetDetails('column', column.name, tableName) },
20
+ precheck: [
21
+ step(
22
+ `ensure column "${column.name}" is missing`,
23
+ `SELECT COUNT(*) = 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(column.name)}'`,
24
+ ),
25
+ ],
26
+ execute: [step(`add column "${column.name}"`, addSql)],
27
+ postcheck: [
28
+ step(
29
+ `verify column "${column.name}" exists`,
30
+ `SELECT COUNT(*) > 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(column.name)}'`,
31
+ ),
32
+ ],
33
+ };
34
+ }
35
+
36
+ export function dropColumn(tableName: string, columnName: string): Op {
37
+ return {
38
+ id: `dropColumn.${tableName}.${columnName}`,
39
+ label: `Drop column ${columnName} on ${tableName}`,
40
+ summary: `Drops column ${columnName} on ${tableName} which is not in the contract`,
41
+ operationClass: 'destructive',
42
+ target: { id: 'sqlite', details: buildTargetDetails('column', columnName, tableName) },
43
+ precheck: [
44
+ step(
45
+ `ensure column "${columnName}" exists on "${tableName}"`,
46
+ `SELECT COUNT(*) > 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(columnName)}'`,
47
+ ),
48
+ ],
49
+ execute: [
50
+ step(
51
+ `drop column "${columnName}" from "${tableName}"`,
52
+ `ALTER TABLE ${quoteIdentifier(tableName)} DROP COLUMN ${quoteIdentifier(columnName)}`,
53
+ ),
54
+ ],
55
+ postcheck: [
56
+ step(
57
+ `verify column "${columnName}" is gone from "${tableName}"`,
58
+ `SELECT COUNT(*) = 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(columnName)}'`,
59
+ ),
60
+ ],
61
+ };
62
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * User-facing `dataTransform` factory for the SQLite migration authoring
3
+ * surface. Invoked directly inside a `migration.ts` file to supply a
4
+ * user-authored SQL statement that runs with operation class `'data'`.
5
+ *
6
+ * Typical use: the planner emits a `DataTransformCall` stub when a NOT NULL
7
+ * tightening requires a backfill. The rendered `migration.ts` exposes the
8
+ * backfill as a `placeholder("…")` slot the user fills in with an
9
+ * `UPDATE … WHERE col IS NULL` statement. The filled-in `dataTransform(...)`
10
+ * invocation returns a runnable operation the runner executes before the
11
+ * subsequent recreate-table op copies data into the tightened schema.
12
+ */
13
+
14
+ import { buildTargetDetails } from '../planner-target-details';
15
+ import { type Op, step } from './shared';
16
+
17
+ export interface DataTransformOptions {
18
+ /** Stable id used in the ledger / for runner idempotency tracking. */
19
+ readonly id: string;
20
+ /** Human-readable label surfaced in CLI output. */
21
+ readonly label: string;
22
+ /** Table the backfill targets; informs `target.details`. */
23
+ readonly table: string;
24
+ /**
25
+ * Short description of the step (shown by the runner on execute). The
26
+ * planner leaves this as `placeholder(...)` for users to replace.
27
+ */
28
+ readonly description: string;
29
+ /**
30
+ * Producer of the SQL string to execute. Invoked eagerly by
31
+ * `dataTransform(...)`, mirroring the Postgres factory — by the time the
32
+ * user calls this factory in `migration.ts`, the SQL is expected to be
33
+ * ready. Planner-emitted stubs that need to defer until the user fills
34
+ * in the SQL go through `DataTransformCall.renderTypeScript()` instead;
35
+ * this factory is only for the post-fill, runnable form.
36
+ */
37
+ readonly run: () => string;
38
+ }
39
+
40
+ export function dataTransform(opts: DataTransformOptions): Op {
41
+ return {
42
+ id: opts.id,
43
+ label: opts.label,
44
+ summary: opts.description,
45
+ operationClass: 'data',
46
+ target: { id: 'sqlite', details: buildTargetDetails('table', opts.table) },
47
+ precheck: [],
48
+ execute: [step(opts.description, opts.run())],
49
+ postcheck: [],
50
+ };
51
+ }
@@ -0,0 +1,52 @@
1
+ import { escapeLiteral } from '../../sql-utils';
2
+ import { buildCreateIndexSql, buildDropIndexSql } from '../planner-ddl-builders';
3
+ import { buildTargetDetails } from '../planner-target-details';
4
+ import { type Op, step } from './shared';
5
+
6
+ export function createIndex(tableName: string, indexName: string, columns: readonly string[]): Op {
7
+ return {
8
+ id: `index.${tableName}.${indexName}`,
9
+ label: `Create index ${indexName} on ${tableName}`,
10
+ summary: `Creates index ${indexName} on ${tableName}`,
11
+ operationClass: 'additive',
12
+ target: { id: 'sqlite', details: buildTargetDetails('index', indexName, tableName) },
13
+ precheck: [
14
+ step(
15
+ `ensure index "${indexName}" is missing`,
16
+ `SELECT COUNT(*) = 0 FROM sqlite_master WHERE type = 'index' AND name = '${escapeLiteral(indexName)}'`,
17
+ ),
18
+ ],
19
+ execute: [
20
+ step(`create index "${indexName}"`, buildCreateIndexSql(tableName, indexName, columns)),
21
+ ],
22
+ postcheck: [
23
+ step(
24
+ `verify index "${indexName}" exists`,
25
+ `SELECT COUNT(*) > 0 FROM sqlite_master WHERE type = 'index' AND name = '${escapeLiteral(indexName)}'`,
26
+ ),
27
+ ],
28
+ };
29
+ }
30
+
31
+ export function dropIndex(tableName: string, indexName: string): Op {
32
+ return {
33
+ id: `dropIndex.${tableName}.${indexName}`,
34
+ label: `Drop index ${indexName} on ${tableName}`,
35
+ summary: `Drops index ${indexName} on ${tableName} which is not in the contract`,
36
+ operationClass: 'destructive',
37
+ target: { id: 'sqlite', details: buildTargetDetails('index', indexName, tableName) },
38
+ precheck: [
39
+ step(
40
+ `ensure index "${indexName}" exists`,
41
+ `SELECT COUNT(*) > 0 FROM sqlite_master WHERE type = 'index' AND name = '${escapeLiteral(indexName)}'`,
42
+ ),
43
+ ],
44
+ execute: [step(`drop index "${indexName}"`, buildDropIndexSql(indexName))],
45
+ postcheck: [
46
+ step(
47
+ `verify index "${indexName}" is gone`,
48
+ `SELECT COUNT(*) = 0 FROM sqlite_master WHERE type = 'index' AND name = '${escapeLiteral(indexName)}'`,
49
+ ),
50
+ ],
51
+ };
52
+ }
@@ -0,0 +1,12 @@
1
+ import type { Op } from './shared';
2
+
3
+ /**
4
+ * Identity factory for an already-materialized
5
+ * `SqlMigrationPlanOperation<SqlitePlanTargetDetails>`. Mirrors the Postgres
6
+ * `rawSql` factory: the planner uses this to carry ops produced by SQL-family
7
+ * paths (codec lifecycle hooks, raw-SQL escape hatches) alongside structured
8
+ * call IR without reverse-engineering their shape.
9
+ */
10
+ export function rawSql(op: Op): Op {
11
+ return op;
12
+ }
@@ -0,0 +1,120 @@
1
+ import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
2
+ import type { ReferentialAction } from '@prisma-next/sql-contract/types';
3
+ import { quoteIdentifier } from '../../sql-utils';
4
+ import type { SqlitePlanTargetDetails } from '../planner-target-details';
5
+
6
+ export type Op = SqlMigrationPlanOperation<SqlitePlanTargetDetails>;
7
+
8
+ export function step(description: string, sql: string): { description: string; sql: string } {
9
+ return { description, sql };
10
+ }
11
+
12
+ /**
13
+ * Flat, fully-resolved column shape consumed by `createTable`, `addColumn`,
14
+ * and `recreateTable`. Codec / `typeRef` / default expansion happens at the
15
+ * call-construction site (in the issue-planner / strategies) so the
16
+ * operation factories deal only in pre-rendered SQL fragments — mirrors the
17
+ * Postgres `ColumnSpec` pattern.
18
+ *
19
+ * - `typeSql` is the column's DDL type token (e.g. `"INTEGER"`, `"TEXT"`).
20
+ * - `defaultSql` is the full `DEFAULT …` clause (or empty when there is no
21
+ * default and when the column is rendered as `INTEGER PRIMARY KEY
22
+ * AUTOINCREMENT`, since SQLite forbids a default on an autoincrement PK).
23
+ * - `inlineAutoincrementPrimaryKey` directs the renderer to emit
24
+ * `INTEGER PRIMARY KEY AUTOINCREMENT` inline and to skip the table-level
25
+ * primary-key constraint for this column. SQLite-specific: the column
26
+ * becomes an alias for `rowid` only when this exact form is used.
27
+ */
28
+ export interface SqliteColumnSpec {
29
+ readonly name: string;
30
+ readonly typeSql: string;
31
+ readonly defaultSql: string;
32
+ readonly nullable: boolean;
33
+ readonly inlineAutoincrementPrimaryKey?: boolean;
34
+ }
35
+
36
+ export interface SqlitePrimaryKeySpec {
37
+ readonly columns: readonly string[];
38
+ }
39
+
40
+ export interface SqliteUniqueSpec {
41
+ readonly columns: readonly string[];
42
+ readonly name?: string;
43
+ }
44
+
45
+ export interface SqliteForeignKeySpec {
46
+ readonly columns: readonly string[];
47
+ readonly references: {
48
+ readonly table: string;
49
+ readonly columns: readonly string[];
50
+ };
51
+ readonly name?: string;
52
+ readonly onDelete?: ReferentialAction;
53
+ readonly onUpdate?: ReferentialAction;
54
+ readonly constraint: boolean;
55
+ }
56
+
57
+ /**
58
+ * Flat shape of a contract table for DDL emission. Used by both
59
+ * `createTable` (additive) and `recreateTable` (widening/destructive).
60
+ */
61
+ export interface SqliteTableSpec {
62
+ readonly columns: readonly SqliteColumnSpec[];
63
+ readonly primaryKey?: SqlitePrimaryKeySpec;
64
+ readonly uniques?: readonly SqliteUniqueSpec[];
65
+ readonly foreignKeys?: readonly SqliteForeignKeySpec[];
66
+ }
67
+
68
+ /**
69
+ * Index recreation spec for `recreateTable`. Both declared indexes and
70
+ * FK-backing indexes flatten to the same shape; the planner dedupes by
71
+ * column-set before constructing the call.
72
+ */
73
+ export interface SqliteIndexSpec {
74
+ readonly name: string;
75
+ readonly columns: readonly string[];
76
+ }
77
+
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
+ /**
87
+ * Renders a single column's inline DDL fragment within a `CREATE TABLE`
88
+ * statement. Honours the `inlineAutoincrementPrimaryKey` flag — SQLite
89
+ * treats `INTEGER PRIMARY KEY AUTOINCREMENT` as a special form that aliases
90
+ * `rowid`, and the column must not carry a `DEFAULT` or repeat `NOT NULL`.
91
+ */
92
+ export function renderColumnDefinition(column: SqliteColumnSpec): string {
93
+ const parts: string[] = [quoteIdentifier(column.name), column.typeSql];
94
+ if (column.inlineAutoincrementPrimaryKey) {
95
+ parts.push('PRIMARY KEY AUTOINCREMENT');
96
+ } else {
97
+ if (column.defaultSql) parts.push(column.defaultSql);
98
+ if (!column.nullable) parts.push('NOT NULL');
99
+ }
100
+ return parts.join(' ');
101
+ }
102
+
103
+ /**
104
+ * Renders an inline FOREIGN KEY constraint clause for a `CREATE TABLE`
105
+ * body. Returns the empty string when `constraint` is false (the FK is
106
+ * tracked at the contract level for index-creation purposes only and must
107
+ * not produce DDL).
108
+ */
109
+ export function renderForeignKeyClause(fk: SqliteForeignKeySpec): string {
110
+ if (!fk.constraint) return '';
111
+ const name = fk.name ? `CONSTRAINT ${quoteIdentifier(fk.name)} ` : '';
112
+ let sql = `${name}FOREIGN KEY (${fk.columns.map(quoteIdentifier).join(', ')}) REFERENCES ${quoteIdentifier(fk.references.table)} (${fk.references.columns.map(quoteIdentifier).join(', ')})`;
113
+ if (fk.onDelete !== undefined) {
114
+ sql += ` ON DELETE ${REFERENTIAL_ACTION_SQL[fk.onDelete]}`;
115
+ }
116
+ if (fk.onUpdate !== undefined) {
117
+ sql += ` ON UPDATE ${REFERENTIAL_ACTION_SQL[fk.onUpdate]}`;
118
+ }
119
+ return sql;
120
+ }