@prisma-next/adapter-sqlite 0.12.0 → 0.13.0-dev.1

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.
@@ -0,0 +1,795 @@
1
+ import { APP_SPACE_ID } from "@prisma-next/framework-components/control";
2
+ import { RawExpr, isDdlNode } from "@prisma-next/sql-relational-core/ast";
3
+ import { escapeLiteral, quoteIdentifier } from "@prisma-next/target-sqlite/sql-utils";
4
+ import { parseMarkerRowSafely, withMarkerReadErrorHandling } from "@prisma-next/errors/execution";
5
+ import { parseContractMarkerRow } from "@prisma-next/family-sql/verify";
6
+ import { buildControlTableBootstrapQueries, buildSignMarkerBootstrapQueries, datetime, integer, jsonText, sqliteTable, text } from "@prisma-next/target-sqlite/contract-free";
7
+ import { parseSqliteDefault } from "@prisma-next/target-sqlite/default-normalizer";
8
+ import { normalizeSqliteNativeType } from "@prisma-next/target-sqlite/native-type-normalizer";
9
+ import { blindCast } from "@prisma-next/utils/casts";
10
+ import { ifDefined } from "@prisma-next/utils/defined";
11
+ import { REFERENTIAL_ACTION_SQL } from "@prisma-next/sql-contract/referential-action-sql";
12
+ import { createAstCodecRegistry, deriveParamMetadata, encodeParamsWithMetadata } from "@prisma-next/sql-runtime";
13
+ import { SQLITE_DATETIME_CODEC_ID } from "@prisma-next/target-sqlite/codec-ids";
14
+ import { sqliteCodecRegistry } from "@prisma-next/target-sqlite/codecs";
15
+ //#region ../../../1-framework/3-tooling/migration/dist/exports/ledger-origin.mjs
16
+ function ledgerOriginFromStored(originCoreHash) {
17
+ if (originCoreHash === null || originCoreHash === "" || originCoreHash === "sha256:empty") return null;
18
+ return originCoreHash;
19
+ }
20
+ //#endregion
21
+ //#region src/core/ddl-renderer.ts
22
+ function renderPrimaryKeyConstraint(constraint) {
23
+ const cols = constraint.columns.join(", ");
24
+ if (constraint.name !== void 0) return `CONSTRAINT ${constraint.name} PRIMARY KEY (${cols})`;
25
+ return `PRIMARY KEY (${cols})`;
26
+ }
27
+ function renderForeignKeyConstraint(constraint) {
28
+ const cols = constraint.columns.join(", ");
29
+ const refCols = constraint.refColumns.join(", ");
30
+ let sql = `FOREIGN KEY (${cols}) REFERENCES ${constraint.refTable} (${refCols})`;
31
+ if (constraint.onDelete !== void 0) sql += ` ON DELETE ${REFERENTIAL_ACTION_SQL[constraint.onDelete]}`;
32
+ if (constraint.onUpdate !== void 0) sql += ` ON UPDATE ${REFERENTIAL_ACTION_SQL[constraint.onUpdate]}`;
33
+ if (constraint.name !== void 0) sql = `CONSTRAINT ${constraint.name} ${sql}`;
34
+ return sql;
35
+ }
36
+ function renderUniqueConstraint(constraint) {
37
+ const cols = constraint.columns.join(", ");
38
+ if (constraint.name !== void 0) return `CONSTRAINT ${constraint.name} UNIQUE (${cols})`;
39
+ return `UNIQUE (${cols})`;
40
+ }
41
+ function renderTableConstraint(constraint) {
42
+ switch (constraint.kind) {
43
+ case "primary-key": return renderPrimaryKeyConstraint(constraint);
44
+ case "foreign-key": return renderForeignKeyConstraint(constraint);
45
+ case "unique": return renderUniqueConstraint(constraint);
46
+ }
47
+ }
48
+ var SqliteDdlVisitorImpl = class {
49
+ createTable(node) {
50
+ const ifNotExists = node.ifNotExists ? "IF NOT EXISTS " : "";
51
+ const tableRef = node.table;
52
+ const columnDefs = node.columns.map((column) => renderColumn$1(column));
53
+ const constraintDefs = node.constraints !== void 0 ? node.constraints.map(renderTableConstraint) : [];
54
+ return `CREATE TABLE ${ifNotExists}${tableRef} (\n ${[...columnDefs, ...constraintDefs].join(",\n ")}\n )`;
55
+ }
56
+ };
57
+ const defaultVisitor = {
58
+ literal(node, _ctx) {
59
+ const { value } = node;
60
+ if (typeof value === "string") return `DEFAULT '${escapeLiteral(value)}'`;
61
+ if (typeof value === "number" || typeof value === "boolean") return `DEFAULT ${String(value)}`;
62
+ if (value === null) return "DEFAULT NULL";
63
+ return `DEFAULT '${JSON.stringify(value)}'`;
64
+ },
65
+ function(node, _ctx) {
66
+ if (node.expression === "autoincrement()") return "";
67
+ return `DEFAULT (${node.expression})`;
68
+ }
69
+ };
70
+ function renderColumn$1(column) {
71
+ if (column.type.includes("AUTOINCREMENT")) return `${column.name} ${column.type}`;
72
+ const parts = [column.name, column.type];
73
+ if (column.notNull) parts.push("NOT NULL");
74
+ if (column.primaryKey) parts.push("PRIMARY KEY");
75
+ const defaultClause = column.default ? column.default.accept(defaultVisitor, { nativeType: column.type }) : "";
76
+ if (defaultClause.length > 0) parts.push(defaultClause);
77
+ return parts.join(" ");
78
+ }
79
+ function renderLoweredDdl(ast) {
80
+ const sql = ast.accept(new SqliteDdlVisitorImpl());
81
+ return Object.freeze({
82
+ sql,
83
+ params: Object.freeze([])
84
+ });
85
+ }
86
+ //#endregion
87
+ //#region src/core/ledger-decode.ts
88
+ const DESIGNATOR_LESS_UTC_DATETIME = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?$/;
89
+ function coerceLedgerAppliedAt(value) {
90
+ if (value instanceof Date) return value;
91
+ if (DESIGNATOR_LESS_UTC_DATETIME.test(value)) return /* @__PURE__ */ new Date(`${value.replace(" ", "T")}Z`);
92
+ return new Date(value);
93
+ }
94
+ function operationCountFromStored(operations) {
95
+ if (Array.isArray(operations)) return operations.length;
96
+ if (typeof operations === "string") try {
97
+ const parsed = JSON.parse(operations);
98
+ return Array.isArray(parsed) ? parsed.length : 0;
99
+ } catch {
100
+ return 0;
101
+ }
102
+ return 0;
103
+ }
104
+ //#endregion
105
+ //#region src/core/marker-ledger.ts
106
+ const CONTROL_CODECS = createAstCodecRegistry(sqliteCodecRegistry);
107
+ const marker = sqliteTable("_prisma_marker", {
108
+ space: text(),
109
+ core_hash: text(),
110
+ profile_hash: text(),
111
+ contract_json: jsonText({ nullable: true }),
112
+ canonical_version: integer({ nullable: true }),
113
+ updated_at: datetime(),
114
+ app_tag: text({ nullable: true }),
115
+ meta: jsonText({ nullable: true }),
116
+ invariants: jsonText()
117
+ });
118
+ /**
119
+ * Writeable subset of `_prisma_ledger`. Omits the DB-generated `id`
120
+ * (`INTEGER PRIMARY KEY AUTOINCREMENT`) and `created_at` (default
121
+ * `strftime(...)`).
122
+ */
123
+ const ledger = sqliteTable("_prisma_ledger", {
124
+ space: text(),
125
+ migration_name: text(),
126
+ migration_hash: text(),
127
+ origin_core_hash: text({ nullable: true }),
128
+ destination_core_hash: text(),
129
+ operations: jsonText()
130
+ });
131
+ /**
132
+ * Read-side handle covering every column of `_prisma_ledger`, including
133
+ * the DB-generated `id` (for ORDER BY) and `created_at`.
134
+ */
135
+ const ledgerReadShape = sqliteTable("_prisma_ledger", {
136
+ id: integer(),
137
+ space: text(),
138
+ migration_name: text(),
139
+ migration_hash: text(),
140
+ origin_core_hash: text({ nullable: true }),
141
+ destination_core_hash: text(),
142
+ operations: jsonText(),
143
+ created_at: text()
144
+ });
145
+ const sqliteCatalog = sqliteTable("sqlite_master", {
146
+ type: text(),
147
+ name: text()
148
+ });
149
+ const NOW = new RawExpr({
150
+ parts: ["datetime('now')"],
151
+ returns: {
152
+ codecId: SQLITE_DATETIME_CODEC_ID,
153
+ nullable: false
154
+ }
155
+ });
156
+ function mergeInvariants(current, incoming) {
157
+ return [...new Set([...current, ...incoming])].sort();
158
+ }
159
+ async function execute(lower, driver, query) {
160
+ const lowered = lower(query);
161
+ const encoded = await encodeParamsWithMetadata(lowered.params.map((slot) => {
162
+ if (slot.kind === "literal") return slot.value;
163
+ throw new Error("SQLite control DML lowered to a bind parameter, which is unsupported");
164
+ }), deriveParamMetadata(query), {}, CONTROL_CODECS);
165
+ return (await driver.query(lowered.sql, encoded)).rows;
166
+ }
167
+ function decodeSqliteMarkerRow(row) {
168
+ if (typeof row !== "object" || row === null || !("invariants" in row)) return row;
169
+ const record = row;
170
+ if (typeof record.invariants !== "string") return row;
171
+ let parsed;
172
+ try {
173
+ parsed = JSON.parse(record.invariants);
174
+ } catch (err) {
175
+ const detail = err instanceof Error ? err.message : String(err);
176
+ throw new Error(`Invalid contract marker row: invariants is not valid JSON: ${detail}`);
177
+ }
178
+ return {
179
+ ...record,
180
+ invariants: parsed
181
+ };
182
+ }
183
+ //#endregion
184
+ //#region src/core/control-adapter.ts
185
+ const SQLITE_MARKER_TABLE = "_prisma_marker";
186
+ const SQLITE_LEDGER_TABLE = "_prisma_ledger";
187
+ var SqliteControlAdapter = class {
188
+ familyId = "sql";
189
+ targetId = "sqlite";
190
+ normalizeDefault = parseSqliteDefault;
191
+ normalizeNativeType = normalizeSqliteNativeType;
192
+ bootstrapControlTableQueries() {
193
+ return buildControlTableBootstrapQueries();
194
+ }
195
+ bootstrapSignMarkerQueries() {
196
+ return buildSignMarkerBootstrapQueries();
197
+ }
198
+ /**
199
+ * Lower a SQL query AST into a SQLite-flavored `{ sql, params }` payload.
200
+ *
201
+ * Delegates to the shared `renderLoweredSql` renderer so the control adapter
202
+ * emits byte-identical SQL to `SqliteAdapterImpl.lower()` for the same AST
203
+ * and contract. Used at migration plan/emit time (e.g. by `dataTransform`)
204
+ * without instantiating the runtime adapter.
205
+ */
206
+ lower(ast, context) {
207
+ if (isDdlNode(ast)) return renderLoweredDdl(ast);
208
+ return renderLoweredSql(ast, context.contract);
209
+ }
210
+ /**
211
+ * Reads the contract marker from `_prisma_marker`. Probes `sqlite_master`
212
+ * first so a fresh database (no marker table) returns `null` instead of a
213
+ * "no such table" error.
214
+ */
215
+ async readMarker(driver, space) {
216
+ const result = await this.readMarkerDiscriminated(driver, space);
217
+ return result.kind === "present" ? result.record : null;
218
+ }
219
+ async readMarkerDiscriminated(driver, space) {
220
+ return withMarkerReadErrorHandling(() => this.readMarkerResult(driver, space), {
221
+ space,
222
+ markerLocation: SQLITE_MARKER_TABLE
223
+ });
224
+ }
225
+ /**
226
+ * Reads every row from `_prisma_marker` and returns them keyed by
227
+ * `space`. Mirrors the existence probe in {@link readMarker}: a
228
+ * fresh database without the marker table returns an empty map.
229
+ */
230
+ async readAllMarkers(driver) {
231
+ return withMarkerReadErrorHandling(() => this.readAllMarkersResult(driver), {
232
+ space: APP_SPACE_ID,
233
+ markerLocation: SQLITE_MARKER_TABLE
234
+ });
235
+ }
236
+ async readAllMarkersResult(driver) {
237
+ const lower = (query) => this.lower(query, { contract: void 0 });
238
+ if ((await execute(lower, driver, sqliteCatalog.select(sqliteCatalog.name).where(sqliteCatalog.type.eq("table").and(sqliteCatalog.name.eq("_prisma_marker"))).build())).length === 0) return /* @__PURE__ */ new Map();
239
+ const rows = blindCast(await execute(lower, driver, marker.select(marker.space, marker.core_hash, marker.profile_hash, marker.contract_json, marker.canonical_version, marker.updated_at, marker.app_tag, marker.meta, marker.invariants).build()));
240
+ const out = /* @__PURE__ */ new Map();
241
+ for (const row of rows) out.set(row.space, parseMarkerRowSafely(row, (raw) => parseContractMarkerRow(decodeSqliteMarkerRow(raw)), {
242
+ space: row.space,
243
+ markerLocation: SQLITE_MARKER_TABLE
244
+ }));
245
+ return out;
246
+ }
247
+ /**
248
+ * Reads per-migration ledger rows from `_prisma_ledger` in apply order.
249
+ * Probes `sqlite_master` first so a fresh database without the ledger
250
+ * table returns `[]` instead of raising "no such table".
251
+ */
252
+ async readLedger(driver, space) {
253
+ return withMarkerReadErrorHandling(() => this.readLedgerResult(driver, space), {
254
+ space: space ?? "*",
255
+ markerLocation: SQLITE_LEDGER_TABLE
256
+ });
257
+ }
258
+ async readLedgerResult(driver, space) {
259
+ const lower = (query) => this.lower(query, { contract: void 0 });
260
+ if ((await execute(lower, driver, sqliteCatalog.select(sqliteCatalog.name).where(sqliteCatalog.type.eq("table").and(sqliteCatalog.name.eq("_prisma_ledger"))).build())).length === 0) return [];
261
+ const base = ledgerReadShape.select(ledgerReadShape.space, ledgerReadShape.migration_name, ledgerReadShape.migration_hash, ledgerReadShape.origin_core_hash, ledgerReadShape.destination_core_hash, ledgerReadShape.operations, ledgerReadShape.created_at);
262
+ return blindCast(await execute(lower, driver, (space !== void 0 ? base.where(ledgerReadShape.space.eq(space)) : base).orderBy(ledgerReadShape.id).build())).map((row) => ({
263
+ space: row.space,
264
+ migrationName: row.migration_name,
265
+ migrationHash: row.migration_hash,
266
+ from: ledgerOriginFromStored(row.origin_core_hash),
267
+ to: row.destination_core_hash,
268
+ appliedAt: coerceLedgerAppliedAt(row.created_at),
269
+ operationCount: operationCountFromStored(row.operations)
270
+ }));
271
+ }
272
+ /**
273
+ * Stamps the initial marker row for `space` via the shared contract-free DML
274
+ * builder, lowered through {@link lower} and executed on the driver. See the
275
+ * `SqlControlAdapter.initMarker` contract.
276
+ */
277
+ async insertMarker(driver, space, destination) {
278
+ await execute((query) => this.lower(query, { contract: void 0 }), driver, marker.insert({
279
+ space,
280
+ core_hash: destination.storageHash,
281
+ profile_hash: destination.profileHash,
282
+ contract_json: null,
283
+ canonical_version: null,
284
+ updated_at: NOW,
285
+ app_tag: null,
286
+ meta: {},
287
+ invariants: destination.invariants ?? []
288
+ }).build());
289
+ }
290
+ async initMarker(driver, space, destination) {
291
+ await execute((query) => this.lower(query, { contract: void 0 }), driver, marker.upsert({
292
+ space,
293
+ core_hash: destination.storageHash,
294
+ profile_hash: destination.profileHash,
295
+ contract_json: null,
296
+ canonical_version: null,
297
+ updated_at: NOW,
298
+ app_tag: null,
299
+ meta: {},
300
+ invariants: destination.invariants ?? []
301
+ }).onConflict(marker.space).doUpdate((excluded) => ({
302
+ core_hash: excluded.core_hash,
303
+ profile_hash: excluded.profile_hash,
304
+ contract_json: excluded.contract_json,
305
+ canonical_version: excluded.canonical_version,
306
+ updated_at: NOW,
307
+ app_tag: excluded.app_tag,
308
+ meta: excluded.meta,
309
+ invariants: excluded.invariants
310
+ })).build());
311
+ }
312
+ /**
313
+ * Compare-and-swap advance of the marker row for `space`. See the
314
+ * `SqlControlAdapter.updateMarker` contract.
315
+ */
316
+ async updateMarker(driver, space, expectedFrom, destination) {
317
+ const currentInvariants = destination.invariants === void 0 ? [] : (await this.readMarker(driver, space))?.invariants ?? [];
318
+ const mergedInvariants = destination.invariants === void 0 ? void 0 : mergeInvariants(currentInvariants, destination.invariants);
319
+ return (await execute((q) => this.lower(q, { contract: void 0 }), driver, marker.update().set({
320
+ core_hash: destination.storageHash,
321
+ profile_hash: destination.profileHash,
322
+ updated_at: NOW,
323
+ ...mergedInvariants !== void 0 ? { invariants: mergedInvariants } : {}
324
+ }).where(marker.space.eq(space).and(marker.core_hash.eq(expectedFrom))).returning(marker.space).build())).length > 0;
325
+ }
326
+ /**
327
+ * Appends a ledger entry for `space`. See the
328
+ * `SqlControlAdapter.writeLedgerEntry` contract.
329
+ */
330
+ async writeLedgerEntry(driver, space, entry) {
331
+ await execute((query) => this.lower(query, { contract: void 0 }), driver, ledger.insert({
332
+ space,
333
+ migration_name: entry.migrationName,
334
+ migration_hash: entry.migrationHash,
335
+ origin_core_hash: entry.from,
336
+ destination_core_hash: entry.to,
337
+ operations: entry.operations
338
+ }).build());
339
+ }
340
+ async readMarkerResult(driver, space) {
341
+ const lower = (query) => this.lower(query, { contract: void 0 });
342
+ if ((await execute(lower, driver, sqliteCatalog.select(sqliteCatalog.name).where(sqliteCatalog.type.eq("table").and(sqliteCatalog.name.eq("_prisma_marker"))).build())).length === 0) return { kind: "no-table" };
343
+ const row = (await execute(lower, driver, marker.select(marker.core_hash, marker.profile_hash, marker.contract_json, marker.canonical_version, marker.updated_at, marker.app_tag, marker.meta, marker.invariants).where(marker.space.eq(space)).build()))[0];
344
+ if (!row) return { kind: "absent" };
345
+ return {
346
+ kind: "present",
347
+ record: parseContractMarkerRow(decodeSqliteMarkerRow(row))
348
+ };
349
+ }
350
+ async introspect(driver, _contract) {
351
+ const tablesResult = await driver.query(`SELECT name FROM sqlite_master
352
+ WHERE type = 'table'
353
+ AND name NOT LIKE 'sqlite_%'
354
+ AND name NOT IN ('_prisma_marker', '_prisma_ledger')
355
+ ORDER BY name`);
356
+ const tables = {};
357
+ for (const tableRow of tablesResult.rows) {
358
+ const tableName = tableRow.name;
359
+ const columnsResult = await driver.query(`PRAGMA table_info("${escapePragmaArg(tableName)}")`);
360
+ const fkResult = await driver.query(`PRAGMA foreign_key_list("${escapePragmaArg(tableName)}")`);
361
+ const indexListResult = await driver.query(`PRAGMA index_list("${escapePragmaArg(tableName)}")`);
362
+ const columns = {};
363
+ const pkColumns = [];
364
+ for (const col of columnsResult.rows) {
365
+ columns[col.name] = {
366
+ name: col.name,
367
+ nativeType: col.type.toLowerCase(),
368
+ nullable: col.notnull === 0 && col.pk === 0,
369
+ ...ifDefined("default", col.dflt_value ?? void 0)
370
+ };
371
+ if (col.pk > 0) pkColumns.push({
372
+ name: col.name,
373
+ pk: col.pk
374
+ });
375
+ }
376
+ pkColumns.sort((a, b) => a.pk - b.pk);
377
+ const primaryKey = pkColumns.length > 0 ? { columns: pkColumns.map((c) => c.name) } : void 0;
378
+ const fkMap = /* @__PURE__ */ new Map();
379
+ for (const fk of fkResult.rows) {
380
+ const existing = fkMap.get(fk.id);
381
+ if (existing) {
382
+ existing.columns.push(fk.from);
383
+ existing.referencedColumns.push(fk.to);
384
+ } else fkMap.set(fk.id, {
385
+ columns: [fk.from],
386
+ referencedTable: fk.table,
387
+ referencedColumns: [fk.to],
388
+ onDelete: fk.on_delete,
389
+ onUpdate: fk.on_update
390
+ });
391
+ }
392
+ const foreignKeys = Array.from(fkMap.values()).map((fk) => ({
393
+ columns: Object.freeze([...fk.columns]),
394
+ referencedTable: fk.referencedTable,
395
+ referencedColumns: Object.freeze([...fk.referencedColumns]),
396
+ ...ifDefined("onDelete", mapSqliteReferentialAction(fk.onDelete)),
397
+ ...ifDefined("onUpdate", mapSqliteReferentialAction(fk.onUpdate))
398
+ }));
399
+ const uniques = [];
400
+ const indexes = [];
401
+ for (const idx of indexListResult.rows) {
402
+ const idxColumns = (await driver.query(`PRAGMA index_info("${escapePragmaArg(idx.name)}")`)).rows.sort((a, b) => a.seqno - b.seqno).map((r) => r.name);
403
+ if (idx.origin === "u") uniques.push({
404
+ columns: Object.freeze([...idxColumns]),
405
+ name: idx.name
406
+ });
407
+ else if (idx.origin === "c") indexes.push({
408
+ columns: Object.freeze([...idxColumns]),
409
+ name: idx.name,
410
+ unique: idx.unique === 1
411
+ });
412
+ }
413
+ tables[tableName] = {
414
+ name: tableName,
415
+ columns,
416
+ ...ifDefined("primaryKey", primaryKey),
417
+ foreignKeys,
418
+ uniques,
419
+ indexes
420
+ };
421
+ }
422
+ return { tables };
423
+ }
424
+ };
425
+ function escapePragmaArg(name) {
426
+ return name.replace(/"/g, "\"\"");
427
+ }
428
+ const SQLITE_REFERENTIAL_ACTION_MAP = {
429
+ "NO ACTION": "noAction",
430
+ RESTRICT: "restrict",
431
+ CASCADE: "cascade",
432
+ "SET NULL": "setNull",
433
+ "SET DEFAULT": "setDefault"
434
+ };
435
+ function mapSqliteReferentialAction(rule) {
436
+ const mapped = SQLITE_REFERENTIAL_ACTION_MAP[rule.toUpperCase()];
437
+ if (mapped === void 0) throw new Error(`Unknown SQLite referential action rule: "${rule}". Expected one of: NO ACTION, RESTRICT, CASCADE, SET NULL, SET DEFAULT.`);
438
+ if (mapped === "noAction") return void 0;
439
+ return mapped;
440
+ }
441
+ //#endregion
442
+ //#region src/core/adapter.ts
443
+ const defaultCapabilities = Object.freeze({ sql: {
444
+ orderBy: true,
445
+ limit: true,
446
+ lateral: false,
447
+ jsonAgg: true,
448
+ returning: true,
449
+ enums: false
450
+ } });
451
+ var SqliteAdapterImpl = class {
452
+ familyId = "sql";
453
+ targetId = "sqlite";
454
+ profile;
455
+ constructor(options) {
456
+ const controlAdapter = new SqliteControlAdapter();
457
+ this.profile = Object.freeze({
458
+ id: options?.profileId ?? "sqlite/default@1",
459
+ target: "sqlite",
460
+ capabilities: defaultCapabilities,
461
+ readMarker: (queryable) => controlAdapter.readMarkerDiscriminated({
462
+ familyId: "sql",
463
+ targetId: "sqlite",
464
+ query: async (sql, params) => {
465
+ return { rows: [...(await queryable.query(sql, params)).rows] };
466
+ },
467
+ close: async () => {}
468
+ }, APP_SPACE_ID)
469
+ });
470
+ }
471
+ lower(ast, context) {
472
+ if (isDdlNode(ast)) return renderLoweredDdl(ast);
473
+ return renderLoweredSql(ast, context.contract);
474
+ }
475
+ };
476
+ /** Codec-id lookup for bare-literal interpolations used by `fns.raw` on a sqlite client. Contributed as the descriptor's static `rawCodecInferer` slot. */
477
+ const sqliteRawCodecInferer = { inferCodec(value) {
478
+ switch (typeof value) {
479
+ case "number": return Number.isSafeInteger(value) && value % 1 === 0 ? "sqlite/integer@1" : "sqlite/real@1";
480
+ case "bigint": return "sqlite/bigint@1";
481
+ case "string": return "sqlite/text@1";
482
+ case "boolean": return "sqlite/integer@1";
483
+ case "object": if (value instanceof Uint8Array) return "sqlite/blob@1";
484
+ }
485
+ throw new Error("unsupported JS value type for raw-SQL interpolation: wrap this value in `param(...)` with an explicit codec");
486
+ } };
487
+ /**
488
+ * Lower a SQL query AST into a SQLite-flavored `{ sql, params }` payload.
489
+ *
490
+ * Shared between the runtime adapter (`SqliteAdapterImpl.lower`) and the control adapter (`SqliteControlAdapter.lower`) so both produce byte-identical SQL for the same AST and contract.
491
+ */
492
+ function renderLoweredSql(ast, contract) {
493
+ const collectedParamRefs = ast.collectParamRefs();
494
+ const params = [];
495
+ for (const ref of collectedParamRefs) params.push(ref.kind === "prepared-param-ref" ? {
496
+ kind: "bind",
497
+ name: ref.name
498
+ } : {
499
+ kind: "literal",
500
+ value: ref.value
501
+ });
502
+ let sql;
503
+ const node = ast;
504
+ switch (node.kind) {
505
+ case "select":
506
+ sql = renderSelect(node, contract);
507
+ break;
508
+ case "insert":
509
+ sql = renderInsert(node, contract);
510
+ break;
511
+ case "update":
512
+ sql = renderUpdate(node, contract);
513
+ break;
514
+ case "delete":
515
+ sql = renderDelete(node, contract);
516
+ break;
517
+ default: throw new Error(`Unsupported AST node kind: ${node.kind}`);
518
+ }
519
+ return Object.freeze({
520
+ sql,
521
+ params
522
+ });
523
+ }
524
+ function renderLimitOffset(keyword, value, contract) {
525
+ if (value === void 0) return "";
526
+ if (typeof value === "number") return `${keyword} ${value}`;
527
+ return `${keyword} ${renderExpr(value, contract)}`;
528
+ }
529
+ function renderSelect(ast, contract) {
530
+ return [
531
+ `SELECT ${ast.distinct ? "DISTINCT " : ""}${renderProjection(ast.projection, contract)}`,
532
+ `FROM ${renderSource(ast.from, contract)}`,
533
+ ast.joins?.length ? ast.joins.map((join) => renderJoin(join, contract)).join(" ") : "",
534
+ ast.where ? `WHERE ${renderExpr(ast.where, contract)}` : "",
535
+ ast.groupBy?.length ? `GROUP BY ${ast.groupBy.map((expr) => renderExpr(expr, contract)).join(", ")}` : "",
536
+ ast.having ? `HAVING ${renderExpr(ast.having, contract)}` : "",
537
+ ast.orderBy?.length ? `ORDER BY ${ast.orderBy.map((order) => `${renderExpr(order.expr, contract)} ${order.dir.toUpperCase()}`).join(", ")}` : "",
538
+ renderLimitOffset("LIMIT", ast.limit, contract),
539
+ renderLimitOffset("OFFSET", ast.offset, contract)
540
+ ].filter((part) => part.length > 0).join(" ").trim();
541
+ }
542
+ function renderProjection(projection, contract) {
543
+ return projection.map((item) => {
544
+ const alias = quoteIdentifier(item.alias);
545
+ if (item.expr.kind === "literal") return `${renderLiteral(item.expr)} AS ${alias}`;
546
+ return `${renderExpr(item.expr, contract)} AS ${alias}`;
547
+ }).join(", ");
548
+ }
549
+ function qualifyTableFromNamespaceCoordinate(table, contract) {
550
+ if (table.namespaceId === void 0) return quoteIdentifier(table.name);
551
+ const namespace = contract.storage.namespaces[table.namespaceId];
552
+ if (namespace === void 0) throw new Error(`Table "${table.name}" references namespace "${table.namespaceId}" which is not present on the contract`);
553
+ const qualifyTable = namespace.qualifyTable;
554
+ if (qualifyTable === void 0) throw new Error(`Table "${table.name}" references namespace "${table.namespaceId}" which is not materialised for SQL rendering on the contract`);
555
+ return qualifyTable.call(namespace, table.name);
556
+ }
557
+ function renderTableSource(source, contract) {
558
+ const qualified = qualifyTableFromNamespaceCoordinate(source, contract);
559
+ if (!source.alias) return qualified;
560
+ return `${qualified} AS ${quoteIdentifier(source.alias)}`;
561
+ }
562
+ function renderSource(source, contract) {
563
+ const node = source;
564
+ switch (node.kind) {
565
+ case "table-source": return renderTableSource(node, contract);
566
+ case "derived-table-source": return `(${renderSelect(node.query, contract)}) AS ${quoteIdentifier(node.alias)}`;
567
+ default: throw new Error(`Unsupported source node kind: ${node.kind}`);
568
+ }
569
+ }
570
+ function renderExpr(expr, contract) {
571
+ const node = expr;
572
+ switch (node.kind) {
573
+ case "column-ref": return renderColumn(node);
574
+ case "identifier-ref": return quoteIdentifier(node.name);
575
+ case "operation": return renderOperation(node, contract);
576
+ case "subquery": return renderSubqueryExpr(node, contract);
577
+ case "aggregate": return renderAggregateExpr(node, contract);
578
+ case "window-func": return renderWindowFuncExpr(node, contract);
579
+ case "json-object": return renderJsonObjectExpr(node, contract);
580
+ case "json-array-agg": return renderJsonArrayAggExpr(node, contract);
581
+ case "binary": return renderBinary(node, contract);
582
+ case "and":
583
+ if (node.exprs.length === 0) return "TRUE";
584
+ return `(${node.exprs.map((part) => renderExpr(part, contract)).join(" AND ")})`;
585
+ case "or":
586
+ if (node.exprs.length === 0) return "FALSE";
587
+ return `(${node.exprs.map((part) => renderExpr(part, contract)).join(" OR ")})`;
588
+ case "exists":
589
+ if (contract === void 0) throw new Error("EXISTS subquery rendering requires a Sqlite contract");
590
+ return `${node.notExists ? "NOT " : ""}EXISTS (${renderSelect(node.subquery, contract)})`;
591
+ case "null-check": return renderNullCheck(node, contract);
592
+ case "not": return `NOT (${renderExpr(node.expr, contract)})`;
593
+ case "param-ref":
594
+ case "prepared-param-ref": return "?";
595
+ case "literal": return renderLiteral(node);
596
+ case "list": return renderListLiteral(node);
597
+ case "raw-expr": return renderRawExpr(node, contract);
598
+ default: throw new Error(`Unsupported expression node kind: ${node.kind}`);
599
+ }
600
+ }
601
+ function renderRawExpr(node, contract) {
602
+ return node.parts.map((part) => typeof part === "string" ? part : renderExpr(part, contract)).join("");
603
+ }
604
+ function renderColumn(ref) {
605
+ if (ref.table === "excluded") return `excluded.${quoteIdentifier(ref.column)}`;
606
+ return `${quoteIdentifier(ref.table)}.${quoteIdentifier(ref.column)}`;
607
+ }
608
+ function renderLiteral(expr) {
609
+ if (typeof expr.value === "string") return `'${escapeLiteral(expr.value)}'`;
610
+ if (typeof expr.value === "number" || typeof expr.value === "boolean") return String(expr.value);
611
+ if (typeof expr.value === "bigint") return String(expr.value);
612
+ if (expr.value === null || expr.value === void 0) return "NULL";
613
+ if (expr.value instanceof Date) return `'${escapeLiteral(expr.value.toISOString())}'`;
614
+ const json = JSON.stringify(expr.value);
615
+ if (json === void 0) return "NULL";
616
+ return `'${escapeLiteral(json)}'`;
617
+ }
618
+ function renderOperation(expr, contract) {
619
+ const self = renderExpr(expr.self, contract);
620
+ const args = expr.args.map((arg) => renderExpr(arg, contract));
621
+ let result = expr.lowering.template;
622
+ result = result.replace(/\{\{self\}\}/g, self);
623
+ for (let i = 0; i < args.length; i++) result = result.replace(new RegExp(`\\{\\{arg${i}\\}\\}`, "g"), args[i] ?? "");
624
+ return result;
625
+ }
626
+ function renderSubqueryExpr(expr, contract) {
627
+ if (expr.query.projection.length !== 1) throw new Error("Subquery expressions must project exactly one column");
628
+ if (contract === void 0) throw new Error("Subquery expression rendering requires a Sqlite contract");
629
+ return `(${renderSelect(expr.query, contract)})`;
630
+ }
631
+ function renderNullCheck(expr, contract) {
632
+ const rendered = renderExpr(expr.expr, contract);
633
+ const renderedExpr = expr.expr.kind === "operation" || expr.expr.kind === "subquery" ? `(${rendered})` : rendered;
634
+ return expr.isNull ? `${renderedExpr} IS NULL` : `${renderedExpr} IS NOT NULL`;
635
+ }
636
+ function renderBinary(expr, contract) {
637
+ if (expr.right.kind === "list" && expr.right.values.length === 0) {
638
+ if (expr.op === "in") return "FALSE";
639
+ if (expr.op === "notIn") return "TRUE";
640
+ }
641
+ const leftExpr = expr.left;
642
+ const left = renderExpr(leftExpr, contract);
643
+ const leftRendered = leftExpr.kind === "operation" || leftExpr.kind === "subquery" ? `(${left})` : left;
644
+ const rightNode = expr.right;
645
+ let right;
646
+ switch (rightNode.kind) {
647
+ case "list":
648
+ right = renderListLiteral(rightNode);
649
+ break;
650
+ case "literal":
651
+ right = renderLiteral(rightNode);
652
+ break;
653
+ case "column-ref":
654
+ right = renderColumn(rightNode);
655
+ break;
656
+ case "param-ref":
657
+ case "prepared-param-ref":
658
+ right = "?";
659
+ break;
660
+ default:
661
+ right = renderExpr(rightNode, contract);
662
+ break;
663
+ }
664
+ return `${leftRendered} ${{
665
+ eq: "=",
666
+ neq: "!=",
667
+ gt: ">",
668
+ lt: "<",
669
+ gte: ">=",
670
+ lte: "<=",
671
+ like: "LIKE",
672
+ in: "IN",
673
+ notIn: "NOT IN"
674
+ }[expr.op]} ${right}`;
675
+ }
676
+ function renderListLiteral(expr) {
677
+ if (expr.values.length === 0) return "(NULL)";
678
+ return `(${expr.values.map((v) => {
679
+ if (v.kind === "param-ref" || v.kind === "prepared-param-ref") return "?";
680
+ if (v.kind === "literal") return renderLiteral(v);
681
+ return renderExpr(v);
682
+ }).join(", ")})`;
683
+ }
684
+ function renderAggregateExpr(expr, contract) {
685
+ const fn = expr.fn.toUpperCase();
686
+ if (!expr.expr) return `${fn}(*)`;
687
+ return `${fn}(${renderExpr(expr.expr, contract)})`;
688
+ }
689
+ function renderWindowFuncExpr(expr, contract) {
690
+ return `${expr.fn.toUpperCase()}(${expr.args.map((arg) => renderExpr(arg, contract)).join(", ")}) OVER (${[expr.partitionBy && expr.partitionBy.length > 0 ? `PARTITION BY ${expr.partitionBy.map((e) => renderExpr(e, contract)).join(", ")}` : "", expr.orderBy && expr.orderBy.length > 0 ? `ORDER BY ${renderOrderByItems(expr.orderBy, contract)}` : ""].filter((part) => part.length > 0).join(" ")})`;
691
+ }
692
+ function renderJsonObjectExpr(expr, contract) {
693
+ return `json_object(${expr.entries.flatMap((entry) => {
694
+ const key = `'${escapeLiteral(entry.key)}'`;
695
+ if (entry.value.kind === "literal") return [key, renderLiteral(entry.value)];
696
+ return [key, renderExpr(entry.value, contract)];
697
+ }).join(", ")})`;
698
+ }
699
+ function renderOrderByItems(items, contract) {
700
+ return items.map((item) => `${renderExpr(item.expr, contract)} ${item.dir.toUpperCase()}`).join(", ");
701
+ }
702
+ function renderJsonArrayAggExpr(expr, contract) {
703
+ const aggregateOrderBy = expr.orderBy && expr.orderBy.length > 0 ? ` ORDER BY ${renderOrderByItems(expr.orderBy, contract)}` : "";
704
+ const aggregated = `json_group_array(${renderExpr(expr.expr, contract)}${aggregateOrderBy})`;
705
+ if (expr.onEmpty === "emptyArray") return `coalesce(${aggregated}, '[]')`;
706
+ return aggregated;
707
+ }
708
+ function renderJoin(join, contract) {
709
+ if (contract === void 0) throw new Error("JOIN rendering requires a Sqlite contract");
710
+ return `${join.joinType.toUpperCase()} JOIN ${renderSource(join.source, contract)} ON ${renderJoinOn(join.on, contract)}`;
711
+ }
712
+ function renderJoinOn(on, contract) {
713
+ if (on.kind === "eq-col-join-on") return `${renderColumn(on.left)} = ${renderColumn(on.right)}`;
714
+ return renderExpr(on, contract);
715
+ }
716
+ function renderInsertValue(value) {
717
+ switch (value.kind) {
718
+ case "param-ref":
719
+ case "prepared-param-ref": return "?";
720
+ case "column-ref": return renderColumn(value);
721
+ case "raw-expr": return renderExpr(value);
722
+ case "default-value": throw new Error("SQLite does not support DEFAULT as a value in INSERT ... VALUES");
723
+ default: throw new Error(`Unsupported value node in INSERT: ${value.kind}`);
724
+ }
725
+ }
726
+ function renderInsert(ast, contract) {
727
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
728
+ const rows = ast.rows;
729
+ if (rows.length === 0) throw new Error("INSERT requires at least one row");
730
+ const firstRow = rows[0];
731
+ const columnOrder = Object.keys(firstRow);
732
+ let insertClause;
733
+ if (columnOrder.length === 0) insertClause = `INSERT INTO ${table} DEFAULT VALUES`;
734
+ else {
735
+ const columns = columnOrder.map((column) => quoteIdentifier(column));
736
+ const values = rows.map((row) => {
737
+ return `(${columnOrder.map((column) => {
738
+ const value = row[column];
739
+ if (value === void 0) throw new Error(`Missing value for column "${column}" in INSERT row`);
740
+ return renderInsertValue(value);
741
+ }).join(", ")})`;
742
+ }).join(", ");
743
+ insertClause = `INSERT INTO ${table} (${columns.join(", ")}) VALUES ${values}`;
744
+ }
745
+ let onConflictClause = "";
746
+ if (ast.onConflict) {
747
+ const conflictColumns = ast.onConflict.columns.map((col) => quoteIdentifier(col.column));
748
+ if (conflictColumns.length === 0) throw new Error("INSERT onConflict requires at least one conflict column");
749
+ const action = ast.onConflict.action;
750
+ switch (action.kind) {
751
+ case "do-nothing":
752
+ onConflictClause = ` ON CONFLICT (${conflictColumns.join(", ")}) DO NOTHING`;
753
+ break;
754
+ case "do-update-set": {
755
+ const updates = Object.entries(action.set).map(([colName, value]) => {
756
+ return `${quoteIdentifier(colName)} = ${renderExpr(value, contract)}`;
757
+ });
758
+ onConflictClause = ` ON CONFLICT (${conflictColumns.join(", ")}) DO UPDATE SET ${updates.join(", ")}`;
759
+ break;
760
+ }
761
+ default: throw new Error(`Unsupported onConflict action: ${action.kind}`);
762
+ }
763
+ }
764
+ const returningClause = renderReturning(ast.returning);
765
+ return `${insertClause}${onConflictClause}${returningClause}`;
766
+ }
767
+ function renderUpdate(ast, contract) {
768
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
769
+ const setClauses = Object.entries(ast.set).map(([col, val]) => {
770
+ return `${quoteIdentifier(col)} = ${renderExpr(val, contract)}`;
771
+ });
772
+ const whereClause = ast.where ? ` WHERE ${renderExpr(ast.where, contract)}` : "";
773
+ const returningClause = renderReturning(ast.returning);
774
+ return `UPDATE ${table} SET ${setClauses.join(", ")}${whereClause}${returningClause}`;
775
+ }
776
+ function renderDelete(ast, contract) {
777
+ return `DELETE FROM ${qualifyTableFromNamespaceCoordinate(ast.table, contract)}${ast.where ? ` WHERE ${renderExpr(ast.where)}` : ""}${renderReturning(ast.returning)}`;
778
+ }
779
+ function renderReturning(returning) {
780
+ if (!returning?.length) return "";
781
+ return ` RETURNING ${returning.map((item) => {
782
+ if (item.expr.kind === "column-ref") {
783
+ const rendered = `${quoteIdentifier(item.expr.table)}.${quoteIdentifier(item.expr.column)}`;
784
+ return item.expr.column === item.alias ? rendered : `${rendered} AS ${quoteIdentifier(item.alias)}`;
785
+ }
786
+ return `${renderExpr(item.expr)} AS ${quoteIdentifier(item.alias)}`;
787
+ }).join(", ")}`;
788
+ }
789
+ function createSqliteAdapter(options) {
790
+ return Object.freeze(new SqliteAdapterImpl(options));
791
+ }
792
+ //#endregion
793
+ export { sqliteRawCodecInferer as n, SqliteControlAdapter as r, createSqliteAdapter as t };
794
+
795
+ //# sourceMappingURL=adapter-DCwhDr2I.mjs.map