@prisma-next/adapter-sqlite 0.5.0-dev.6 → 0.5.0-dev.60

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 (45) hide show
  1. package/README.md +2 -2
  2. package/dist/{adapter-BG7_o_3h.d.mts → adapter-CjuvmCVF.d.mts} +10 -2
  3. package/dist/adapter-CjuvmCVF.d.mts.map +1 -0
  4. package/dist/{adapter-lwcslPF8.mjs → adapter-DjLhS34S.mjs} +56 -50
  5. package/dist/adapter-DjLhS34S.mjs.map +1 -0
  6. package/dist/adapter.d.mts +2 -2
  7. package/dist/adapter.mjs +1 -2
  8. package/dist/codec-types.d.mts +2 -40
  9. package/dist/codec-types.mjs +1 -1
  10. package/dist/column-types.d.mts +1 -5
  11. package/dist/column-types.d.mts.map +1 -1
  12. package/dist/column-types.mjs +2 -6
  13. package/dist/column-types.mjs.map +1 -1
  14. package/dist/control.d.mts +35 -3
  15. package/dist/control.d.mts.map +1 -1
  16. package/dist/control.mjs +415 -8
  17. package/dist/control.mjs.map +1 -1
  18. package/dist/{descriptor-meta-Bg-c1LmL.mjs → descriptor-meta-DYT9Gt_F.mjs} +1 -1
  19. package/dist/{descriptor-meta-Bg-c1LmL.mjs.map → descriptor-meta-DYT9Gt_F.mjs.map} +1 -1
  20. package/dist/runtime.d.mts +3 -3
  21. package/dist/runtime.d.mts.map +1 -1
  22. package/dist/runtime.mjs +19 -4
  23. package/dist/runtime.mjs.map +1 -1
  24. package/dist/{types-gAqc4ucF.d.mts → types-BFRXGAgd.d.mts} +1 -1
  25. package/dist/{types-gAqc4ucF.d.mts.map → types-BFRXGAgd.d.mts.map} +1 -1
  26. package/dist/types.d.mts +1 -1
  27. package/package.json +21 -17
  28. package/src/core/adapter.ts +65 -29
  29. package/src/core/column-types.ts +1 -7
  30. package/src/core/control-adapter.ts +279 -15
  31. package/src/core/control-mutation-defaults.ts +358 -0
  32. package/src/core/runtime-adapter.ts +20 -2
  33. package/src/exports/codec-types.ts +8 -6
  34. package/src/exports/column-types.ts +0 -1
  35. package/src/exports/control.ts +45 -1
  36. package/dist/adapter-BG7_o_3h.d.mts.map +0 -1
  37. package/dist/adapter-lwcslPF8.mjs.map +0 -1
  38. package/dist/codec-ids-o_Z8i4nt.mjs +0 -15
  39. package/dist/codec-ids-o_Z8i4nt.mjs.map +0 -1
  40. package/dist/codec-types.d.mts.map +0 -1
  41. package/dist/codecs-Bhz0W5Xm.mjs +0 -87
  42. package/dist/codecs-Bhz0W5Xm.mjs.map +0 -1
  43. package/src/core/codec-ids.ts +0 -14
  44. package/src/core/codecs.ts +0 -108
  45. package/src/core/sql-utils.ts +0 -35
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@prisma-next/adapter-sqlite",
3
- "version": "0.5.0-dev.6",
3
+ "version": "0.5.0-dev.60",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "sideEffects": false,
6
7
  "files": [
@@ -9,28 +10,31 @@
9
10
  ],
10
11
  "dependencies": {
11
12
  "arktype": "^2.0.0",
12
- "@prisma-next/contract": "0.5.0-dev.6",
13
- "@prisma-next/framework-components": "0.5.0-dev.6",
14
- "@prisma-next/family-sql": "0.5.0-dev.6",
15
- "@prisma-next/ids": "0.5.0-dev.6",
16
- "@prisma-next/sql-contract": "0.5.0-dev.6",
17
- "@prisma-next/contract-authoring": "0.5.0-dev.6",
18
- "@prisma-next/sql-contract-psl": "0.5.0-dev.6",
19
- "@prisma-next/sql-contract-ts": "0.5.0-dev.6",
20
- "@prisma-next/sql-operations": "0.5.0-dev.6",
21
- "@prisma-next/sql-relational-core": "0.5.0-dev.6",
22
- "@prisma-next/sql-runtime": "0.5.0-dev.6",
23
- "@prisma-next/cli": "0.5.0-dev.6",
24
- "@prisma-next/sql-schema-ir": "0.5.0-dev.6",
25
- "@prisma-next/utils": "0.5.0-dev.6"
13
+ "@prisma-next/cli": "0.5.0-dev.60",
14
+ "@prisma-next/contract": "0.5.0-dev.60",
15
+ "@prisma-next/contract-authoring": "0.5.0-dev.60",
16
+ "@prisma-next/framework-components": "0.5.0-dev.60",
17
+ "@prisma-next/ids": "0.5.0-dev.60",
18
+ "@prisma-next/sql-contract-ts": "0.5.0-dev.60",
19
+ "@prisma-next/sql-operations": "0.5.0-dev.60",
20
+ "@prisma-next/sql-relational-core": "0.5.0-dev.60",
21
+ "@prisma-next/sql-contract": "0.5.0-dev.60",
22
+ "@prisma-next/sql-contract-psl": "0.5.0-dev.60",
23
+ "@prisma-next/family-sql": "0.5.0-dev.60",
24
+ "@prisma-next/sql-runtime": "0.5.0-dev.60",
25
+ "@prisma-next/sql-schema-ir": "0.5.0-dev.60",
26
+ "@prisma-next/target-sqlite": "0.5.0-dev.60",
27
+ "@prisma-next/utils": "0.5.0-dev.60"
26
28
  },
27
29
  "devDependencies": {
30
+ "pathe": "^2.0.3",
28
31
  "tsdown": "0.18.4",
29
32
  "typescript": "5.9.3",
30
33
  "vitest": "4.0.17",
31
- "@prisma-next/tsconfig": "0.0.0",
34
+ "@prisma-next/driver-sqlite": "0.5.0-dev.60",
32
35
  "@prisma-next/test-utils": "0.0.1",
33
- "@prisma-next/tsdown": "0.0.0"
36
+ "@prisma-next/tsdown": "0.0.0",
37
+ "@prisma-next/tsconfig": "0.0.0"
34
38
  },
35
39
  "exports": {
36
40
  "./adapter": "./dist/adapter.mjs",
@@ -27,8 +27,9 @@ import {
27
27
  type SubqueryExpr,
28
28
  type UpdateAst,
29
29
  } from '@prisma-next/sql-relational-core/ast';
30
- import { codecDefinitions } from './codecs';
31
- import { escapeLiteral, quoteIdentifier } from './sql-utils';
30
+ import { parseContractMarkerRow } from '@prisma-next/sql-runtime';
31
+ import { codecDefinitions } from '@prisma-next/target-sqlite/codecs';
32
+ import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-sqlite/sql-utils';
32
33
  import type { SqliteAdapterOptions, SqliteContract, SqliteLoweredStatement } from './types';
33
34
 
34
35
  const defaultCapabilities = Object.freeze({
@@ -62,9 +63,20 @@ class SqliteAdapterImpl implements Adapter<AnyQueryAst, SqliteContract, SqliteLo
62
63
  capabilities: defaultCapabilities,
63
64
  codecs: () => this.codecRegistry,
64
65
  readMarkerStatement: () => ({
65
- sql: 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta from prisma_contract_marker where id = ?',
66
+ sql: 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta, invariants from _prisma_marker where id = ?',
66
67
  params: [1],
67
68
  }),
69
+ // SQLite stores arrays as JSON-encoded TEXT (no native array type),
70
+ // so the driver returns `invariants` as a string. Decode before
71
+ // delegating to the shared row schema, which expects `string[]`.
72
+ parseMarkerRow: (row: unknown) => {
73
+ const raw = row as Record<string, unknown>;
74
+ const invariants =
75
+ typeof raw['invariants'] === 'string'
76
+ ? (JSON.parse(raw['invariants']) as unknown)
77
+ : raw['invariants'];
78
+ return parseContractMarkerRow({ ...raw, invariants });
79
+ },
68
80
  });
69
81
  }
70
82
 
@@ -73,34 +85,48 @@ class SqliteAdapterImpl implements Adapter<AnyQueryAst, SqliteContract, SqliteLo
73
85
  }
74
86
 
75
87
  lower(ast: AnyQueryAst, context: LowererContext<SqliteContract>): SqliteLoweredStatement {
76
- const collectedParamRefs = ast.collectParamRefs();
77
- const params: unknown[] = [];
78
- for (const ref of collectedParamRefs) {
79
- params.push(ref.value);
80
- }
88
+ return renderLoweredSql(ast, context.contract);
89
+ }
90
+ }
81
91
 
82
- let sql: string;
92
+ /**
93
+ * Lower a SQL query AST into a SQLite-flavored `{ sql, params }` payload.
94
+ *
95
+ * Shared between the runtime adapter (`SqliteAdapterImpl.lower`) and the
96
+ * control adapter (`SqliteControlAdapter.lower`) so both produce
97
+ * byte-identical SQL for the same AST and contract.
98
+ */
99
+ export function renderLoweredSql(
100
+ ast: AnyQueryAst,
101
+ contract: SqliteContract,
102
+ ): SqliteLoweredStatement {
103
+ const collectedParamRefs = ast.collectParamRefs();
104
+ const params: unknown[] = [];
105
+ for (const ref of collectedParamRefs) {
106
+ params.push(ref.value);
107
+ }
83
108
 
84
- const node = ast;
85
- switch (node.kind) {
86
- case 'select':
87
- sql = renderSelect(node, context.contract);
88
- break;
89
- case 'insert':
90
- sql = renderInsert(node);
91
- break;
92
- case 'update':
93
- sql = renderUpdate(node, context.contract);
94
- break;
95
- case 'delete':
96
- sql = renderDelete(node);
97
- break;
98
- default:
99
- throw new Error(`Unsupported AST node kind: ${(node as { kind: string }).kind}`);
100
- }
109
+ let sql: string;
101
110
 
102
- return Object.freeze({ sql, params });
111
+ const node = ast;
112
+ switch (node.kind) {
113
+ case 'select':
114
+ sql = renderSelect(node, contract);
115
+ break;
116
+ case 'insert':
117
+ sql = renderInsert(node);
118
+ break;
119
+ case 'update':
120
+ sql = renderUpdate(node, contract);
121
+ break;
122
+ case 'delete':
123
+ sql = renderDelete(node);
124
+ break;
125
+ default:
126
+ throw new Error(`Unsupported AST node kind: ${(node as { kind: string }).kind}`);
103
127
  }
128
+
129
+ return Object.freeze({ sql, params });
104
130
  }
105
131
 
106
132
  function renderSelect(ast: SelectAst, contract?: SqliteContract): string {
@@ -506,11 +532,21 @@ function renderDelete(ast: DeleteAst): string {
506
532
  return `DELETE FROM ${table}${whereClause}${returningClause}`;
507
533
  }
508
534
 
509
- function renderReturning(returning: ReadonlyArray<ColumnRef> | undefined): string {
535
+ function renderReturning(returning: ReadonlyArray<ProjectionItem> | undefined): string {
510
536
  if (!returning?.length) {
511
537
  return '';
512
538
  }
513
- return ` RETURNING ${returning.map((col) => `${quoteIdentifier(col.table)}.${quoteIdentifier(col.column)}`).join(', ')}`;
539
+ return ` RETURNING ${returning
540
+ .map((item) => {
541
+ if (item.expr.kind === 'column-ref') {
542
+ const rendered = `${quoteIdentifier(item.expr.table)}.${quoteIdentifier(item.expr.column)}`;
543
+ return item.expr.column === item.alias
544
+ ? rendered
545
+ : `${rendered} AS ${quoteIdentifier(item.alias)}`;
546
+ }
547
+ return `${renderExpr(item.expr)} AS ${quoteIdentifier(item.alias)}`;
548
+ })
549
+ .join(', ')}`;
514
550
  }
515
551
 
516
552
  export function createSqliteAdapter(options?: SqliteAdapterOptions) {
@@ -1,13 +1,12 @@
1
1
  import {
2
2
  SQLITE_BIGINT_CODEC_ID,
3
3
  SQLITE_BLOB_CODEC_ID,
4
- SQLITE_BOOLEAN_CODEC_ID,
5
4
  SQLITE_DATETIME_CODEC_ID,
6
5
  SQLITE_INTEGER_CODEC_ID,
7
6
  SQLITE_JSON_CODEC_ID,
8
7
  SQLITE_REAL_CODEC_ID,
9
8
  SQLITE_TEXT_CODEC_ID,
10
- } from './codec-ids';
9
+ } from '@prisma-next/target-sqlite/codec-ids';
11
10
 
12
11
  export const textColumn = {
13
12
  codecId: SQLITE_TEXT_CODEC_ID,
@@ -29,11 +28,6 @@ export const blobColumn = {
29
28
  nativeType: 'blob',
30
29
  } as const;
31
30
 
32
- export const booleanColumn = {
33
- codecId: SQLITE_BOOLEAN_CODEC_ID,
34
- nativeType: 'integer',
35
- } as const;
36
-
37
31
  export const datetimeColumn = {
38
32
  codecId: SQLITE_DATETIME_CODEC_ID,
39
33
  nativeType: 'text',
@@ -1,18 +1,282 @@
1
+ import type { ContractMarkerRecord } from '@prisma-next/contract/types';
2
+ import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
3
+ import { parseContractMarkerRow } from '@prisma-next/family-sql/verify';
4
+ import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
1
5
  import type {
2
- ControlAdapterDescriptor,
3
- ControlAdapterInstance,
4
- } from '@prisma-next/framework-components/control';
5
- import { sqliteAdapterDescriptorMeta } from './descriptor-meta';
6
-
7
- const sqliteControlAdapterDescriptor: ControlAdapterDescriptor<
8
- 'sql',
9
- 'sqlite',
10
- ControlAdapterInstance<'sql', 'sqlite'>
11
- > = {
12
- ...sqliteAdapterDescriptorMeta,
13
- create(_stack): ControlAdapterInstance<'sql', 'sqlite'> {
14
- return { familyId: 'sql', targetId: 'sqlite' };
15
- },
6
+ AnyQueryAst,
7
+ LoweredStatement,
8
+ LowererContext,
9
+ } from '@prisma-next/sql-relational-core/ast';
10
+ import type {
11
+ PrimaryKey,
12
+ SqlColumnIR,
13
+ SqlForeignKeyIR,
14
+ SqlIndexIR,
15
+ SqlReferentialAction,
16
+ SqlSchemaIR,
17
+ SqlTableIR,
18
+ SqlUniqueIR,
19
+ } from '@prisma-next/sql-schema-ir/types';
20
+ import { parseSqliteDefault } from '@prisma-next/target-sqlite/default-normalizer';
21
+ import { normalizeSqliteNativeType } from '@prisma-next/target-sqlite/native-type-normalizer';
22
+ import { ifDefined } from '@prisma-next/utils/defined';
23
+ import { renderLoweredSql } from './adapter';
24
+ import type { SqliteContract } from './types';
25
+
26
+ // PRAGMA result row types
27
+ type PragmaTableInfoRow = {
28
+ cid: number;
29
+ name: string;
30
+ type: string;
31
+ notnull: number;
32
+ dflt_value: string | null;
33
+ pk: number;
34
+ };
35
+
36
+ type PragmaForeignKeyRow = {
37
+ id: number;
38
+ seq: number;
39
+ table: string;
40
+ from: string;
41
+ to: string;
42
+ on_update: string;
43
+ on_delete: string;
44
+ };
45
+
46
+ type PragmaIndexListRow = {
47
+ seq: number;
48
+ name: string;
49
+ unique: number;
50
+ origin: string;
51
+ partial: number;
52
+ };
53
+
54
+ type PragmaIndexInfoRow = {
55
+ seqno: number;
56
+ cid: number;
57
+ name: string;
58
+ };
59
+
60
+ type FkAccumulator = {
61
+ columns: string[];
62
+ referencedTable: string;
63
+ referencedColumns: string[];
64
+ onDelete: string;
65
+ onUpdate: string;
66
+ };
67
+
68
+ export class SqliteControlAdapter implements SqlControlAdapter<'sqlite'> {
69
+ readonly familyId = 'sql' as const;
70
+ readonly targetId = 'sqlite' as const;
71
+
72
+ readonly normalizeDefault = parseSqliteDefault;
73
+ readonly normalizeNativeType = normalizeSqliteNativeType;
74
+
75
+ /**
76
+ * Lower a SQL query AST into a SQLite-flavored `{ sql, params }` payload.
77
+ *
78
+ * Delegates to the shared `renderLoweredSql` renderer so the control adapter
79
+ * emits byte-identical SQL to `SqliteAdapterImpl.lower()` for the same AST
80
+ * and contract. Used at migration plan/emit time (e.g. by `dataTransform`)
81
+ * without instantiating the runtime adapter.
82
+ */
83
+ lower(ast: AnyQueryAst, context: LowererContext<unknown>): LoweredStatement {
84
+ return renderLoweredSql(ast, context.contract as SqliteContract);
85
+ }
86
+
87
+ /**
88
+ * Reads the contract marker from `_prisma_marker`. Probes `sqlite_master`
89
+ * first so a fresh database (no marker table) returns `null` instead of a
90
+ * "no such table" error.
91
+ */
92
+ async readMarker(
93
+ driver: ControlDriverInstance<'sql', 'sqlite'>,
94
+ ): Promise<ContractMarkerRecord | null> {
95
+ const exists = await driver.query(
96
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?`,
97
+ ['_prisma_marker'],
98
+ );
99
+ if (exists.rows.length === 0) {
100
+ return null;
101
+ }
102
+
103
+ const result = await driver.query<{
104
+ core_hash: string;
105
+ profile_hash: string;
106
+ contract_json: unknown | null;
107
+ canonical_version: number | null;
108
+ updated_at: Date | string;
109
+ app_tag: string | null;
110
+ meta: unknown | null;
111
+ invariants: unknown;
112
+ }>(
113
+ `SELECT
114
+ core_hash,
115
+ profile_hash,
116
+ contract_json,
117
+ canonical_version,
118
+ updated_at,
119
+ app_tag,
120
+ meta,
121
+ invariants
122
+ FROM _prisma_marker
123
+ WHERE id = ?`,
124
+ [1],
125
+ );
126
+
127
+ const row = result.rows[0];
128
+ if (!row) return null;
129
+ // SQLite stores arrays as JSON-encoded TEXT (no native array type), so
130
+ // the driver returns `invariants` as a string. Decode before delegating
131
+ // to the shared row schema, which expects `string[]`.
132
+ const invariants =
133
+ typeof row.invariants === 'string' ? (JSON.parse(row.invariants) as unknown) : row.invariants;
134
+ return parseContractMarkerRow({ ...row, invariants });
135
+ }
136
+
137
+ async introspect(
138
+ driver: ControlDriverInstance<'sql', 'sqlite'>,
139
+ _contract?: unknown,
140
+ ): Promise<SqlSchemaIR> {
141
+ // Filter out runner-managed control tables (`_prisma_marker`,
142
+ // `_prisma_ledger`) — they're an implementation detail of the migration
143
+ // runner, not part of the user-authored contract, so they must not
144
+ // appear in introspection output (otherwise strict schema verification
145
+ // flags them as `extra_table`).
146
+ const tablesResult = await driver.query<{ name: string }>(
147
+ `SELECT name FROM sqlite_master
148
+ WHERE type = 'table'
149
+ AND name NOT LIKE 'sqlite_%'
150
+ AND name NOT IN ('_prisma_marker', '_prisma_ledger')
151
+ ORDER BY name`,
152
+ );
153
+
154
+ const tables: Record<string, SqlTableIR> = {};
155
+
156
+ for (const tableRow of tablesResult.rows) {
157
+ const tableName = tableRow.name;
158
+
159
+ // SQLite's synchronous driver serializes reads — no benefit from Promise.all
160
+ const columnsResult = await driver.query<PragmaTableInfoRow>(
161
+ `PRAGMA table_info("${escapePragmaArg(tableName)}")`,
162
+ );
163
+ const fkResult = await driver.query<PragmaForeignKeyRow>(
164
+ `PRAGMA foreign_key_list("${escapePragmaArg(tableName)}")`,
165
+ );
166
+ const indexListResult = await driver.query<PragmaIndexListRow>(
167
+ `PRAGMA index_list("${escapePragmaArg(tableName)}")`,
168
+ );
169
+
170
+ const columns: Record<string, SqlColumnIR> = {};
171
+ const pkColumns: Array<{ name: string; pk: number }> = [];
172
+
173
+ for (const col of columnsResult.rows) {
174
+ columns[col.name] = {
175
+ name: col.name,
176
+ nativeType: col.type.toLowerCase(),
177
+ nullable: col.notnull === 0 && col.pk === 0,
178
+ ...ifDefined('default', col.dflt_value ?? undefined),
179
+ };
180
+ if (col.pk > 0) {
181
+ pkColumns.push({ name: col.name, pk: col.pk });
182
+ }
183
+ }
184
+
185
+ pkColumns.sort((a, b) => a.pk - b.pk);
186
+ const primaryKey: PrimaryKey | undefined =
187
+ pkColumns.length > 0 ? { columns: pkColumns.map((c) => c.name) } : undefined;
188
+
189
+ const fkMap = new Map<number, FkAccumulator>();
190
+ for (const fk of fkResult.rows) {
191
+ const existing = fkMap.get(fk.id);
192
+ if (existing) {
193
+ existing.columns.push(fk.from);
194
+ existing.referencedColumns.push(fk.to);
195
+ } else {
196
+ fkMap.set(fk.id, {
197
+ columns: [fk.from],
198
+ referencedTable: fk.table,
199
+ referencedColumns: [fk.to],
200
+ onDelete: fk.on_delete,
201
+ onUpdate: fk.on_update,
202
+ });
203
+ }
204
+ }
205
+ const foreignKeys: readonly SqlForeignKeyIR[] = Array.from(fkMap.values()).map((fk) => ({
206
+ columns: Object.freeze([...fk.columns]) as readonly string[],
207
+ referencedTable: fk.referencedTable,
208
+ referencedColumns: Object.freeze([...fk.referencedColumns]) as readonly string[],
209
+ ...ifDefined('onDelete', mapSqliteReferentialAction(fk.onDelete)),
210
+ ...ifDefined('onUpdate', mapSqliteReferentialAction(fk.onUpdate)),
211
+ }));
212
+
213
+ const uniques: SqlUniqueIR[] = [];
214
+ const indexes: SqlIndexIR[] = [];
215
+
216
+ for (const idx of indexListResult.rows) {
217
+ // origin: 'c' = CREATE INDEX, 'u' = UNIQUE constraint, 'pk' = PRIMARY KEY
218
+ const idxInfoResult = await driver.query<PragmaIndexInfoRow>(
219
+ `PRAGMA index_info("${escapePragmaArg(idx.name)}")`,
220
+ );
221
+
222
+ const idxColumns = idxInfoResult.rows.sort((a, b) => a.seqno - b.seqno).map((r) => r.name);
223
+
224
+ if (idx.origin === 'u') {
225
+ uniques.push({
226
+ columns: Object.freeze([...idxColumns]) as readonly string[],
227
+ name: idx.name,
228
+ });
229
+ } else if (idx.origin === 'c') {
230
+ indexes.push({
231
+ columns: Object.freeze([...idxColumns]) as readonly string[],
232
+ name: idx.name,
233
+ unique: idx.unique === 1,
234
+ });
235
+ }
236
+ // Skip 'pk' origin — already captured in primaryKey
237
+ }
238
+
239
+ tables[tableName] = {
240
+ name: tableName,
241
+ columns,
242
+ ...ifDefined('primaryKey', primaryKey),
243
+ foreignKeys,
244
+ uniques,
245
+ indexes,
246
+ };
247
+ }
248
+
249
+ return {
250
+ tables,
251
+ dependencies: [],
252
+ };
253
+ }
254
+ }
255
+
256
+ // PRAGMA queries use the function-argument form (`PRAGMA table_info("name")`)
257
+ // which doesn't support `?` placeholders — the argument is part of the
258
+ // statement name, not a bound parameter. We quote-escape the table name instead.
259
+ function escapePragmaArg(name: string): string {
260
+ return name.replace(/"/g, '""');
261
+ }
262
+
263
+ const SQLITE_REFERENTIAL_ACTION_MAP: Record<string, SqlReferentialAction> = {
264
+ 'NO ACTION': 'noAction',
265
+ RESTRICT: 'restrict',
266
+ CASCADE: 'cascade',
267
+ 'SET NULL': 'setNull',
268
+ 'SET DEFAULT': 'setDefault',
16
269
  };
17
270
 
18
- export default sqliteControlAdapterDescriptor;
271
+ function mapSqliteReferentialAction(rule: string): SqlReferentialAction | undefined {
272
+ const normalized = rule.toUpperCase();
273
+ const mapped = SQLITE_REFERENTIAL_ACTION_MAP[normalized];
274
+ if (mapped === undefined) {
275
+ throw new Error(
276
+ `Unknown SQLite referential action rule: "${rule}". ` +
277
+ 'Expected one of: NO ACTION, RESTRICT, CASCADE, SET NULL, SET DEFAULT.',
278
+ );
279
+ }
280
+ if (mapped === 'noAction') return undefined;
281
+ return mapped;
282
+ }