@prisma-next/target-sqlite 0.13.0 → 0.14.0

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 (86) hide show
  1. package/dist/contract-free.d.mts +35 -2
  2. package/dist/contract-free.d.mts.map +1 -1
  3. package/dist/contract-free.mjs +3 -22
  4. package/dist/contract-free.mjs.map +1 -1
  5. package/dist/control.d.mts +5 -8
  6. package/dist/control.d.mts.map +1 -1
  7. package/dist/control.mjs +18 -20
  8. package/dist/control.mjs.map +1 -1
  9. package/dist/ddl-DrtjQMFK.mjs +68 -0
  10. package/dist/ddl-DrtjQMFK.mjs.map +1 -0
  11. package/dist/{descriptor-meta-Dxx2A6PT.mjs → descriptor-meta-DxmEeTJ-.mjs} +10 -3
  12. package/dist/descriptor-meta-DxmEeTJ-.mjs.map +1 -0
  13. package/dist/migration.d.mts +4 -46
  14. package/dist/migration.d.mts.map +1 -1
  15. package/dist/migration.mjs +4 -3
  16. package/dist/migration.mjs.map +1 -1
  17. package/dist/op-factory-call-DmdfD1yd.mjs +794 -0
  18. package/dist/op-factory-call-DmdfD1yd.mjs.map +1 -0
  19. package/dist/op-factory-call.d.mts +22 -12
  20. package/dist/op-factory-call.d.mts.map +1 -1
  21. package/dist/op-factory-call.mjs +1 -1
  22. package/dist/pack.mjs +1 -1
  23. package/dist/{planner-DSNDwQy9.mjs → planner-Ciq8p_dL.mjs} +80 -12
  24. package/dist/planner-Ciq8p_dL.mjs.map +1 -0
  25. package/dist/{planner-produced-sqlite-migration-DowV_vHw.mjs → planner-produced-sqlite-migration-0xPEm3R1.mjs} +9 -5
  26. package/dist/planner-produced-sqlite-migration-0xPEm3R1.mjs.map +1 -0
  27. package/dist/{planner-produced-sqlite-migration-C1yqJAiM.d.mts → planner-produced-sqlite-migration-CpgsY-M9.d.mts} +5 -4
  28. package/dist/planner-produced-sqlite-migration-CpgsY-M9.d.mts.map +1 -0
  29. package/dist/planner-produced-sqlite-migration.d.mts +1 -1
  30. package/dist/planner-produced-sqlite-migration.mjs +1 -1
  31. package/dist/planner.d.mts +5 -2
  32. package/dist/planner.d.mts.map +1 -1
  33. package/dist/planner.mjs +1 -1
  34. package/dist/render-ops-BDW2tUeR.mjs +22 -0
  35. package/dist/render-ops-BDW2tUeR.mjs.map +1 -0
  36. package/dist/render-ops.d.mts +2 -1
  37. package/dist/render-ops.d.mts.map +1 -1
  38. package/dist/render-ops.mjs +1 -1
  39. package/dist/runtime.d.mts +1 -0
  40. package/dist/runtime.d.mts.map +1 -1
  41. package/dist/runtime.mjs +2 -2
  42. package/dist/shared-Dhc8mLK1.d.mts.map +1 -1
  43. package/dist/{sqlite-contract-serializer-jcRu8aHh.mjs → sqlite-contract-serializer--iaDgC8e.mjs} +17 -6
  44. package/dist/sqlite-contract-serializer--iaDgC8e.mjs.map +1 -0
  45. package/dist/sqlite-migration-A0rwqPOG.mjs +92 -0
  46. package/dist/sqlite-migration-A0rwqPOG.mjs.map +1 -0
  47. package/dist/sqlite-migration-DVfhQwN_.d.mts +75 -0
  48. package/dist/sqlite-migration-DVfhQwN_.d.mts.map +1 -0
  49. package/package.json +18 -18
  50. package/src/contract-free/checks.ts +75 -0
  51. package/src/core/control-target.ts +4 -4
  52. package/src/core/errors.ts +28 -0
  53. package/src/core/migrations/issue-planner.ts +151 -8
  54. package/src/core/migrations/op-factory-call.ts +332 -45
  55. package/src/core/migrations/operations/columns.ts +32 -26
  56. package/src/core/migrations/operations/indexes.ts +31 -27
  57. package/src/core/migrations/operations/shared.ts +11 -3
  58. package/src/core/migrations/operations/tables.ts +39 -37
  59. package/src/core/migrations/planner-ddl-builders.ts +7 -16
  60. package/src/core/migrations/planner-produced-sqlite-migration.ts +8 -2
  61. package/src/core/migrations/planner-strategies.ts +3 -3
  62. package/src/core/migrations/planner.ts +14 -2
  63. package/src/core/migrations/render-ops.ts +37 -9
  64. package/src/core/migrations/runner.ts +16 -12
  65. package/src/core/migrations/sqlite-migration.ts +119 -1
  66. package/src/core/sqlite-contract-serializer.ts +5 -0
  67. package/src/core/sqlite-unbound-database.ts +30 -54
  68. package/src/exports/contract-free.ts +8 -0
  69. package/src/exports/migration.ts +8 -3
  70. package/dist/descriptor-meta-Dxx2A6PT.mjs.map +0 -1
  71. package/dist/descriptor-meta-runtime-BkXK3OjD.mjs +0 -12
  72. package/dist/descriptor-meta-runtime-BkXK3OjD.mjs.map +0 -1
  73. package/dist/op-factory-call-DymqdXQW.mjs +0 -279
  74. package/dist/op-factory-call-DymqdXQW.mjs.map +0 -1
  75. package/dist/planner-DSNDwQy9.mjs.map +0 -1
  76. package/dist/planner-produced-sqlite-migration-C1yqJAiM.d.mts.map +0 -1
  77. package/dist/planner-produced-sqlite-migration-DowV_vHw.mjs.map +0 -1
  78. package/dist/render-ops-CFRbJ3Yb.mjs +0 -8
  79. package/dist/render-ops-CFRbJ3Yb.mjs.map +0 -1
  80. package/dist/sqlite-contract-serializer-jcRu8aHh.mjs.map +0 -1
  81. package/dist/sqlite-migration-CUqgmzQH.mjs +0 -16
  82. package/dist/sqlite-migration-CUqgmzQH.mjs.map +0 -1
  83. package/dist/sqlite-migration-D4XGYzgQ.d.mts +0 -17
  84. package/dist/sqlite-migration-D4XGYzgQ.d.mts.map +0 -1
  85. package/dist/tables-CjB7vXCr.mjs +0 -412
  86. package/dist/tables-CjB7vXCr.mjs.map +0 -1
@@ -1,13 +1,21 @@
1
- import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
1
+ import type {
2
+ SqlMigrationPlanOperation,
3
+ SqlMigrationPlanOperationStep,
4
+ } from '@prisma-next/family-sql/control';
2
5
  import { REFERENTIAL_ACTION_SQL } from '@prisma-next/sql-contract/referential-action-sql';
3
6
  import type { ReferentialAction } from '@prisma-next/sql-contract/types';
7
+ import { ifDefined } from '@prisma-next/utils/defined';
4
8
  import { quoteIdentifier } from '../../sql-utils';
5
9
  import type { SqlitePlanTargetDetails } from '../planner-target-details';
6
10
 
7
11
  export type Op = SqlMigrationPlanOperation<SqlitePlanTargetDetails>;
8
12
 
9
- export function step(description: string, sql: string): { description: string; sql: string } {
10
- return { description, sql };
13
+ export function step(
14
+ description: string,
15
+ sql: string,
16
+ params?: readonly unknown[],
17
+ ): SqlMigrationPlanOperationStep {
18
+ return { description, sql, ...ifDefined('params', params) };
11
19
  }
12
20
 
13
21
  /**
@@ -1,5 +1,7 @@
1
1
  import type { MigrationOperationClass } from '@prisma-next/family-sql/control';
2
+ import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
2
3
  import type { SchemaIssue } from '@prisma-next/framework-components/control';
4
+ import { tableExistsAst } from '../../../contract-free/checks';
3
5
  import { stripOuterParens } from '../../default-normalizer';
4
6
  import { escapeLiteral, quoteIdentifier } from '../../sql-utils';
5
7
  import { buildCreateIndexSql } from '../planner-ddl-builders';
@@ -13,6 +15,18 @@ import {
13
15
  step,
14
16
  } from './shared';
15
17
 
18
+ type CheckStep = { sql: string; params?: readonly unknown[] };
19
+
20
+ async function tableExistsSteps(
21
+ lowerer: ExecuteRequestLowerer,
22
+ tableName: string,
23
+ ): Promise<{ present: CheckStep; absent: CheckStep }> {
24
+ const checks = tableExistsAst(tableName);
25
+ const present = await lowerer.lowerToExecuteRequest(checks.tablePresent());
26
+ const absent = await lowerer.lowerToExecuteRequest(checks.tableAbsent());
27
+ return { present, absent };
28
+ }
29
+
16
30
  /**
17
31
  * Renders the body of a `CREATE TABLE <name> ( … )` statement from a flat
18
32
  * `SqliteTableSpec`. SQLite's `INTEGER PRIMARY KEY AUTOINCREMENT` form is
@@ -42,49 +56,35 @@ function renderCreateTableSql(tableName: string, spec: SqliteTableSpec): string
42
56
  return `CREATE TABLE ${quoteIdentifier(tableName)} (\n ${allDefs.join(',\n ')}\n)`;
43
57
  }
44
58
 
45
- export function createTable(tableName: string, spec: SqliteTableSpec): Op {
59
+ export async function createTable(
60
+ tableName: string,
61
+ spec: SqliteTableSpec,
62
+ lowerer: ExecuteRequestLowerer,
63
+ ): Promise<Op> {
64
+ const { present, absent } = await tableExistsSteps(lowerer, tableName);
46
65
  return {
47
66
  id: `table.${tableName}`,
48
67
  label: `Create table ${tableName}`,
49
68
  summary: `Creates table ${tableName} with required columns`,
50
69
  operationClass: 'additive',
51
70
  target: { id: 'sqlite', details: buildTargetDetails('table', tableName) },
52
- precheck: [
53
- step(
54
- `ensure table "${tableName}" does not exist`,
55
- `SELECT COUNT(*) = 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapeLiteral(tableName)}'`,
56
- ),
57
- ],
71
+ precheck: [step(`ensure table "${tableName}" does not exist`, absent.sql, absent.params)],
58
72
  execute: [step(`create table "${tableName}"`, renderCreateTableSql(tableName, spec))],
59
- postcheck: [
60
- step(
61
- `verify table "${tableName}" exists`,
62
- `SELECT COUNT(*) > 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapeLiteral(tableName)}'`,
63
- ),
64
- ],
73
+ postcheck: [step(`verify table "${tableName}" exists`, present.sql, present.params)],
65
74
  };
66
75
  }
67
76
 
68
- export function dropTable(tableName: string): Op {
77
+ export async function dropTable(tableName: string, lowerer: ExecuteRequestLowerer): Promise<Op> {
78
+ const { present, absent } = await tableExistsSteps(lowerer, tableName);
69
79
  return {
70
80
  id: `dropTable.${tableName}`,
71
81
  label: `Drop table ${tableName}`,
72
82
  summary: `Drops table ${tableName} which is not in the contract`,
73
83
  operationClass: 'destructive',
74
84
  target: { id: 'sqlite', details: buildTargetDetails('table', tableName) },
75
- precheck: [
76
- step(
77
- `ensure table "${tableName}" exists`,
78
- `SELECT COUNT(*) > 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapeLiteral(tableName)}'`,
79
- ),
80
- ],
85
+ precheck: [step(`ensure table "${tableName}" exists`, present.sql, present.params)],
81
86
  execute: [step(`drop table "${tableName}"`, `DROP TABLE ${quoteIdentifier(tableName)}`)],
82
- postcheck: [
83
- step(
84
- `verify table "${tableName}" is gone`,
85
- `SELECT COUNT(*) = 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapeLiteral(tableName)}'`,
86
- ),
87
- ],
87
+ postcheck: [step(`verify table "${tableName}" is gone`, absent.sql, absent.params)],
88
88
  };
89
89
  }
90
90
 
@@ -115,7 +115,10 @@ export interface RecreateTableArgs {
115
115
  readonly operationClass: MigrationOperationClass;
116
116
  }
117
117
 
118
- export function recreateTable(args: RecreateTableArgs): Op {
118
+ export async function recreateTable(
119
+ args: RecreateTableArgs,
120
+ lowerer: ExecuteRequestLowerer,
121
+ ): Promise<Op> {
119
122
  const {
120
123
  tableName,
121
124
  contractTable,
@@ -149,6 +152,9 @@ export function recreateTable(args: RecreateTableArgs): Op {
149
152
  ]
150
153
  : [];
151
154
 
155
+ const tableSteps = await tableExistsSteps(lowerer, tableName);
156
+ const tempSteps = await tableExistsSteps(lowerer, tempName);
157
+
152
158
  return {
153
159
  id: `recreateTable.${tableName}`,
154
160
  label: `Recreate table ${tableName}`,
@@ -156,13 +162,11 @@ export function recreateTable(args: RecreateTableArgs): Op {
156
162
  operationClass,
157
163
  target: { id: 'sqlite', details: buildTargetDetails('table', tableName) },
158
164
  precheck: [
159
- step(
160
- `ensure table "${tableName}" exists`,
161
- `SELECT COUNT(*) > 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapeLiteral(tableName)}'`,
162
- ),
165
+ step(`ensure table "${tableName}" exists`, tableSteps.present.sql, tableSteps.present.params),
163
166
  step(
164
167
  `ensure temp table "${tempName}" does not exist`,
165
- `SELECT COUNT(*) = 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapeLiteral(tempName)}'`,
168
+ tempSteps.absent.sql,
169
+ tempSteps.absent.params,
166
170
  ),
167
171
  ],
168
172
  execute: [
@@ -179,13 +183,11 @@ export function recreateTable(args: RecreateTableArgs): Op {
179
183
  ...indexStatements,
180
184
  ],
181
185
  postcheck: [
182
- step(
183
- `verify table "${tableName}" exists`,
184
- `SELECT COUNT(*) > 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapeLiteral(tableName)}'`,
185
- ),
186
+ step(`verify table "${tableName}" exists`, tableSteps.present.sql, tableSteps.present.params),
186
187
  step(
187
188
  `verify temp table "${tempName}" is gone`,
188
- `SELECT COUNT(*) = 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapeLiteral(tempName)}'`,
189
+ tempSteps.absent.sql,
190
+ tempSteps.absent.params,
189
191
  ),
190
192
  ...postchecks,
191
193
  ],
@@ -8,12 +8,10 @@
8
8
  * see `StorageColumn` or `storageTypes`.
9
9
  */
10
10
 
11
- import {
12
- isPostgresEnumStorageEntry,
13
- type PostgresEnumStorageEntry,
14
- type StorageColumn,
15
- type StorageTable,
16
- type StorageTypeInstance,
11
+ import type {
12
+ StorageColumn,
13
+ StorageTable,
14
+ StorageTypeInstance,
17
15
  } from '@prisma-next/sql-contract/types';
18
16
  import { escapeLiteral, quoteIdentifier } from '../sql-utils';
19
17
 
@@ -46,7 +44,7 @@ function assertSafeDefaultExpression(expression: string): void {
46
44
  */
47
45
  export function buildColumnTypeSql(
48
46
  column: StorageColumn,
49
- storageTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = {},
47
+ storageTypes: Record<string, StorageTypeInstance> = {},
50
48
  ): string {
51
49
  const resolved = resolveColumnTypeMetadata(column, storageTypes);
52
50
  assertSafeNativeType(resolved.nativeType);
@@ -123,9 +121,9 @@ export function isInlineAutoincrementPrimaryKey(table: StorageTable, columnName:
123
121
 
124
122
  type ResolvedColumnTypeMetadata = Pick<StorageColumn, 'nativeType' | 'codecId' | 'typeParams'>;
125
123
 
126
- function resolveColumnTypeMetadata(
124
+ export function resolveColumnTypeMetadata(
127
125
  column: StorageColumn,
128
- storageTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
126
+ storageTypes: Record<string, StorageTypeInstance>,
129
127
  ): ResolvedColumnTypeMetadata {
130
128
  if (!column.typeRef) {
131
129
  return column;
@@ -136,13 +134,6 @@ function resolveColumnTypeMetadata(
136
134
  `Storage type "${column.typeRef}" referenced by column is not defined in storage.types.`,
137
135
  );
138
136
  }
139
- if (isPostgresEnumStorageEntry(referencedType)) {
140
- return {
141
- codecId: referencedType.codecId,
142
- nativeType: referencedType.nativeType,
143
- typeParams: { values: referencedType.values } as Record<string, unknown>,
144
- };
145
- }
146
137
  return {
147
138
  codecId: referencedType.codecId,
148
139
  nativeType: referencedType.nativeType,
@@ -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 {
@@ -23,7 +23,6 @@ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-comp
23
23
  import type { SchemaIssue } from '@prisma-next/framework-components/control';
24
24
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
25
25
  import type {
26
- PostgresEnumStorageEntry,
27
26
  SqlStorage,
28
27
  StorageTable,
29
28
  StorageTypeInstance,
@@ -39,7 +38,7 @@ export interface StrategyContext {
39
38
  readonly toContract: Contract<SqlStorage>;
40
39
  readonly fromContract: Contract<SqlStorage> | null;
41
40
  readonly codecHooks: ReadonlyMap<string, CodecControlHooks>;
42
- readonly storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>;
41
+ readonly storageTypes: Readonly<Record<string, StorageTypeInstance>>;
43
42
  readonly schema: SqlSchemaIR;
44
43
  readonly policy: MigrationOperationPolicy;
45
44
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
@@ -61,7 +60,8 @@ export function tableAt(
61
60
  namespaceId: string,
62
61
  tableName: string,
63
62
  ): StorageTable | undefined {
64
- return storage.namespaces[namespaceId]?.entries.table[tableName] as StorageTable | undefined;
63
+ const ns = storage.namespaces[namespaceId];
64
+ return ns !== undefined ? ns.entries.table?.[tableName] : undefined;
65
65
  }
66
66
 
67
67
  /**
@@ -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,13 +1,131 @@
1
+ import type {
2
+ MigrationOperationClass,
3
+ SqlMigrationPlanOperation,
4
+ } from '@prisma-next/family-sql/control';
5
+ import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
1
6
  import { Migration as SqlMigration } from '@prisma-next/family-sql/migration';
7
+ import type { ControlStack } from '@prisma-next/framework-components/control';
8
+ import type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
9
+ import { blindCast } from '@prisma-next/utils/casts';
10
+ import { errorSqliteMigrationStackMissing } from '../errors';
11
+ import {
12
+ AddColumnCall,
13
+ CreateIndexCall,
14
+ CreateTableCall,
15
+ DropColumnCall,
16
+ DropIndexCall,
17
+ DropTableCall,
18
+ RecreateTableCall,
19
+ } from './op-factory-call';
20
+ import type { SqliteColumnSpec, SqliteIndexSpec, SqliteTableSpec } from './operations/shared';
2
21
  import type { SqlitePlanTargetDetails } from './planner-target-details';
3
22
 
23
+ type Op = SqlMigrationPlanOperation<SqlitePlanTargetDetails>;
24
+
4
25
  /**
5
26
  * Target-owned base class for SQLite migrations. Fixes the `SqlMigration`
6
27
  * generic to `SqlitePlanTargetDetails` and the abstract `targetId` to the
7
28
  * SQLite literal, so both user-authored migrations and renderer-generated
8
29
  * scaffolds can extend `SqliteMigration` directly without redeclaring
9
30
  * target-local identity.
31
+ *
32
+ * The constructor materializes a single SQLite `SqlControlAdapter` from
33
+ * `stack.adapter.create(stack)` and stores it; the protected instance methods
34
+ * forward to the corresponding `*Call` with that stored adapter, so user
35
+ * migrations can write `this.createTable({...})` without threading the adapter
36
+ * through every call.
10
37
  */
11
- export abstract class SqliteMigration extends SqlMigration<SqlitePlanTargetDetails> {
38
+ export abstract class SqliteMigration extends SqlMigration<SqlitePlanTargetDetails, 'sqlite'> {
12
39
  readonly targetId = 'sqlite' as const;
40
+
41
+ /**
42
+ * Materialized SQLite control adapter, created once per migration
43
+ * instance from the injected stack. `undefined` only when the migration
44
+ * was instantiated without a stack (test fixtures); the operation methods
45
+ * throw in that case to surface the misuse.
46
+ */
47
+ protected readonly controlAdapter: SqlControlAdapter<'sqlite'> | undefined;
48
+
49
+ constructor(stack?: ControlStack<'sql', 'sqlite'>) {
50
+ super(stack);
51
+ this.controlAdapter = stack?.adapter
52
+ ? blindCast<
53
+ SqlControlAdapter<'sqlite'>,
54
+ 'The SQLite descriptor create() returns SqlControlAdapter<sqlite>; typed as wider ControlAdapterInstance at the framework boundary'
55
+ >(stack.adapter.create(stack))
56
+ : undefined;
57
+ }
58
+
59
+ protected createTable(options: {
60
+ readonly table: string;
61
+ readonly ifNotExists?: boolean;
62
+ readonly columns: readonly DdlColumn[];
63
+ readonly constraints?: readonly DdlTableConstraint[];
64
+ }): Promise<Op> {
65
+ if (!this.controlAdapter) {
66
+ throw errorSqliteMigrationStackMissing();
67
+ }
68
+ return new CreateTableCall(options.table, options.columns, options.constraints).toOp(
69
+ this.controlAdapter,
70
+ );
71
+ }
72
+
73
+ protected dropTable(options: { readonly table: string }): Promise<Op> {
74
+ if (!this.controlAdapter) {
75
+ throw errorSqliteMigrationStackMissing();
76
+ }
77
+ return new DropTableCall(options.table).toOp(this.controlAdapter);
78
+ }
79
+
80
+ protected addColumn(options: {
81
+ readonly table: string;
82
+ readonly column: SqliteColumnSpec;
83
+ }): Promise<Op> {
84
+ if (!this.controlAdapter) {
85
+ throw errorSqliteMigrationStackMissing();
86
+ }
87
+ return new AddColumnCall(options.table, options.column).toOp(this.controlAdapter);
88
+ }
89
+
90
+ protected dropColumn(options: { readonly table: string; readonly column: string }): Promise<Op> {
91
+ if (!this.controlAdapter) {
92
+ throw errorSqliteMigrationStackMissing();
93
+ }
94
+ return new DropColumnCall(options.table, options.column).toOp(this.controlAdapter);
95
+ }
96
+
97
+ protected createIndex(options: {
98
+ readonly table: string;
99
+ readonly index: string;
100
+ readonly columns: readonly string[];
101
+ }): Promise<Op> {
102
+ if (!this.controlAdapter) {
103
+ throw errorSqliteMigrationStackMissing();
104
+ }
105
+ return new CreateIndexCall(options.table, options.index, options.columns).toOp(
106
+ this.controlAdapter,
107
+ );
108
+ }
109
+
110
+ protected dropIndex(options: { readonly table: string; readonly index: string }): Promise<Op> {
111
+ if (!this.controlAdapter) {
112
+ throw errorSqliteMigrationStackMissing();
113
+ }
114
+ return new DropIndexCall(options.table, options.index).toOp(this.controlAdapter);
115
+ }
116
+
117
+ protected recreateTable(options: {
118
+ readonly tableName: string;
119
+ readonly contractTable: SqliteTableSpec;
120
+ readonly schemaColumnNames: readonly string[];
121
+ readonly indexes: readonly SqliteIndexSpec[];
122
+ readonly summary: string;
123
+ readonly postchecks: readonly { readonly description: string; readonly sql: string }[];
124
+ readonly operationClass: MigrationOperationClass;
125
+ }): Promise<Op> {
126
+ if (!this.controlAdapter) {
127
+ throw errorSqliteMigrationStackMissing();
128
+ }
129
+ return new RecreateTableCall(options).toOp(this.controlAdapter);
130
+ }
13
131
  }
@@ -3,6 +3,7 @@ import { SqlContractSerializerBase } from '@prisma-next/family-sql/ir';
3
3
  import { type Namespace, NamespaceBase } from '@prisma-next/framework-components/ir';
4
4
  import type { SqlNamespaceTablesInput, SqlStorage } from '@prisma-next/sql-contract/types';
5
5
  import { blindCast } from '@prisma-next/utils/casts';
6
+ import { sqliteTargetDescriptorMeta } from './descriptor-meta';
6
7
  import { buildSqliteNamespace } from './sqlite-unbound-database';
7
8
 
8
9
  /**
@@ -16,6 +17,10 @@ export class SqliteContractSerializer extends SqlContractSerializerBase<Contract
16
17
  super(new Map());
17
18
  }
18
19
 
20
+ protected override get defaultNamespaceId(): string {
21
+ return sqliteTargetDescriptorMeta.defaultNamespaceId;
22
+ }
23
+
19
24
  protected override hydrateSqlNamespaceEntry(
20
25
  nsId: string,
21
26
  raw: Namespace | Record<string, unknown>,