litedbmodel 1.2.10 → 2.0.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.
Files changed (143) hide show
  1. package/README.md +1 -1
  2. package/dist/DBModel.d.ts.map +1 -1
  3. package/dist/DBModel.js +6 -0
  4. package/dist/DBModel.js.map +1 -1
  5. package/dist/scp/authoring-sql.d.ts +134 -0
  6. package/dist/scp/authoring-sql.d.ts.map +1 -0
  7. package/dist/scp/authoring-sql.js +191 -0
  8. package/dist/scp/authoring-sql.js.map +1 -0
  9. package/dist/scp/authoring.d.ts +155 -0
  10. package/dist/scp/authoring.d.ts.map +1 -0
  11. package/dist/scp/authoring.js +162 -0
  12. package/dist/scp/authoring.js.map +1 -0
  13. package/dist/scp/catalog.d.ts +91 -0
  14. package/dist/scp/catalog.d.ts.map +1 -0
  15. package/dist/scp/catalog.js +230 -0
  16. package/dist/scp/catalog.js.map +1 -0
  17. package/dist/scp/codegen.d.ts +95 -0
  18. package/dist/scp/codegen.d.ts.map +1 -0
  19. package/dist/scp/codegen.js +94 -0
  20. package/dist/scp/codegen.js.map +1 -0
  21. package/dist/scp/dialect.d.ts +141 -0
  22. package/dist/scp/dialect.d.ts.map +1 -0
  23. package/dist/scp/dialect.js +194 -0
  24. package/dist/scp/dialect.js.map +1 -0
  25. package/dist/scp/errors.d.ts +36 -0
  26. package/dist/scp/errors.d.ts.map +1 -0
  27. package/dist/scp/errors.js +72 -0
  28. package/dist/scp/errors.js.map +1 -0
  29. package/dist/scp/find-filter-guard.d.ts +68 -0
  30. package/dist/scp/find-filter-guard.d.ts.map +1 -0
  31. package/dist/scp/find-filter-guard.js +88 -0
  32. package/dist/scp/find-filter-guard.js.map +1 -0
  33. package/dist/scp/fragment/assemble.d.ts +61 -0
  34. package/dist/scp/fragment/assemble.d.ts.map +1 -0
  35. package/dist/scp/fragment/assemble.js +148 -0
  36. package/dist/scp/fragment/assemble.js.map +1 -0
  37. package/dist/scp/fragment/compile-pg.d.ts +107 -0
  38. package/dist/scp/fragment/compile-pg.d.ts.map +1 -0
  39. package/dist/scp/fragment/compile-pg.js +142 -0
  40. package/dist/scp/fragment/compile-pg.js.map +1 -0
  41. package/dist/scp/fragment/index.d.ts +16 -0
  42. package/dist/scp/fragment/index.d.ts.map +1 -0
  43. package/dist/scp/fragment/index.js +34 -0
  44. package/dist/scp/fragment/index.js.map +1 -0
  45. package/dist/scp/fragment/model.d.ts +119 -0
  46. package/dist/scp/fragment/model.d.ts.map +1 -0
  47. package/dist/scp/fragment/model.js +66 -0
  48. package/dist/scp/fragment/model.js.map +1 -0
  49. package/dist/scp/fragment/pg-array-type.d.ts +13 -0
  50. package/dist/scp/fragment/pg-array-type.d.ts.map +1 -0
  51. package/dist/scp/fragment/pg-array-type.js +34 -0
  52. package/dist/scp/fragment/pg-array-type.js.map +1 -0
  53. package/dist/scp/guard.d.ts +43 -0
  54. package/dist/scp/guard.d.ts.map +1 -0
  55. package/dist/scp/guard.js +134 -0
  56. package/dist/scp/guard.js.map +1 -0
  57. package/dist/scp/index.cjs +12202 -0
  58. package/dist/scp/index.d.ts +64 -0
  59. package/dist/scp/index.d.ts.map +1 -0
  60. package/dist/scp/index.js +238 -0
  61. package/dist/scp/index.js.map +1 -0
  62. package/dist/scp/index.mjs +4819 -0
  63. package/dist/scp/makesql/authoring-compile.d.ts +90 -0
  64. package/dist/scp/makesql/authoring-compile.d.ts.map +1 -0
  65. package/dist/scp/makesql/authoring-compile.js +494 -0
  66. package/dist/scp/makesql/authoring-compile.js.map +1 -0
  67. package/dist/scp/makesql/compile-crud.d.ts +133 -0
  68. package/dist/scp/makesql/compile-crud.d.ts.map +1 -0
  69. package/dist/scp/makesql/compile-crud.js +288 -0
  70. package/dist/scp/makesql/compile-crud.js.map +1 -0
  71. package/dist/scp/makesql/compile-relation.d.ts +157 -0
  72. package/dist/scp/makesql/compile-relation.d.ts.map +1 -0
  73. package/dist/scp/makesql/compile-relation.js +425 -0
  74. package/dist/scp/makesql/compile-relation.js.map +1 -0
  75. package/dist/scp/makesql/compile-select.d.ts +45 -0
  76. package/dist/scp/makesql/compile-select.d.ts.map +1 -0
  77. package/dist/scp/makesql/compile-select.js +58 -0
  78. package/dist/scp/makesql/compile-select.js.map +1 -0
  79. package/dist/scp/makesql/compile.d.ts +63 -0
  80. package/dist/scp/makesql/compile.d.ts.map +1 -0
  81. package/dist/scp/makesql/compile.js +99 -0
  82. package/dist/scp/makesql/compile.js.map +1 -0
  83. package/dist/scp/makesql/handler.d.ts +58 -0
  84. package/dist/scp/makesql/handler.d.ts.map +1 -0
  85. package/dist/scp/makesql/handler.js +113 -0
  86. package/dist/scp/makesql/handler.js.map +1 -0
  87. package/dist/scp/makesql/index.d.ts +33 -0
  88. package/dist/scp/makesql/index.d.ts.map +1 -0
  89. package/dist/scp/makesql/index.js +114 -0
  90. package/dist/scp/makesql/index.js.map +1 -0
  91. package/dist/scp/makesql/ir.d.ts +42 -0
  92. package/dist/scp/makesql/ir.d.ts.map +1 -0
  93. package/dist/scp/makesql/ir.js +66 -0
  94. package/dist/scp/makesql/ir.js.map +1 -0
  95. package/dist/scp/makesql/json-array.d.ts +84 -0
  96. package/dist/scp/makesql/json-array.d.ts.map +1 -0
  97. package/dist/scp/makesql/json-array.js +118 -0
  98. package/dist/scp/makesql/json-array.js.map +1 -0
  99. package/dist/scp/makesql/json-batch.d.ts +102 -0
  100. package/dist/scp/makesql/json-batch.d.ts.map +1 -0
  101. package/dist/scp/makesql/json-batch.js +269 -0
  102. package/dist/scp/makesql/json-batch.js.map +1 -0
  103. package/dist/scp/makesql/makesql.d.ts +99 -0
  104. package/dist/scp/makesql/makesql.d.ts.map +1 -0
  105. package/dist/scp/makesql/makesql.js +122 -0
  106. package/dist/scp/makesql/makesql.js.map +1 -0
  107. package/dist/scp/makesql/pool-executor.d.ts +47 -0
  108. package/dist/scp/makesql/pool-executor.d.ts.map +1 -0
  109. package/dist/scp/makesql/pool-executor.js +50 -0
  110. package/dist/scp/makesql/pool-executor.js.map +1 -0
  111. package/dist/scp/makesql/static-bundle.d.ts +185 -0
  112. package/dist/scp/makesql/static-bundle.d.ts.map +1 -0
  113. package/dist/scp/makesql/static-bundle.js +1024 -0
  114. package/dist/scp/makesql/static-bundle.js.map +1 -0
  115. package/dist/scp/makesql/tx.d.ts +216 -0
  116. package/dist/scp/makesql/tx.d.ts.map +1 -0
  117. package/dist/scp/makesql/tx.js +743 -0
  118. package/dist/scp/makesql/tx.js.map +1 -0
  119. package/dist/scp/relation.d.ts +148 -0
  120. package/dist/scp/relation.d.ts.map +1 -0
  121. package/dist/scp/relation.js +257 -0
  122. package/dist/scp/relation.js.map +1 -0
  123. package/dist/scp/runtime.d.ts +231 -0
  124. package/dist/scp/runtime.d.ts.map +1 -0
  125. package/dist/scp/runtime.js +381 -0
  126. package/dist/scp/runtime.js.map +1 -0
  127. package/dist/scp/typed-object.d.ts +99 -0
  128. package/dist/scp/typed-object.d.ts.map +1 -0
  129. package/dist/scp/typed-object.js +184 -0
  130. package/dist/scp/typed-object.js.map +1 -0
  131. package/dist/scp/writes.d.ts +251 -0
  132. package/dist/scp/writes.d.ts.map +1 -0
  133. package/dist/scp/writes.js +176 -0
  134. package/dist/scp/writes.js.map +1 -0
  135. package/docs/proposal/contracts-architecture-feasibility.md +328 -0
  136. package/docs/proposal/contracts-architecture.md +579 -0
  137. package/docs/proposal/litedbmodel-v2-scp-architecture.md +382 -0
  138. package/docs/proposal/sql-dynamic-expansion-spec.md +175 -0
  139. package/docs/proposal/v0-coverage-matrix.md +157 -0
  140. package/docs/proposal/v2-completeness-plan.md +90 -0
  141. package/docs/proposal/v2-sql-parity-checklist.md +162 -0
  142. package/package.json +27 -5
  143. package/docs/README_REVIEW.md +0 -23
@@ -0,0 +1,4819 @@
1
+ // src/scp/makesql/makesql.ts
2
+ var MAKESQL = "makeSQL";
3
+ function isMakeSQL(p) {
4
+ return typeof p === "object" && p !== null && typeof p.sql === "string" && Array.isArray(p.params);
5
+ }
6
+ var makeSqlCatalogEntry = {
7
+ name: MAKESQL,
8
+ inputPorts: {
9
+ sql: { type: "string", required: true },
10
+ params: { type: "arr" },
11
+ skip: { type: "bool" }
12
+ },
13
+ output: { shape: "items" },
14
+ portableToIR: true
15
+ };
16
+ var LITEDBMODEL_MAKESQL_CATALOG = {
17
+ [MAKESQL]: makeSqlCatalogEntry
18
+ };
19
+ function assembleMakeSQL(node) {
20
+ if (node.skip === true) return { sql: "", params: [] };
21
+ const chunks = node.sql.split("?");
22
+ if (chunks.length - 1 !== node.params.length) {
23
+ throw new Error(
24
+ `makeSQL placeholder/param mismatch: ${chunks.length - 1} '?' vs ${node.params.length} params in ${JSON.stringify(node.sql)}`
25
+ );
26
+ }
27
+ let sql2 = chunks[0];
28
+ const params = [];
29
+ for (let i = 0; i < node.params.length; i++) {
30
+ const p = node.params[i];
31
+ if (isMakeSQL(p)) {
32
+ const inner = assembleMakeSQL(p);
33
+ sql2 += inner.sql + chunks[i + 1];
34
+ params.push(...inner.params);
35
+ } else {
36
+ sql2 += "?" + chunks[i + 1];
37
+ params.push(p);
38
+ }
39
+ }
40
+ return { sql: sql2, params };
41
+ }
42
+ function composeMakeSQL(nodes) {
43
+ let sql2 = "";
44
+ const params = [];
45
+ for (const node of nodes) {
46
+ const r = assembleMakeSQL(node);
47
+ sql2 += r.sql;
48
+ params.push(...r.params);
49
+ }
50
+ return { sql: sql2, params };
51
+ }
52
+
53
+ // src/scp/makesql/handler.ts
54
+ function renderPlaceholders(sql2, dialect) {
55
+ if (dialect !== "postgres") return sql2;
56
+ let out = "";
57
+ let index = 0;
58
+ let inString = false;
59
+ for (let i = 0; i < sql2.length; i++) {
60
+ const ch = sql2[i];
61
+ if (inString) {
62
+ out += ch;
63
+ if (ch === "'") inString = false;
64
+ } else if (ch === "'") {
65
+ out += ch;
66
+ inString = true;
67
+ } else if (ch === "?") {
68
+ out += `$${++index}`;
69
+ } else {
70
+ out += ch;
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+ function toMakeSQL(ports) {
76
+ return {
77
+ sql: ports.sql,
78
+ params: ports.params ?? [],
79
+ skip: ports.skip === true
80
+ };
81
+ }
82
+ function renderPorts(ports, dialect) {
83
+ const node = toMakeSQL(ports);
84
+ const assembled = assembleMakeSQL(node);
85
+ return { sql: renderPlaceholders(assembled.sql, dialect), params: assembled.params };
86
+ }
87
+ function makeSqlHandler(exec, dialect) {
88
+ return async (ports, _ctx) => {
89
+ if (ports.skip === true) return { ok: [] };
90
+ const { sql: sql2, params } = renderPorts(ports, dialect);
91
+ const rows = await exec(sql2, params);
92
+ return { ok: rows };
93
+ };
94
+ }
95
+ function makeSqlHandlerSync(exec, dialect) {
96
+ return (ports, _ctx) => {
97
+ if (ports.skip === true) return { ok: [] };
98
+ const { sql: sql2, params } = renderPorts(ports, dialect);
99
+ const rows = exec(sql2, params);
100
+ return { ok: rows };
101
+ };
102
+ }
103
+
104
+ // src/DBValues.ts
105
+ var DBToken = class {
106
+ value;
107
+ operator;
108
+ constructor(value, operator = "=") {
109
+ this.value = value;
110
+ this.operator = operator;
111
+ }
112
+ /**
113
+ * Compile the token to SQL fragment
114
+ * @param params - Parameter array to append values
115
+ * @param key - Column name (optional)
116
+ * @param _formatter - Optional formatter (used by DBCast subclasses)
117
+ * @returns SQL fragment
118
+ */
119
+ compile(params, key, _formatter) {
120
+ params.push(this.value);
121
+ if (key) {
122
+ return `${key} ${this.operator} ?`;
123
+ }
124
+ return "?";
125
+ }
126
+ };
127
+ var DBImmediateValue = class extends DBToken {
128
+ constructor(value) {
129
+ super(value, "=");
130
+ }
131
+ compile(_params, key) {
132
+ if (key) {
133
+ return `${key} ${this.operator} ${this.value}`;
134
+ }
135
+ return String(this.value);
136
+ }
137
+ };
138
+ var DBDynamicValue = class extends DBToken {
139
+ func;
140
+ values;
141
+ constructor(func, values = []) {
142
+ super(null, "=");
143
+ this.func = func;
144
+ this.values = values;
145
+ }
146
+ compile(params, key) {
147
+ for (const val of this.values) {
148
+ params.push(val);
149
+ }
150
+ if (key) {
151
+ return `${key} ${this.operator} ${this.func}`;
152
+ }
153
+ return this.func;
154
+ }
155
+ };
156
+ var DBTupleIn = class extends DBToken {
157
+ columns;
158
+ tuples;
159
+ constructor(columns, tuples) {
160
+ super(null, "IN");
161
+ this.columns = columns;
162
+ this.tuples = tuples;
163
+ }
164
+ compile(params, _key) {
165
+ if (this.tuples.length === 0) {
166
+ return "1 = 0";
167
+ }
168
+ const tuplePlaceholders = this.tuples.map((tuple) => {
169
+ const placeholders = tuple.map((val) => {
170
+ params.push(val);
171
+ return "?";
172
+ });
173
+ return `(${placeholders.join(", ")})`;
174
+ });
175
+ return `(${this.columns.join(", ")}) IN (${tuplePlaceholders.join(", ")})`;
176
+ }
177
+ };
178
+ function dbDynamic(func, values = []) {
179
+ return new DBDynamicValue(func, values);
180
+ }
181
+ function dbImmediate(value) {
182
+ return new DBImmediateValue(value);
183
+ }
184
+ var defaultFormatter = (placeholder, sqlType) => `${placeholder}::${sqlType}`;
185
+ var DBCast = class extends DBToken {
186
+ /** The SQL type to cast to (e.g., 'uuid', 'jsonb', 'text[]') */
187
+ sqlType;
188
+ constructor(value, sqlType, operator = "=") {
189
+ super(value, operator);
190
+ this.sqlType = sqlType;
191
+ }
192
+ /**
193
+ * Compile to SQL fragment with type cast
194
+ * @param params - Parameter array to append values
195
+ * @param key - Column name (optional)
196
+ * @param formatter - Optional function to format placeholder with cast (driver-specific)
197
+ */
198
+ compile(params, key, formatter) {
199
+ params.push(this.value);
200
+ const format = formatter ?? defaultFormatter;
201
+ const placeholder = format("?", this.sqlType);
202
+ if (key) {
203
+ return `${key} ${this.operator} ${placeholder}`;
204
+ }
205
+ return placeholder;
206
+ }
207
+ };
208
+ var DBCastArray = class extends DBToken {
209
+ /** The SQL type to cast to */
210
+ sqlType;
211
+ constructor(values, sqlType) {
212
+ super(values, "IN");
213
+ this.sqlType = sqlType;
214
+ }
215
+ /**
216
+ * Compile to SQL fragment
217
+ * @param params - Parameter array to append values
218
+ * @param key - Column name (optional)
219
+ * @param formatter - Optional function to format placeholder with cast (driver-specific)
220
+ */
221
+ compile(params, key, formatter) {
222
+ const arr = this.value;
223
+ if (arr.length === 0) {
224
+ return "1 = 0";
225
+ }
226
+ const format = formatter ?? defaultFormatter;
227
+ const placeholder = format("?", this.sqlType);
228
+ const placeholders = arr.map((v) => {
229
+ params.push(v);
230
+ return placeholder;
231
+ });
232
+ if (key) {
233
+ return `${key} IN (${placeholders.join(", ")})`;
234
+ }
235
+ return `(${placeholders.join(", ")})`;
236
+ }
237
+ };
238
+ function dbCast(value, sqlType, operator = "=") {
239
+ return new DBCast(value, sqlType, operator);
240
+ }
241
+ function dbTupleIn(columns, tuples) {
242
+ return new DBTupleIn(columns, tuples);
243
+ }
244
+ var DBParentRef = class extends DBToken {
245
+ /** The parent column name */
246
+ columnName;
247
+ /** The parent table name */
248
+ tableName;
249
+ constructor(column) {
250
+ super(null, "=");
251
+ this.columnName = column.columnName;
252
+ this.tableName = column.tableName;
253
+ }
254
+ /**
255
+ * Compile to SQL - returns table.column format
256
+ */
257
+ compile(_params, _key) {
258
+ return `${this.tableName}.${this.columnName}`;
259
+ }
260
+ };
261
+ var DBSubquery = class extends DBToken {
262
+ constructor(parentColumns, tableName, selectColumns, conditions, subqueryOperator = "IN") {
263
+ super(null, subqueryOperator);
264
+ this.parentColumns = parentColumns;
265
+ this.tableName = tableName;
266
+ this.selectColumns = selectColumns;
267
+ this.conditions = conditions;
268
+ this.subqueryOperator = subqueryOperator;
269
+ }
270
+ parentColumns;
271
+ tableName;
272
+ selectColumns;
273
+ conditions;
274
+ subqueryOperator;
275
+ /** Format column as table.column */
276
+ formatColumn(col) {
277
+ return `${col.tableName}.${col.columnName}`;
278
+ }
279
+ /**
280
+ * Compile to SQL with parameter binding
281
+ */
282
+ compile(params, _key) {
283
+ const whereParts = [];
284
+ for (const cond of this.conditions) {
285
+ const colRef = this.formatColumn(cond.column);
286
+ if (cond.value instanceof DBParentRef) {
287
+ whereParts.push(`${colRef} = ${cond.value.compile(params)}`);
288
+ } else if (cond.value instanceof DBToken) {
289
+ whereParts.push(cond.value.compile(params, colRef));
290
+ } else if (cond.value === null) {
291
+ whereParts.push(`${colRef} IS NULL`);
292
+ } else if (Array.isArray(cond.value)) {
293
+ if (cond.value.length === 0) {
294
+ whereParts.push("1 = 0");
295
+ } else {
296
+ const placeholders = cond.value.map((v) => {
297
+ params.push(v);
298
+ return "?";
299
+ });
300
+ whereParts.push(`${colRef} IN (${placeholders.join(", ")})`);
301
+ }
302
+ } else {
303
+ params.push(cond.value);
304
+ whereParts.push(`${colRef} = ?`);
305
+ }
306
+ }
307
+ const whereClause2 = whereParts.length > 0 ? ` WHERE ${whereParts.join(" AND ")}` : "";
308
+ const selectClause = this.selectColumns.map((c) => this.formatColumn(c)).join(", ");
309
+ const subquery = `SELECT ${selectClause} FROM ${this.tableName}${whereClause2}`;
310
+ if (this.parentColumns.length === 1) {
311
+ return `${this.formatColumn(this.parentColumns[0])} ${this.subqueryOperator} (${subquery})`;
312
+ } else {
313
+ const parentColList = this.parentColumns.map((c) => this.formatColumn(c)).join(", ");
314
+ return `(${parentColList}) ${this.subqueryOperator} (${subquery})`;
315
+ }
316
+ }
317
+ };
318
+ var DBExists = class extends DBToken {
319
+ constructor(tableName, conditions, notExists = false) {
320
+ super(null, notExists ? "NOT EXISTS" : "EXISTS");
321
+ this.tableName = tableName;
322
+ this.conditions = conditions;
323
+ this.notExists = notExists;
324
+ }
325
+ tableName;
326
+ conditions;
327
+ notExists;
328
+ /** Format column as table.column */
329
+ formatColumn(col) {
330
+ return `${col.tableName}.${col.columnName}`;
331
+ }
332
+ /**
333
+ * Compile to SQL with parameter binding
334
+ */
335
+ compile(params, _key) {
336
+ const whereParts = [];
337
+ for (const cond of this.conditions) {
338
+ const colRef = this.formatColumn(cond.column);
339
+ if (cond.value instanceof DBParentRef) {
340
+ whereParts.push(`${colRef} = ${cond.value.compile(params)}`);
341
+ } else if (cond.value instanceof DBToken) {
342
+ whereParts.push(cond.value.compile(params, colRef));
343
+ } else if (cond.value === null) {
344
+ whereParts.push(`${colRef} IS NULL`);
345
+ } else if (Array.isArray(cond.value)) {
346
+ if (cond.value.length === 0) {
347
+ whereParts.push("1 = 0");
348
+ } else {
349
+ const placeholders = cond.value.map((v) => {
350
+ params.push(v);
351
+ return "?";
352
+ });
353
+ whereParts.push(`${colRef} IN (${placeholders.join(", ")})`);
354
+ }
355
+ } else {
356
+ params.push(cond.value);
357
+ whereParts.push(`${colRef} = ?`);
358
+ }
359
+ }
360
+ const whereClause2 = whereParts.length > 0 ? ` WHERE ${whereParts.join(" AND ")}` : "";
361
+ const subquery = `SELECT 1 FROM ${this.tableName}${whereClause2}`;
362
+ return `${this.operator} (${subquery})`;
363
+ }
364
+ };
365
+
366
+ // src/DBConditions.ts
367
+ var DBConditions = class _DBConditions {
368
+ conditions;
369
+ operator;
370
+ nested;
371
+ constructor(conditions = {}, operator = "AND") {
372
+ this.conditions = conditions;
373
+ this.operator = operator;
374
+ this.nested = [];
375
+ }
376
+ /**
377
+ * Add conditions with specified operator
378
+ */
379
+ add(conditions, operator = "AND") {
380
+ if (conditions instanceof _DBConditions) {
381
+ this.nested.push({ conditions, operator });
382
+ } else {
383
+ const cond = new _DBConditions(conditions, operator);
384
+ this.nested.push({ conditions: cond, operator });
385
+ }
386
+ return this;
387
+ }
388
+ /**
389
+ * Compile conditions to SQL WHERE clause
390
+ * @param params - Parameter array to append values
391
+ * @param formatter - Optional SQL cast formatter (for driver-specific casting)
392
+ * @returns SQL WHERE clause (without 'WHERE' keyword)
393
+ */
394
+ compile(params, formatter) {
395
+ const parts = [];
396
+ for (const [key, value] of Object.entries(this.conditions)) {
397
+ const sql2 = this.compileCondition(key, value, params, formatter);
398
+ if (sql2) {
399
+ parts.push(sql2);
400
+ }
401
+ }
402
+ for (const nested of this.nested) {
403
+ const nestedSql = nested.conditions.compile(params, formatter);
404
+ if (nestedSql) {
405
+ parts.push(`(${nestedSql})`);
406
+ }
407
+ }
408
+ if (parts.length === 0) {
409
+ return "";
410
+ }
411
+ return parts.join(` ${this.operator} `);
412
+ }
413
+ /**
414
+ * Compile a single condition
415
+ */
416
+ compileCondition(key, value, params, formatter) {
417
+ if (key === "__or__" && Array.isArray(value)) {
418
+ const orParts = [];
419
+ for (const group of value) {
420
+ const groupCond = new _DBConditions(group, "AND");
421
+ const groupSql = groupCond.compile(params, formatter);
422
+ if (groupSql) {
423
+ orParts.push(`(${groupSql})`);
424
+ }
425
+ }
426
+ if (orParts.length > 0) {
427
+ return `(${orParts.join(" OR ")})`;
428
+ }
429
+ return "";
430
+ }
431
+ if (key === "__exists__" && value instanceof DBExists) {
432
+ return value.compile(params);
433
+ }
434
+ if (key === "__subquery__" && value instanceof DBSubquery) {
435
+ return value.compile(params);
436
+ }
437
+ if (key === "__raw__") {
438
+ if (typeof value === "string") {
439
+ return value;
440
+ }
441
+ if (Array.isArray(value)) {
442
+ if (value.length === 2 && typeof value[0] === "string" && Array.isArray(value[1])) {
443
+ const [sql2, rawParams] = value;
444
+ params.push(...rawParams);
445
+ return sql2;
446
+ }
447
+ const rawParts = value.filter((v) => typeof v === "string");
448
+ if (rawParts.length > 0) {
449
+ return rawParts.join(" AND ");
450
+ }
451
+ }
452
+ return "";
453
+ }
454
+ if (value instanceof DBToken) {
455
+ return value.compile(params, key, formatter);
456
+ }
457
+ if (key.startsWith("__")) {
458
+ if (value instanceof _DBConditions) {
459
+ return `(${value.compile(params, formatter)})`;
460
+ }
461
+ return "";
462
+ }
463
+ if (value instanceof _DBConditions) {
464
+ return `(${value.compile(params, formatter)})`;
465
+ }
466
+ if (value === null) {
467
+ return `${key} IS NULL`;
468
+ }
469
+ if (typeof value === "boolean") {
470
+ if (value && key.includes(" ")) {
471
+ return key;
472
+ }
473
+ return `${key} = ${value ? "TRUE" : "FALSE"}`;
474
+ }
475
+ if (Array.isArray(value)) {
476
+ if (key.includes("?")) {
477
+ return this.compileCustomOperator(key, value, params);
478
+ }
479
+ if (value.length === 0) {
480
+ return "1 = 0";
481
+ }
482
+ const placeholders = value.map((v) => {
483
+ params.push(v);
484
+ return "?";
485
+ });
486
+ return `${key} IN (${placeholders.join(", ")})`;
487
+ }
488
+ if (key.includes("?")) {
489
+ return this.compileCustomOperator(key, value, params);
490
+ }
491
+ params.push(value);
492
+ return `${key} = ?`;
493
+ }
494
+ /**
495
+ * Compile custom operator condition
496
+ * Key already contains ? placeholders, just push params in order
497
+ */
498
+ compileCustomOperator(key, value, params) {
499
+ if (Array.isArray(value)) {
500
+ for (const v of value) {
501
+ params.push(v);
502
+ }
503
+ } else {
504
+ params.push(value);
505
+ }
506
+ return key;
507
+ }
508
+ /**
509
+ * Check if conditions are empty
510
+ */
511
+ isEmpty() {
512
+ return Object.keys(this.conditions).length === 0 && this.nested.length === 0;
513
+ }
514
+ /**
515
+ * Get the raw conditions object
516
+ */
517
+ getConditions() {
518
+ return this.conditions;
519
+ }
520
+ };
521
+
522
+ // src/scp/makesql/json-array.ts
523
+ function inListJson(dialect, col, values) {
524
+ if (dialect === "mysql") {
525
+ return {
526
+ sql: `${col} IN (SELECT JSON_UNQUOTE(v) FROM JSON_TABLE(?, '$[*]' COLUMNS(v JSON PATH '$')) jt)`,
527
+ // A BOOLEAN element serializes to `1`/`0` in the MySQL JSON param (NOT JSON `true`/`false`):
528
+ // `JSON_UNQUOTE(v)` on JSON `true` yields the STRING `'true'`, which MySQL coerces to `0`
529
+ // against a TINYINT(1) — silently mismatching. `1`/`0` is exactly what v1's `col IN (?)`
530
+ // bound (the mysql2 driver sends a JS bool as `1`/`0`), so this keeps v1 RESULT parity. SQL
531
+ // text + PG are untouched; SQLite's `json_each` coerces JSON booleans natively (no change).
532
+ param: JSON.stringify(values.map((v) => typeof v === "boolean" ? v ? 1 : 0 : v))
533
+ };
534
+ }
535
+ return { sql: `${col} IN (SELECT value FROM json_each(?))`, param: JSON.stringify(values) };
536
+ }
537
+ var JsonArrayConditions = class extends DBConditions {
538
+ _dialect;
539
+ constructor(conditions, dialect, operator = "AND") {
540
+ super(conditions, operator);
541
+ this._dialect = dialect;
542
+ }
543
+ compileCondition(key, value, params, formatter) {
544
+ if (Array.isArray(value) && value.length > 0 && !key.includes("?") && !key.startsWith("__") && !(value instanceof DBToken)) {
545
+ const { sql: sql2, param } = inListJson(this._dialect, key, value);
546
+ params.push(param);
547
+ return sql2;
548
+ }
549
+ return super.compileCondition(key, value, params, formatter);
550
+ }
551
+ };
552
+ function conditionsFor(conditions, dialect) {
553
+ if (dialect === "postgres") return new DBConditions(conditions);
554
+ return new JsonArrayConditions(conditions, dialect);
555
+ }
556
+
557
+ // src/scp/makesql/compile.ts
558
+ var pgCastFormatter = (placeholder, sqlType) => `${placeholder}::${sqlType}`;
559
+ var noCastFormatter = (placeholder) => placeholder;
560
+ function formatterFor(dialect) {
561
+ return dialect === "postgres" ? pgCastFormatter : noCastFormatter;
562
+ }
563
+ function compileWhere(conditions, dialect) {
564
+ const params = [];
565
+ const formatter = formatterFor(dialect);
566
+ const core = conditionsFor(conditions, dialect).compile(params, formatter);
567
+ return { sql: core, params };
568
+ }
569
+ function whereClause(where) {
570
+ if (where.sql === "") return { sql: "", params: [] };
571
+ return { sql: ` WHERE ${where.sql}`, params: where.params };
572
+ }
573
+ function andTrailing(head, tails) {
574
+ const guardedTails = tails.map((t) => ({
575
+ sql: ` AND ${t.sql}`,
576
+ params: t.params,
577
+ ...t.skip !== void 0 ? { skip: t.skip } : {}
578
+ }));
579
+ return [head, ...guardedTails];
580
+ }
581
+
582
+ // src/SqlFragment.ts
583
+ var SqlRaw = class {
584
+ constructor(value) {
585
+ this.value = value;
586
+ }
587
+ value;
588
+ _tag = "SqlRaw";
589
+ toString() {
590
+ return this.value;
591
+ }
592
+ };
593
+ var SqlRef = class {
594
+ _tag = "SqlRef";
595
+ tableName;
596
+ columnName;
597
+ constructor(column) {
598
+ this.tableName = column.tableName;
599
+ this.columnName = column.columnName;
600
+ }
601
+ toString() {
602
+ return `${this.tableName}.${this.columnName}`;
603
+ }
604
+ };
605
+ function isSqlFragment(value) {
606
+ return typeof value === "object" && value !== null && "_tag" in value && value._tag === "SqlFragment";
607
+ }
608
+ function isSqlTypedFragment(value) {
609
+ return typeof value === "object" && value !== null && "_tag" in value && value._tag === "SqlTypedFragment";
610
+ }
611
+ function isSqlCondition(value) {
612
+ return typeof value === "object" && value !== null && "_tag" in value && value._tag === "SqlCondition";
613
+ }
614
+ function isAnySqlFragment(value) {
615
+ return isSqlFragment(value) || isSqlTypedFragment(value) || isSqlCondition(value);
616
+ }
617
+ function isSqlRaw(value) {
618
+ return value instanceof SqlRaw;
619
+ }
620
+ function isSqlRef(value) {
621
+ return value instanceof SqlRef;
622
+ }
623
+ function hasTableName(value) {
624
+ return (typeof value === "object" || typeof value === "function") && value !== null && "TABLE_NAME" in value && typeof value.TABLE_NAME === "string" && !isColumn(value);
625
+ }
626
+ function isIdentifierLike(val) {
627
+ return isColumn(val) || hasTableName(val) || isSqlRaw(val) || isSqlRef(val) || val instanceof DBParentRef;
628
+ }
629
+ function processInterpolation(val, sqlParts, params) {
630
+ if (isColumn(val)) {
631
+ sqlParts.push(val.columnName);
632
+ } else if (hasTableName(val)) {
633
+ sqlParts.push(val.TABLE_NAME);
634
+ } else if (isSqlRaw(val)) {
635
+ sqlParts.push(val.value);
636
+ } else if (isSqlRef(val)) {
637
+ sqlParts.push(`${val.tableName}.${val.columnName}`);
638
+ } else if (val instanceof DBParentRef) {
639
+ sqlParts.push(`${val.tableName}.${val.columnName}`);
640
+ } else if (isAnySqlFragment(val)) {
641
+ sqlParts.push(val.sql);
642
+ if ("params" in val && Array.isArray(val.params)) {
643
+ params.push(...val.params);
644
+ }
645
+ } else if (Array.isArray(val)) {
646
+ if (val.length === 0) {
647
+ sqlParts.push("NULL");
648
+ } else {
649
+ const placeholders = val.map(() => "?");
650
+ for (const v of val) {
651
+ params.push(v);
652
+ }
653
+ sqlParts.push(placeholders.join(", "));
654
+ }
655
+ } else if (val === null || val === void 0) {
656
+ sqlParts.push("NULL");
657
+ } else {
658
+ params.push(val);
659
+ sqlParts.push("?");
660
+ }
661
+ }
662
+ function sql(strings, ...values) {
663
+ let firstColumn = null;
664
+ let columnCount = 0;
665
+ let valueCount = 0;
666
+ let otherIdentifierCount = 0;
667
+ for (const val of values) {
668
+ if (isColumn(val)) {
669
+ columnCount++;
670
+ if (!firstColumn) firstColumn = val;
671
+ } else if (isIdentifierLike(val)) {
672
+ otherIdentifierCount++;
673
+ } else if (isAnySqlFragment(val)) {
674
+ otherIdentifierCount++;
675
+ } else {
676
+ valueCount++;
677
+ }
678
+ }
679
+ const isConditionPattern = columnCount === 1 && otherIdentifierCount === 0 && isColumn(values[0]);
680
+ if (isConditionPattern && valueCount === 0 && values.length === 1) {
681
+ const col = values[0];
682
+ const sqlStr = strings[0] + col.columnName + strings[1];
683
+ return {
684
+ _tag: "SqlTypedFragment",
685
+ sql: sqlStr,
686
+ params: []
687
+ };
688
+ }
689
+ const sqlParts = [];
690
+ const params = [];
691
+ for (let i = 0; i < strings.length; i++) {
692
+ sqlParts.push(strings[i]);
693
+ if (i < values.length) {
694
+ processInterpolation(values[i], sqlParts, params);
695
+ }
696
+ }
697
+ const sqlString = sqlParts.join("");
698
+ if (isConditionPattern && valueCount > 0) {
699
+ return {
700
+ _tag: "SqlCondition",
701
+ sql: sqlString,
702
+ params
703
+ };
704
+ }
705
+ return {
706
+ _tag: "SqlFragment",
707
+ sql: sqlString,
708
+ params
709
+ };
710
+ }
711
+ sql.raw = function(value) {
712
+ return new SqlRaw(value);
713
+ };
714
+ sql.ref = function(column) {
715
+ return new SqlRef(column);
716
+ };
717
+
718
+ // src/Column.ts
719
+ function orderToString(order) {
720
+ if (!order) return void 0;
721
+ if (typeof order === "string") return order;
722
+ if (Array.isArray(order)) {
723
+ return order.map((o) => o.toString()).join(", ");
724
+ }
725
+ return order.toString();
726
+ }
727
+ function isColumn(value) {
728
+ return typeof value === "function" && "_brand" in value && value._brand === "Column";
729
+ }
730
+ var SKIP = /* @__PURE__ */ Symbol.for("litedbmodel.SKIP");
731
+ function isOrCond(cond) {
732
+ return typeof cond === "object" && cond !== null && "_type" in cond && cond._type === "or";
733
+ }
734
+ function condToKeyValue(cond) {
735
+ if (cond.length === 1) {
736
+ return [cond[0], true];
737
+ }
738
+ const [keyOrCol, value] = cond;
739
+ if (value === SKIP) {
740
+ return null;
741
+ }
742
+ if (Array.isArray(keyOrCol)) {
743
+ const columns = keyOrCol;
744
+ const tuples = value;
745
+ const columnNames = columns.map((col2) => col2.columnName);
746
+ return ["__tuple__", dbTupleIn(columnNames, tuples)];
747
+ }
748
+ if (isSqlTypedFragment(keyOrCol)) {
749
+ return [keyOrCol.sql, value];
750
+ }
751
+ if (typeof keyOrCol === "string") {
752
+ return [keyOrCol, value];
753
+ }
754
+ const col = keyOrCol;
755
+ const sqlCast = col.sqlCast;
756
+ if (value instanceof DBToken) {
757
+ return [col.columnName, value];
758
+ }
759
+ if (sqlCast) {
760
+ if (Array.isArray(value)) {
761
+ return [col.columnName, new DBCastArray(value, sqlCast)];
762
+ }
763
+ return [col.columnName, new DBCast(value, sqlCast, "=")];
764
+ }
765
+ return [col.columnName, value];
766
+ }
767
+ function sqlFragmentToKeyValue(frag) {
768
+ if (frag.params.length === 0) {
769
+ return [frag.sql, true];
770
+ }
771
+ if (frag.params.length === 1) {
772
+ return [frag.sql, frag.params[0]];
773
+ }
774
+ return [frag.sql, [...frag.params]];
775
+ }
776
+ function condsToRecord(conditions) {
777
+ const result = {};
778
+ for (const cond of conditions) {
779
+ if (cond === SKIP) {
780
+ continue;
781
+ }
782
+ if (isOrCond(cond)) {
783
+ const orGroups = cond.conditions.map((group) => condsToRecord(group));
784
+ result["__or__"] = orGroups;
785
+ } else if (isAnySqlFragment(cond)) {
786
+ const kv = sqlFragmentToKeyValue(cond);
787
+ if (kv !== null) {
788
+ const [key, value] = kv;
789
+ result[key] = value;
790
+ }
791
+ } else {
792
+ const kv = condToKeyValue(cond);
793
+ if (kv === null) continue;
794
+ const [key, value] = kv;
795
+ result[key] = value;
796
+ }
797
+ }
798
+ return result;
799
+ }
800
+
801
+ // src/scp/makesql/compile-select.ts
802
+ function compileSelect(desc) {
803
+ const params = [];
804
+ const selectCols = desc.select ?? "*";
805
+ const formatter = formatterFor(desc.dialect);
806
+ if (desc.cte?.params && desc.cte.params.length > 0) params.push(...desc.cte.params);
807
+ if (desc.joinParams && desc.joinParams.length > 0) params.push(...desc.joinParams);
808
+ const whereClause2 = conditionsFor(desc.conditions ?? {}, desc.dialect).compile(params, formatter);
809
+ let sql2 = "";
810
+ if (desc.cte) sql2 = `WITH ${desc.cte.name} AS (${desc.cte.sql}) `;
811
+ sql2 += `SELECT ${selectCols} FROM ${desc.tableName}`;
812
+ if (desc.join) sql2 += ` ${desc.join}`;
813
+ if (whereClause2) sql2 += ` WHERE ${whereClause2}`;
814
+ if (desc.group) sql2 += ` GROUP BY ${desc.group}`;
815
+ const orderClause = typeof desc.order === "string" ? desc.order : orderToString(desc.order);
816
+ if (orderClause) sql2 += ` ORDER BY ${orderClause}`;
817
+ if (desc.limit !== void 0) sql2 += ` LIMIT ${desc.limit}`;
818
+ if (desc.offset !== void 0) sql2 += ` OFFSET ${desc.offset}`;
819
+ if (desc.forUpdate) sql2 += " FOR UPDATE";
820
+ if (desc.append) sql2 += ` ${desc.append}`;
821
+ return { sql: sql2, params };
822
+ }
823
+
824
+ // src/TypeCast.ts
825
+ function formatUTCDate(d) {
826
+ const y = d.getUTCFullYear();
827
+ const m = String(d.getUTCMonth() + 1).padStart(2, "0");
828
+ const day = String(d.getUTCDate()).padStart(2, "0");
829
+ return `${y}-${m}-${day}`;
830
+ }
831
+
832
+ // src/drivers/PostgresSqlBuilder.ts
833
+ var postgresTypeCast = {
834
+ driverName: "postgres",
835
+ serializeArray(val) {
836
+ if (val === null || val === void 0) return null;
837
+ const escaped = val.map((v) => {
838
+ if (v === null) return "NULL";
839
+ if (typeof v === "string") {
840
+ const esc = v.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
841
+ if (v.includes(",") || v.includes("{") || v.includes("}") || v.includes('"') || v.includes(" ")) {
842
+ return `"${esc}"`;
843
+ }
844
+ return esc;
845
+ }
846
+ return String(v);
847
+ });
848
+ return `{${escaped.join(",")}}`;
849
+ },
850
+ deserializeArray(val) {
851
+ if (val === null || val === void 0) return null;
852
+ if (Array.isArray(val)) return val;
853
+ return null;
854
+ },
855
+ serializeJson(val) {
856
+ if (val === null || val === void 0) return null;
857
+ return JSON.stringify(val);
858
+ },
859
+ deserializeJson(val) {
860
+ if (val === null || val === void 0) return null;
861
+ if (typeof val === "object") return val;
862
+ if (typeof val === "string") {
863
+ try {
864
+ return JSON.parse(val);
865
+ } catch {
866
+ return null;
867
+ }
868
+ }
869
+ return null;
870
+ },
871
+ serializeBooleanArray(val) {
872
+ if (val === null || val === void 0) return null;
873
+ const mapped = val.map((v) => v === null ? "NULL" : v ? "t" : "f");
874
+ return `{${mapped.join(",")}}`;
875
+ },
876
+ deserializeBooleanArray(val) {
877
+ if (val === null || val === void 0) return null;
878
+ if (Array.isArray(val)) return val;
879
+ return null;
880
+ },
881
+ /**
882
+ * PostgreSQL: serialize to ISO 8601 UTC string for explicit timezone handling
883
+ * This ensures TIMESTAMP WITH TIME ZONE stores the correct UTC moment
884
+ */
885
+ serializeDatetime(val) {
886
+ return val.toISOString();
887
+ },
888
+ /**
889
+ * PostgreSQL: serialize to UTC date string (YYYY-MM-DD)
890
+ * Uses UTC components to avoid timezone-related date shifts
891
+ */
892
+ serializeDate(val) {
893
+ return formatUTCDate(val);
894
+ }
895
+ };
896
+ function getArrayElementType(arrayType) {
897
+ if (!arrayType.endsWith("[]")) return arrayType;
898
+ return arrayType.slice(0, -2);
899
+ }
900
+ function buildJsonToTypeExpr(colRef, pgType) {
901
+ if (pgType === "jsonb" || pgType === "json") {
902
+ return `${colRef}::${pgType}`;
903
+ }
904
+ if (pgType.endsWith("[]")) {
905
+ const elemType = getArrayElementType(pgType);
906
+ return `CASE WHEN ${colRef} IS NULL THEN NULL ELSE COALESCE((SELECT array_agg(elem::${elemType}) FROM jsonb_array_elements_text(${colRef}::jsonb) AS elem), ARRAY[]::${pgType}) END`;
907
+ }
908
+ return colRef;
909
+ }
910
+ function inferPgType(val) {
911
+ if (val === null || val === void 0) return "text";
912
+ if (typeof val === "number") {
913
+ return Number.isInteger(val) ? "int" : "numeric";
914
+ }
915
+ if (typeof val === "bigint") return "bigint";
916
+ if (typeof val === "boolean") return "boolean";
917
+ if (typeof val === "string") return "text";
918
+ if (val instanceof Date) return "timestamp";
919
+ if (Array.isArray(val)) {
920
+ if (val.length === 0) return "text[]";
921
+ const first = val.find((v) => v !== null && v !== void 0);
922
+ if (first === void 0) return "text[]";
923
+ if (typeof first === "number") {
924
+ return Number.isInteger(first) ? "int[]" : "numeric[]";
925
+ }
926
+ if (typeof first === "boolean") return "boolean[]";
927
+ if (typeof first === "string") return "text[]";
928
+ if (first instanceof Date) return "timestamp[]";
929
+ return "jsonb";
930
+ }
931
+ if (typeof val === "object") return "jsonb";
932
+ return "text";
933
+ }
934
+ var postgresSqlBuilder = {
935
+ driverType: "postgres",
936
+ typeCast: postgresTypeCast,
937
+ supportsReturning: true,
938
+ buildInsert(options) {
939
+ const {
940
+ tableName,
941
+ columns,
942
+ records,
943
+ rawRecords,
944
+ sqlCastMap = /* @__PURE__ */ new Map(),
945
+ onConflict,
946
+ onConflictIgnore,
947
+ onConflictUpdate,
948
+ returning
949
+ } = options;
950
+ const params = [];
951
+ if (records.length > 1) {
952
+ const unnestArrays = [];
953
+ const selectExprs = [];
954
+ let hasDBToken = false;
955
+ for (const col of columns) {
956
+ const sqlCast = sqlCastMap.get(col);
957
+ let pgType = sqlCast;
958
+ if (!pgType && rawRecords) {
959
+ for (const r of rawRecords) {
960
+ const val = r[col];
961
+ if (val !== null && val !== void 0 && !(val instanceof DBToken)) {
962
+ pgType = inferPgType(val);
963
+ break;
964
+ }
965
+ }
966
+ }
967
+ pgType = pgType || "text";
968
+ const isArrayType = pgType.endsWith("[]") && pgType !== "jsonb" && pgType !== "json";
969
+ const isJsonType = pgType === "jsonb" || pgType === "json";
970
+ const colValues = records.map((r, idx) => {
971
+ const serializedVal = r[col];
972
+ if (serializedVal instanceof DBToken) {
973
+ hasDBToken = true;
974
+ return serializedVal;
975
+ }
976
+ if (isArrayType) {
977
+ const rawVal = rawRecords?.[idx]?.[col];
978
+ if (rawVal === null || rawVal === void 0) return null;
979
+ return JSON.stringify(rawVal);
980
+ }
981
+ return serializedVal;
982
+ });
983
+ if (hasDBToken) break;
984
+ params.push(colValues);
985
+ if (isArrayType) {
986
+ unnestArrays.push(`?::text[]`);
987
+ selectExprs.push(buildJsonToTypeExpr(`v.${col}`, pgType));
988
+ } else if (isJsonType) {
989
+ unnestArrays.push(`?::text[]`);
990
+ selectExprs.push(`v.${col}::${pgType}`);
991
+ } else {
992
+ unnestArrays.push(`?::${pgType}[]`);
993
+ selectExprs.push(`v.${col}`);
994
+ }
995
+ }
996
+ if (!hasDBToken) {
997
+ let sql3 = `INSERT INTO ${tableName} (${columns.join(", ")}) SELECT ${selectExprs.join(", ")} FROM UNNEST(${unnestArrays.join(", ")}) AS v(${columns.join(", ")})`;
998
+ if (onConflict) {
999
+ sql3 += ` ON CONFLICT (${onConflict.join(", ")})`;
1000
+ if (onConflictIgnore) {
1001
+ sql3 += " DO NOTHING";
1002
+ } else if (onConflictUpdate) {
1003
+ const updateCols = onConflictUpdate === "all" ? columns : onConflictUpdate;
1004
+ const updateClauses = updateCols.map((c) => `${c} = EXCLUDED.${c}`);
1005
+ sql3 += ` DO UPDATE SET ${updateClauses.join(", ")}`;
1006
+ }
1007
+ }
1008
+ if (returning) {
1009
+ sql3 += ` RETURNING ${returning}`;
1010
+ }
1011
+ return { sql: sql3, params };
1012
+ }
1013
+ params.length = 0;
1014
+ }
1015
+ const valueRows = [];
1016
+ for (const record of records) {
1017
+ const rowValues = [];
1018
+ for (const col of columns) {
1019
+ const val = record[col];
1020
+ if (val instanceof DBToken) {
1021
+ rowValues.push(val.compile(params));
1022
+ } else {
1023
+ params.push(val);
1024
+ const sqlCast = sqlCastMap.get(col);
1025
+ if (sqlCast && sqlCast !== "timestamp" && sqlCast !== "date") {
1026
+ rowValues.push(`?::${sqlCast}`);
1027
+ } else {
1028
+ rowValues.push("?");
1029
+ }
1030
+ }
1031
+ }
1032
+ valueRows.push(`(${rowValues.join(", ")})`);
1033
+ }
1034
+ let sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES ${valueRows.join(", ")}`;
1035
+ if (onConflict) {
1036
+ sql2 += ` ON CONFLICT (${onConflict.join(", ")})`;
1037
+ if (onConflictIgnore) {
1038
+ sql2 += " DO NOTHING";
1039
+ } else if (onConflictUpdate) {
1040
+ const updateCols = onConflictUpdate === "all" ? columns : onConflictUpdate;
1041
+ const updateClauses = updateCols.map((c) => `${c} = EXCLUDED.${c}`);
1042
+ sql2 += ` DO UPDATE SET ${updateClauses.join(", ")}`;
1043
+ }
1044
+ }
1045
+ if (returning) {
1046
+ sql2 += ` RETURNING ${returning}`;
1047
+ }
1048
+ return { sql: sql2, params };
1049
+ },
1050
+ buildUpdateMany(options) {
1051
+ const {
1052
+ tableName,
1053
+ keyColumns,
1054
+ updateColumns,
1055
+ records,
1056
+ rawRecords,
1057
+ sqlCastMap = /* @__PURE__ */ new Map(),
1058
+ skipMap = /* @__PURE__ */ new Map(),
1059
+ returning
1060
+ } = options;
1061
+ const params = [];
1062
+ const allColumns = [...keyColumns, ...updateColumns];
1063
+ const skipColumnsNeeded = /* @__PURE__ */ new Set();
1064
+ for (const skipSet of skipMap.values()) {
1065
+ for (const col of skipSet) {
1066
+ skipColumnsNeeded.add(col);
1067
+ }
1068
+ }
1069
+ const unnestArrays = [];
1070
+ const colTypes = /* @__PURE__ */ new Map();
1071
+ for (const col of allColumns) {
1072
+ const sqlCast = sqlCastMap.get(col);
1073
+ let pgType = sqlCast;
1074
+ if (!pgType && rawRecords) {
1075
+ for (const r of rawRecords) {
1076
+ const val = r[col];
1077
+ if (val !== null && val !== void 0 && !(val instanceof DBToken)) {
1078
+ pgType = inferPgType(val);
1079
+ break;
1080
+ }
1081
+ }
1082
+ }
1083
+ pgType = pgType || "text";
1084
+ colTypes.set(col, pgType);
1085
+ const isArrayType = pgType.endsWith("[]") && pgType !== "jsonb" && pgType !== "json";
1086
+ const isJsonType = pgType === "jsonb" || pgType === "json";
1087
+ const colValues = records.map((r, idx) => {
1088
+ if (skipMap.get(idx)?.has(col)) {
1089
+ return null;
1090
+ }
1091
+ const serializedVal = r[col];
1092
+ if (isArrayType) {
1093
+ const rawVal = rawRecords?.[idx]?.[col];
1094
+ if (rawVal === null || rawVal === void 0) return null;
1095
+ return JSON.stringify(rawVal);
1096
+ }
1097
+ if (isJsonType && serializedVal !== null && serializedVal !== void 0) {
1098
+ return typeof serializedVal === "string" ? serializedVal : JSON.stringify(serializedVal);
1099
+ }
1100
+ return serializedVal;
1101
+ });
1102
+ params.push(colValues);
1103
+ if (isArrayType || isJsonType) {
1104
+ unnestArrays.push(`?::text[]`);
1105
+ } else {
1106
+ const castType = pgType === "timestamp" ? "timestamptz" : pgType;
1107
+ unnestArrays.push(`?::${castType}[]`);
1108
+ }
1109
+ }
1110
+ const skipFlagCols = [];
1111
+ for (const col of skipColumnsNeeded) {
1112
+ skipFlagCols.push(`_skip_${col}`);
1113
+ const flagValues = records.map((_, idx) => skipMap.get(idx)?.has(col) ?? false);
1114
+ params.push(flagValues);
1115
+ unnestArrays.push(`?::boolean[]`);
1116
+ }
1117
+ const setClauses = [];
1118
+ for (const col of updateColumns) {
1119
+ const pgType = colTypes.get(col);
1120
+ const isArrayType = pgType.endsWith("[]") && pgType !== "jsonb" && pgType !== "json";
1121
+ const isJsonType = pgType === "jsonb" || pgType === "json";
1122
+ let valueExpr;
1123
+ if (isArrayType) {
1124
+ valueExpr = buildJsonToTypeExpr(`v.${col}`, pgType);
1125
+ } else if (isJsonType) {
1126
+ valueExpr = `v.${col}::${pgType}`;
1127
+ } else {
1128
+ valueExpr = `v.${col}`;
1129
+ }
1130
+ if (skipColumnsNeeded.has(col)) {
1131
+ setClauses.push(`${col} = CASE WHEN v._skip_${col} THEN t.${col} ELSE ${valueExpr} END`);
1132
+ } else {
1133
+ setClauses.push(`${col} = ${valueExpr}`);
1134
+ }
1135
+ }
1136
+ const whereConditions = keyColumns.map((col) => `t.${col} = v.${col}`);
1137
+ const allUnnestCols = [...allColumns, ...skipFlagCols];
1138
+ let sql2 = `UPDATE ${tableName} AS t SET ${setClauses.join(", ")} FROM UNNEST(${unnestArrays.join(", ")}) AS v(${allUnnestCols.join(", ")}) WHERE ${whereConditions.join(" AND ")}`;
1139
+ if (returning) {
1140
+ sql2 += ` RETURNING ${returning}`;
1141
+ }
1142
+ return { sql: sql2, params };
1143
+ },
1144
+ buildFindByPkeys(options) {
1145
+ const { tableName, pkeyColumns, pkeyValues, selectColumn, sqlCastMap } = options;
1146
+ const params = [];
1147
+ if (pkeyColumns.length === 1) {
1148
+ const colValues = pkeyValues.map((v) => v[0]);
1149
+ params.push(colValues);
1150
+ const sqlCast = sqlCastMap?.get(pkeyColumns[0]);
1151
+ const pgType = sqlCast || inferPgType(colValues.find((v) => v !== null && v !== void 0));
1152
+ const sql3 = `SELECT ${selectColumn} FROM ${tableName} WHERE ${pkeyColumns[0]} = ANY(?::${pgType}[])`;
1153
+ return { sql: sql3, params };
1154
+ }
1155
+ const unnestArrays = [];
1156
+ for (let i = 0; i < pkeyColumns.length; i++) {
1157
+ const colValues = pkeyValues.map((v) => v[i]);
1158
+ params.push(colValues);
1159
+ const sqlCast = sqlCastMap?.get(pkeyColumns[i]);
1160
+ const pgType = sqlCast || inferPgType(colValues.find((v) => v !== null && v !== void 0));
1161
+ unnestArrays.push(`?::${pgType}[]`);
1162
+ }
1163
+ const sql2 = `SELECT ${selectColumn} FROM ${tableName} WHERE (${pkeyColumns.join(", ")}) IN (SELECT * FROM UNNEST(${unnestArrays.join(", ")}))`;
1164
+ return { sql: sql2, params };
1165
+ },
1166
+ buildReturning(_tableName, columns, alias) {
1167
+ const prefix = alias ? `${alias}.` : "";
1168
+ return columns.map((col) => `${prefix}${col}`).join(", ");
1169
+ },
1170
+ inferPgType
1171
+ };
1172
+
1173
+ // src/drivers/MysqlSqlBuilder.ts
1174
+ var mysqlTypeCast = {
1175
+ driverName: "mysql",
1176
+ serializeArray(val) {
1177
+ if (val === null || val === void 0) return null;
1178
+ return JSON.stringify(val);
1179
+ },
1180
+ deserializeArray(val) {
1181
+ if (val === null || val === void 0) return null;
1182
+ if (Array.isArray(val)) return val;
1183
+ if (typeof val === "string") {
1184
+ try {
1185
+ const parsed = JSON.parse(val);
1186
+ return Array.isArray(parsed) ? parsed : null;
1187
+ } catch {
1188
+ return null;
1189
+ }
1190
+ }
1191
+ return null;
1192
+ },
1193
+ serializeJson(val) {
1194
+ if (val === null || val === void 0) return null;
1195
+ return JSON.stringify(val);
1196
+ },
1197
+ deserializeJson(val) {
1198
+ if (val === null || val === void 0) return null;
1199
+ if (typeof val === "object") return val;
1200
+ if (typeof val === "string") {
1201
+ try {
1202
+ return JSON.parse(val);
1203
+ } catch {
1204
+ return null;
1205
+ }
1206
+ }
1207
+ return null;
1208
+ },
1209
+ serializeBooleanArray(val) {
1210
+ if (val === null || val === void 0) return null;
1211
+ return JSON.stringify(val);
1212
+ },
1213
+ deserializeBooleanArray(val) {
1214
+ if (val === null || val === void 0) return null;
1215
+ if (Array.isArray(val)) return val;
1216
+ if (typeof val === "string") {
1217
+ try {
1218
+ const parsed = JSON.parse(val);
1219
+ return Array.isArray(parsed) ? parsed : null;
1220
+ } catch {
1221
+ return null;
1222
+ }
1223
+ }
1224
+ return null;
1225
+ },
1226
+ /**
1227
+ * MySQL: return Date object as-is, let mysql2 driver handle conversion
1228
+ * Note: MySQL DATETIME has no timezone - values stored/retrieved in server timezone
1229
+ */
1230
+ serializeDatetime(val) {
1231
+ return val;
1232
+ },
1233
+ /**
1234
+ * MySQL: return Date object as-is, let mysql2 driver handle conversion
1235
+ */
1236
+ serializeDate(val) {
1237
+ return val;
1238
+ }
1239
+ };
1240
+ var mysqlSqlBuilder = {
1241
+ driverType: "mysql",
1242
+ typeCast: mysqlTypeCast,
1243
+ supportsReturning: false,
1244
+ buildInsert(options) {
1245
+ const {
1246
+ tableName,
1247
+ columns,
1248
+ records,
1249
+ onConflict,
1250
+ onConflictIgnore,
1251
+ onConflictUpdate,
1252
+ returning
1253
+ } = options;
1254
+ const params = [];
1255
+ const valueRows = [];
1256
+ for (const record of records) {
1257
+ const rowValues = [];
1258
+ for (const col of columns) {
1259
+ const val = record[col];
1260
+ if (val instanceof DBToken) {
1261
+ rowValues.push(val.compile(params));
1262
+ } else {
1263
+ params.push(val);
1264
+ rowValues.push("?");
1265
+ }
1266
+ }
1267
+ valueRows.push(`(${rowValues.join(", ")})`);
1268
+ }
1269
+ let sql2;
1270
+ if (onConflict) {
1271
+ if (onConflictIgnore) {
1272
+ sql2 = `INSERT IGNORE INTO ${tableName} (${columns.join(", ")}) VALUES ${valueRows.join(", ")}`;
1273
+ } else if (onConflictUpdate) {
1274
+ const updateCols = onConflictUpdate === "all" ? columns : onConflictUpdate;
1275
+ const updateClauses = updateCols.map((c) => `${c} = VALUES(${c})`);
1276
+ sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES ${valueRows.join(", ")} ON DUPLICATE KEY UPDATE ${updateClauses.join(", ")}`;
1277
+ } else {
1278
+ sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES ${valueRows.join(", ")}`;
1279
+ }
1280
+ } else {
1281
+ sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES ${valueRows.join(", ")}`;
1282
+ }
1283
+ if (returning) {
1284
+ sql2 += ` RETURNING ${returning}`;
1285
+ }
1286
+ return { sql: sql2, params };
1287
+ },
1288
+ buildUpdateMany(options) {
1289
+ const {
1290
+ tableName,
1291
+ keyColumns,
1292
+ updateColumns,
1293
+ records,
1294
+ skipMap = /* @__PURE__ */ new Map()
1295
+ } = options;
1296
+ const params = [];
1297
+ const allColumns = [...keyColumns, ...updateColumns];
1298
+ const skipColumnsNeeded = /* @__PURE__ */ new Set();
1299
+ for (const skipSet of skipMap.values()) {
1300
+ for (const col of skipSet) {
1301
+ skipColumnsNeeded.add(col);
1302
+ }
1303
+ }
1304
+ const valueRowDefs = [];
1305
+ const allValueCols = [...allColumns];
1306
+ for (const col of skipColumnsNeeded) {
1307
+ allValueCols.push(`_skip_${col}`);
1308
+ }
1309
+ for (let i = 0; i < records.length; i++) {
1310
+ const record = records[i];
1311
+ const rowValues = [];
1312
+ for (const col of allColumns) {
1313
+ const val = record[col];
1314
+ if (val instanceof DBToken) {
1315
+ rowValues.push(val.compile(params));
1316
+ } else {
1317
+ params.push(val);
1318
+ rowValues.push("?");
1319
+ }
1320
+ }
1321
+ for (const col of skipColumnsNeeded) {
1322
+ const isSkipped = skipMap.get(i)?.has(col) ?? false;
1323
+ params.push(isSkipped);
1324
+ rowValues.push("?");
1325
+ }
1326
+ valueRowDefs.push(`ROW(${rowValues.join(", ")})`);
1327
+ }
1328
+ const setClauses = [];
1329
+ for (const col of updateColumns) {
1330
+ if (skipColumnsNeeded.has(col)) {
1331
+ setClauses.push(`t.${col} = IF(v._skip_${col}, t.${col}, v.${col})`);
1332
+ } else {
1333
+ setClauses.push(`t.${col} = v.${col}`);
1334
+ }
1335
+ }
1336
+ const whereConditions = keyColumns.map((col) => `t.${col} = v.${col}`);
1337
+ const sql2 = `UPDATE ${tableName} AS t JOIN (VALUES ${valueRowDefs.join(", ")}) AS v(${allValueCols.join(", ")}) ON ${whereConditions.join(" AND ")} SET ${setClauses.join(", ")}`;
1338
+ return { sql: sql2, params };
1339
+ },
1340
+ buildSelectPkeys(options) {
1341
+ const { tableName, pkeyColumns, keyColumns, keyValues } = options;
1342
+ const params = [];
1343
+ const whereConditions = keyColumns.map((col, i) => {
1344
+ params.push(keyValues.map((kv) => kv[i]));
1345
+ return `${col} IN (?)`;
1346
+ });
1347
+ const sql2 = `SELECT DISTINCT ${pkeyColumns.join(", ")} FROM ${tableName} WHERE ${whereConditions.join(" AND ")}`;
1348
+ return { sql: sql2, params };
1349
+ },
1350
+ buildFindByPkeys(options) {
1351
+ const { tableName, pkeyColumns, pkeyValues, selectColumn } = options;
1352
+ const params = [];
1353
+ if (pkeyColumns.length === 1) {
1354
+ const placeholders = pkeyValues.map(() => "?").join(", ");
1355
+ params.push(...pkeyValues.map((v) => v[0]));
1356
+ const sql3 = `SELECT ${selectColumn} FROM ${tableName} WHERE ${pkeyColumns[0]} IN (${placeholders})`;
1357
+ return { sql: sql3, params };
1358
+ }
1359
+ const rowPlaceholders = [];
1360
+ for (const pkValueTuple of pkeyValues) {
1361
+ const rowVals = pkValueTuple.map(() => "?").join(", ");
1362
+ params.push(...pkValueTuple);
1363
+ rowPlaceholders.push(`ROW(${rowVals})`);
1364
+ }
1365
+ const onConditions = pkeyColumns.map((col) => `t.${col} = v.${col}`).join(" AND ");
1366
+ const selectCol = selectColumn === "*" ? "t.*" : selectColumn;
1367
+ const sql2 = `SELECT ${selectCol} FROM ${tableName} AS t JOIN (VALUES ${rowPlaceholders.join(", ")}) AS v(${pkeyColumns.join(", ")}) ON ${onConditions}`;
1368
+ return { sql: sql2, params };
1369
+ },
1370
+ buildReturning() {
1371
+ return void 0;
1372
+ }
1373
+ };
1374
+
1375
+ // src/drivers/SqliteSqlBuilder.ts
1376
+ var sqliteTypeCast = {
1377
+ driverName: "sqlite",
1378
+ serializeArray(val) {
1379
+ if (val === null || val === void 0) return null;
1380
+ return JSON.stringify(val);
1381
+ },
1382
+ deserializeArray(val) {
1383
+ if (val === null || val === void 0) return null;
1384
+ if (Array.isArray(val)) return val;
1385
+ if (typeof val === "string") {
1386
+ try {
1387
+ const parsed = JSON.parse(val);
1388
+ return Array.isArray(parsed) ? parsed : null;
1389
+ } catch {
1390
+ return null;
1391
+ }
1392
+ }
1393
+ return null;
1394
+ },
1395
+ serializeJson(val) {
1396
+ if (val === null || val === void 0) return null;
1397
+ return JSON.stringify(val);
1398
+ },
1399
+ deserializeJson(val) {
1400
+ if (val === null || val === void 0) return null;
1401
+ if (typeof val === "object") return val;
1402
+ if (typeof val === "string") {
1403
+ try {
1404
+ return JSON.parse(val);
1405
+ } catch {
1406
+ return null;
1407
+ }
1408
+ }
1409
+ return null;
1410
+ },
1411
+ serializeBooleanArray(val) {
1412
+ if (val === null || val === void 0) return null;
1413
+ return JSON.stringify(val);
1414
+ },
1415
+ deserializeBooleanArray(val) {
1416
+ if (val === null || val === void 0) return null;
1417
+ if (Array.isArray(val)) return val;
1418
+ if (typeof val === "string") {
1419
+ try {
1420
+ const parsed = JSON.parse(val);
1421
+ return Array.isArray(parsed) ? parsed : null;
1422
+ } catch {
1423
+ return null;
1424
+ }
1425
+ }
1426
+ return null;
1427
+ },
1428
+ /**
1429
+ * SQLite: return Date object as-is, let better-sqlite3 driver handle conversion
1430
+ * Note: SQLite stores as TEXT - values stored/retrieved in server timezone
1431
+ */
1432
+ serializeDatetime(val) {
1433
+ return val;
1434
+ },
1435
+ /**
1436
+ * SQLite: return Date object as-is, let better-sqlite3 driver handle conversion
1437
+ */
1438
+ serializeDate(val) {
1439
+ return val;
1440
+ }
1441
+ };
1442
+ var sqliteSqlBuilder = {
1443
+ driverType: "sqlite",
1444
+ typeCast: sqliteTypeCast,
1445
+ supportsReturning: true,
1446
+ buildInsert(options) {
1447
+ const {
1448
+ tableName,
1449
+ columns,
1450
+ records,
1451
+ onConflict,
1452
+ onConflictIgnore,
1453
+ onConflictUpdate,
1454
+ returning
1455
+ } = options;
1456
+ const params = [];
1457
+ const valueRows = [];
1458
+ for (const record of records) {
1459
+ const rowValues = [];
1460
+ for (const col of columns) {
1461
+ const val = record[col];
1462
+ if (val instanceof DBToken) {
1463
+ rowValues.push(val.compile(params));
1464
+ } else {
1465
+ params.push(val);
1466
+ rowValues.push("?");
1467
+ }
1468
+ }
1469
+ valueRows.push(`(${rowValues.join(", ")})`);
1470
+ }
1471
+ let sql2;
1472
+ if (onConflict) {
1473
+ if (onConflictIgnore) {
1474
+ sql2 = `INSERT OR IGNORE INTO ${tableName} (${columns.join(", ")}) VALUES ${valueRows.join(", ")}`;
1475
+ } else if (onConflictUpdate) {
1476
+ const updateCols = onConflictUpdate === "all" ? columns : onConflictUpdate;
1477
+ const updateClauses = updateCols.map((c) => `${c} = excluded.${c}`);
1478
+ sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES ${valueRows.join(", ")} ON CONFLICT (${onConflict.join(", ")}) DO UPDATE SET ${updateClauses.join(", ")}`;
1479
+ } else {
1480
+ sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES ${valueRows.join(", ")}`;
1481
+ }
1482
+ } else {
1483
+ sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES ${valueRows.join(", ")}`;
1484
+ }
1485
+ if (returning) {
1486
+ sql2 += ` RETURNING ${returning}`;
1487
+ }
1488
+ return { sql: sql2, params };
1489
+ },
1490
+ buildUpdateMany(options) {
1491
+ const {
1492
+ tableName,
1493
+ keyColumns,
1494
+ updateColumns,
1495
+ records,
1496
+ skipMap = /* @__PURE__ */ new Map(),
1497
+ returning
1498
+ } = options;
1499
+ const params = [];
1500
+ const setClauses = [];
1501
+ const keyValues = records.map((r) => keyColumns.map((k) => r[k]));
1502
+ for (const col of updateColumns) {
1503
+ const whenClauses = [];
1504
+ for (let i = 0; i < records.length; i++) {
1505
+ const record = records[i];
1506
+ const isSkipped = skipMap.get(i)?.has(col) ?? false;
1507
+ const keyConditions = keyColumns.map((k) => {
1508
+ params.push(record[k]);
1509
+ return `${k} = ?`;
1510
+ });
1511
+ const keyCondition = keyConditions.length === 1 ? keyConditions[0] : `(${keyConditions.join(" AND ")})`;
1512
+ if (isSkipped) {
1513
+ whenClauses.push(`WHEN ${keyCondition} THEN ${tableName}.${col}`);
1514
+ } else {
1515
+ const val = record[col];
1516
+ if (val instanceof DBToken) {
1517
+ whenClauses.push(`WHEN ${keyCondition} THEN ${val.compile(params)}`);
1518
+ } else {
1519
+ params.push(val);
1520
+ whenClauses.push(`WHEN ${keyCondition} THEN ?`);
1521
+ }
1522
+ }
1523
+ }
1524
+ setClauses.push(`${col} = CASE ${whenClauses.join(" ")} END`);
1525
+ }
1526
+ let whereClause2;
1527
+ if (keyColumns.length === 1) {
1528
+ const keyCol = keyColumns[0];
1529
+ const placeholders = keyValues.map(() => "?");
1530
+ whereClause2 = `${keyCol} IN (${placeholders.join(", ")})`;
1531
+ for (const kv of keyValues) {
1532
+ params.push(kv[0]);
1533
+ }
1534
+ } else {
1535
+ const tuples = keyValues.map(() => `(${keyColumns.map(() => "?").join(", ")})`);
1536
+ whereClause2 = `(${keyColumns.join(", ")}) IN (${tuples.join(", ")})`;
1537
+ for (const kv of keyValues) {
1538
+ params.push(...kv);
1539
+ }
1540
+ }
1541
+ let sql2 = `UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE ${whereClause2}`;
1542
+ if (returning) {
1543
+ sql2 += ` RETURNING ${returning}`;
1544
+ }
1545
+ return { sql: sql2, params };
1546
+ },
1547
+ buildFindByPkeys(options) {
1548
+ const { tableName, pkeyColumns, pkeyValues, selectColumn } = options;
1549
+ const params = [];
1550
+ if (pkeyColumns.length === 1) {
1551
+ const placeholders = pkeyValues.map(() => "?").join(", ");
1552
+ params.push(...pkeyValues.map((v) => v[0]));
1553
+ const sql3 = `SELECT ${selectColumn} FROM ${tableName} WHERE ${pkeyColumns[0]} IN (${placeholders})`;
1554
+ return { sql: sql3, params };
1555
+ }
1556
+ const rowPlaceholders = [];
1557
+ for (const pkValueTuple of pkeyValues) {
1558
+ const rowVals = pkValueTuple.map(() => "?").join(", ");
1559
+ params.push(...pkValueTuple);
1560
+ rowPlaceholders.push(`(${rowVals})`);
1561
+ }
1562
+ const onConditions = pkeyColumns.map((col) => `t.${col} = v.${col}`).join(" AND ");
1563
+ const selectCol = selectColumn === "*" ? "t.*" : selectColumn;
1564
+ const sql2 = `WITH v(${pkeyColumns.join(", ")}) AS (VALUES ${rowPlaceholders.join(", ")}) SELECT ${selectCol} FROM ${tableName} AS t JOIN v ON ${onConditions}`;
1565
+ return { sql: sql2, params };
1566
+ },
1567
+ buildReturning(tableName, columns) {
1568
+ return columns.map((col) => `${tableName}.${col}`).join(", ");
1569
+ }
1570
+ };
1571
+
1572
+ // src/scp/makesql/json-batch.ts
1573
+ function rowsHaveDbToken(rows, columns) {
1574
+ for (const r of rows) {
1575
+ for (const c of columns) {
1576
+ if (r[c] instanceof DBToken) return true;
1577
+ }
1578
+ }
1579
+ return false;
1580
+ }
1581
+ function mysqlJsonTableColumn(col, rows) {
1582
+ let sample;
1583
+ for (const r of rows) {
1584
+ const v = r[col];
1585
+ if (v !== null && v !== void 0) {
1586
+ sample = v;
1587
+ break;
1588
+ }
1589
+ }
1590
+ if (typeof sample === "boolean" || typeof sample === "bigint") {
1591
+ return { colType: "BIGINT", selectExpr: (ref) => ref };
1592
+ }
1593
+ if (typeof sample === "number") {
1594
+ return Number.isInteger(sample) ? { colType: "BIGINT", selectExpr: (ref) => ref } : { colType: "DECIMAL(65,30)", selectExpr: (ref) => ref };
1595
+ }
1596
+ if (sample !== null && typeof sample === "object") {
1597
+ return { colType: "JSON", selectExpr: (ref) => ref };
1598
+ }
1599
+ return { colType: "JSON", selectExpr: (ref) => `JSON_UNQUOTE(${ref})` };
1600
+ }
1601
+ function rowsToJson(rows, columns) {
1602
+ const objects = rows.map((r) => {
1603
+ const o = {};
1604
+ for (const c of columns) o[c] = r[c] === void 0 ? null : r[c];
1605
+ return o;
1606
+ });
1607
+ return JSON.stringify(objects);
1608
+ }
1609
+ function mysqlInsertJson(opts) {
1610
+ const { tableName, columns, records, onConflict, onConflictIgnore, onConflictUpdate, returning } = opts;
1611
+ const jtCols = [];
1612
+ const selectExprs = [];
1613
+ for (const col of columns) {
1614
+ const { colType, selectExpr } = mysqlJsonTableColumn(col, records);
1615
+ jtCols.push(`${col} ${colType} PATH '$.${col}'`);
1616
+ selectExprs.push(selectExpr(`jt.${col}`));
1617
+ }
1618
+ const jsonParam = rowsToJson(records, columns);
1619
+ const source = `SELECT ${selectExprs.join(", ")} FROM JSON_TABLE(?, '$[*]' COLUMNS(${jtCols.join(", ")})) jt`;
1620
+ let sql2;
1621
+ if (onConflict && onConflictIgnore) {
1622
+ sql2 = `INSERT IGNORE INTO ${tableName} (${columns.join(", ")}) ${source}`;
1623
+ } else if (onConflict && onConflictUpdate) {
1624
+ const updateCols = onConflictUpdate === "all" ? columns : onConflictUpdate;
1625
+ const updateClauses = updateCols.map((c) => `${c} = VALUES(${c})`);
1626
+ sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) ${source} ON DUPLICATE KEY UPDATE ${updateClauses.join(", ")}`;
1627
+ } else {
1628
+ sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) ${source}`;
1629
+ }
1630
+ if (returning) sql2 += ` RETURNING ${returning}`;
1631
+ return { sql: sql2, params: [jsonParam] };
1632
+ }
1633
+ function sqliteInsertJson(opts) {
1634
+ const { tableName, columns, records, onConflict, onConflictIgnore, onConflictUpdate, returning } = opts;
1635
+ const selectExprs = columns.map((c) => `json_extract(value, '$.${c}')`);
1636
+ const jsonParam = rowsToJson(records, columns);
1637
+ const source = `SELECT ${selectExprs.join(", ")} FROM json_each(?)`;
1638
+ let sql2;
1639
+ if (onConflict && onConflictIgnore) {
1640
+ sql2 = `INSERT OR IGNORE INTO ${tableName} (${columns.join(", ")}) ${source}`;
1641
+ } else if (onConflict && onConflictUpdate) {
1642
+ const updateCols = onConflictUpdate === "all" ? columns : onConflictUpdate;
1643
+ const updateClauses = updateCols.map((c) => `${c} = excluded.${c}`);
1644
+ sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) ${source} ON CONFLICT (${onConflict.join(", ")}) DO UPDATE SET ${updateClauses.join(", ")}`;
1645
+ } else {
1646
+ sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) ${source}`;
1647
+ }
1648
+ if (returning) sql2 += ` RETURNING ${returning}`;
1649
+ return { sql: sql2, params: [jsonParam] };
1650
+ }
1651
+ function mysqlUpdateManyJson(opts) {
1652
+ const { tableName, keyColumns, updateColumns, records, skipMap = /* @__PURE__ */ new Map(), returning } = opts;
1653
+ const allColumns = [...keyColumns, ...updateColumns];
1654
+ const skipColumnsNeeded = /* @__PURE__ */ new Set();
1655
+ for (const s of skipMap.values()) for (const c of s) skipColumnsNeeded.add(c);
1656
+ const skipCols = [...skipColumnsNeeded];
1657
+ const payload = records.map((r, i) => {
1658
+ const o = {};
1659
+ for (const c of allColumns) o[c] = r[c] === void 0 ? null : r[c];
1660
+ for (const c of skipCols) o[`_skip_${c}`] = skipMap.get(i)?.has(c) ? 1 : 0;
1661
+ return o;
1662
+ });
1663
+ const jsonParam = JSON.stringify(payload);
1664
+ const jtCols = [];
1665
+ const selectExprOf = /* @__PURE__ */ new Map();
1666
+ for (const col of allColumns) {
1667
+ const { colType, selectExpr } = mysqlJsonTableColumn(col, records);
1668
+ jtCols.push(`${col} ${colType} PATH '$.${col}'`);
1669
+ selectExprOf.set(col, selectExpr);
1670
+ }
1671
+ for (const c of skipCols) jtCols.push(`_skip_${c} BIGINT PATH '$._skip_${c}'`);
1672
+ const setClauses = updateColumns.map((col) => {
1673
+ const vExpr = selectExprOf.get(col)(`v.${col}`);
1674
+ return skipColumnsNeeded.has(col) ? `u.${col} = IF(v._skip_${col}, u.${col}, ${vExpr})` : `u.${col} = ${vExpr}`;
1675
+ });
1676
+ const onConditions = keyColumns.map((k) => `u.${k} = ${selectExprOf.get(k)(`v.${k}`)}`);
1677
+ let sql2 = `UPDATE ${tableName} AS u JOIN JSON_TABLE(?, '$[*]' COLUMNS(${jtCols.join(", ")})) AS v ON ${onConditions.join(" AND ")} SET ${setClauses.join(", ")}`;
1678
+ if (returning) sql2 += ` RETURNING ${returning}`;
1679
+ return { sql: sql2, params: [jsonParam] };
1680
+ }
1681
+ function sqliteUpdateManyJson(opts) {
1682
+ const { tableName, keyColumns, updateColumns, records, skipMap = /* @__PURE__ */ new Map(), returning } = opts;
1683
+ const allColumns = [...keyColumns, ...updateColumns];
1684
+ const skipColumnsNeeded = /* @__PURE__ */ new Set();
1685
+ for (const s of skipMap.values()) for (const c of s) skipColumnsNeeded.add(c);
1686
+ const skipCols = [...skipColumnsNeeded];
1687
+ const payload = records.map((r, i) => {
1688
+ const o = {};
1689
+ for (const c of allColumns) o[c] = r[c] === void 0 ? null : r[c];
1690
+ for (const c of skipCols) o[`_skip_${c}`] = skipMap.get(i)?.has(c) ? 1 : 0;
1691
+ return o;
1692
+ });
1693
+ const jsonParam = JSON.stringify(payload);
1694
+ const params = [];
1695
+ const keyMatch = keyColumns.map((k) => `json_extract(je.value, '$.${k}') = ${tableName}.${k}`).join(" AND ");
1696
+ const setClauses = updateColumns.map((col) => {
1697
+ params.push(jsonParam);
1698
+ const valueExpr = skipColumnsNeeded.has(col) ? `CASE WHEN json_extract(je.value, '$._skip_${col}') THEN ${tableName}.${col} ELSE json_extract(je.value, '$.${col}') END` : `json_extract(je.value, '$.${col}')`;
1699
+ return `${col} = (SELECT ${valueExpr} FROM json_each(?) je WHERE ${keyMatch} LIMIT 1)`;
1700
+ });
1701
+ let whereClause2;
1702
+ if (keyColumns.length === 1) {
1703
+ params.push(jsonParam);
1704
+ whereClause2 = `${keyColumns[0]} IN (SELECT json_extract(value, '$.${keyColumns[0]}') FROM json_each(?))`;
1705
+ } else {
1706
+ params.push(jsonParam);
1707
+ const match = keyColumns.map((k) => `json_extract(je.value, '$.${k}') = ${tableName}.${k}`).join(" AND ");
1708
+ whereClause2 = `EXISTS (SELECT 1 FROM json_each(?) je WHERE ${match})`;
1709
+ }
1710
+ let sql2 = `UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE ${whereClause2}`;
1711
+ if (returning) sql2 += ` RETURNING ${returning}`;
1712
+ return { sql: sql2, params };
1713
+ }
1714
+
1715
+ // src/scp/makesql/compile-crud.ts
1716
+ function builderFor(dialect) {
1717
+ switch (dialect) {
1718
+ case "postgres":
1719
+ return postgresSqlBuilder;
1720
+ case "mysql":
1721
+ return mysqlSqlBuilder;
1722
+ case "sqlite":
1723
+ return sqliteSqlBuilder;
1724
+ }
1725
+ }
1726
+ function compileInsert(dialect, options) {
1727
+ const { sql: sql2, params } = builderFor(dialect).buildInsert(options);
1728
+ return { sql: sql2, params };
1729
+ }
1730
+ function compileInsertMany(dialect, options) {
1731
+ const { records, rawRecords, tableName, sqlCastMap, onConflict, onConflictIgnore, onConflictUpdate, returning } = options;
1732
+ const grouped = /* @__PURE__ */ new Map();
1733
+ if (records.length === 1) {
1734
+ const record = records[0];
1735
+ const recordColumns = [];
1736
+ for (const key of Object.keys(record)) {
1737
+ const val = record[key];
1738
+ if (val !== void 0 && !(val instanceof DBImmediateValue && val.value === "DEFAULT")) {
1739
+ recordColumns.push(key);
1740
+ }
1741
+ }
1742
+ recordColumns.sort();
1743
+ grouped.set("_", { columns: recordColumns, records: [record], rawRecords: [rawRecords ? rawRecords[0] : record] });
1744
+ } else {
1745
+ for (let i = 0; i < records.length; i++) {
1746
+ const record = records[i];
1747
+ const recordColumns = [];
1748
+ for (const key of Object.keys(record)) {
1749
+ const val = record[key];
1750
+ if (val !== void 0 && !(val instanceof DBImmediateValue && val.value === "DEFAULT")) {
1751
+ recordColumns.push(key);
1752
+ }
1753
+ }
1754
+ recordColumns.sort();
1755
+ const patternKey = recordColumns.join(",");
1756
+ if (!grouped.has(patternKey)) {
1757
+ grouped.set(patternKey, { columns: recordColumns, records: [], rawRecords: [] });
1758
+ }
1759
+ const group = grouped.get(patternKey);
1760
+ group.records.push(record);
1761
+ group.rawRecords.push(rawRecords ? rawRecords[i] : record);
1762
+ }
1763
+ }
1764
+ const components2 = [];
1765
+ for (const { columns, records: groupRecords, rawRecords: groupRawRecords } of grouped.values()) {
1766
+ if (dialect !== "postgres" && !rowsHaveDbToken(groupRecords, columns)) {
1767
+ const jsonOpts = { tableName, columns, records: groupRecords, onConflict, onConflictIgnore, onConflictUpdate, returning };
1768
+ components2.push(dialect === "mysql" ? mysqlInsertJson(jsonOpts) : sqliteInsertJson(jsonOpts));
1769
+ continue;
1770
+ }
1771
+ components2.push(
1772
+ compileInsert(dialect, {
1773
+ tableName,
1774
+ columns,
1775
+ records: groupRecords,
1776
+ rawRecords: groupRawRecords,
1777
+ sqlCastMap,
1778
+ onConflict,
1779
+ onConflictIgnore,
1780
+ onConflictUpdate,
1781
+ returning
1782
+ })
1783
+ );
1784
+ }
1785
+ return components2;
1786
+ }
1787
+ function compileUpdateMany(dialect, options) {
1788
+ if (dialect !== "postgres") {
1789
+ const allCols = [...options.keyColumns, ...options.updateColumns];
1790
+ if (!rowsHaveDbToken(options.records, allCols)) {
1791
+ const jsonOpts = {
1792
+ tableName: options.tableName,
1793
+ keyColumns: options.keyColumns,
1794
+ updateColumns: options.updateColumns,
1795
+ records: options.records,
1796
+ skipMap: options.skipMap,
1797
+ returning: options.returning
1798
+ };
1799
+ return dialect === "mysql" ? mysqlUpdateManyJson(jsonOpts) : sqliteUpdateManyJson(jsonOpts);
1800
+ }
1801
+ }
1802
+ const { sql: sql2, params } = builderFor(dialect).buildUpdateMany(options);
1803
+ return { sql: sql2, params };
1804
+ }
1805
+ function compileUpdateSingle(opts) {
1806
+ const params = [];
1807
+ const formatter = opts.dialect === "postgres" ? formatterFor(opts.dialect) : void 0;
1808
+ const sqlCastMap = opts.sqlCastMap ?? /* @__PURE__ */ new Map();
1809
+ const setClauses = [];
1810
+ for (const [col, val] of Object.entries(opts.serializedValues)) {
1811
+ if (val instanceof DBToken) {
1812
+ setClauses.push(`${col} = ${val.compile(params, void 0, formatter)}`);
1813
+ } else {
1814
+ params.push(val);
1815
+ const sqlCast = sqlCastMap.get(col);
1816
+ if (sqlCast && formatter && sqlCast !== "timestamp" && sqlCast !== "date") {
1817
+ setClauses.push(`${col} = ${formatter("?", sqlCast)}`);
1818
+ } else {
1819
+ setClauses.push(`${col} = ?`);
1820
+ }
1821
+ }
1822
+ }
1823
+ const whereClause2 = conditionsFor(opts.conditions, opts.dialect).compile(params, formatter);
1824
+ if (!whereClause2) throw new Error("UPDATE requires conditions");
1825
+ let sql2 = `UPDATE ${opts.tableName} SET ${setClauses.join(", ")} WHERE ${whereClause2}`;
1826
+ if (opts.returning) sql2 += ` RETURNING ${opts.returning}`;
1827
+ return { sql: sql2, params };
1828
+ }
1829
+ function compileDelete(opts) {
1830
+ const params = [];
1831
+ const formatter = opts.dialect === "postgres" ? formatterFor(opts.dialect) : void 0;
1832
+ const whereClause2 = conditionsFor(opts.conditions, opts.dialect).compile(params, formatter);
1833
+ if (!whereClause2) throw new Error("DELETE requires conditions");
1834
+ let sql2 = `DELETE FROM ${opts.tableName} WHERE ${whereClause2}`;
1835
+ if (opts.returning) sql2 += ` RETURNING ${opts.returning}`;
1836
+ return { sql: sql2, params };
1837
+ }
1838
+ function compileDeleteMany(opts) {
1839
+ const { dialect, tableName, keyColumns, keys, returning } = opts;
1840
+ if (keyColumns.length === 0) throw new Error("compileDeleteMany: keyColumns must be non-empty");
1841
+ if (keys.length === 0) return [];
1842
+ if (keyColumns.length === 1) {
1843
+ const col = keyColumns[0];
1844
+ const values = keys.map((k) => k[col]);
1845
+ const conditions = { [col]: values };
1846
+ return [compileDelete({ dialect, tableName, conditions, returning })];
1847
+ }
1848
+ const grouped = /* @__PURE__ */ new Map();
1849
+ for (const key of keys) {
1850
+ const present = keyColumns.filter((c) => key[c] !== void 0).sort();
1851
+ const patternKey = present.join(",");
1852
+ if (!grouped.has(patternKey)) grouped.set(patternKey, []);
1853
+ grouped.get(patternKey).push(key);
1854
+ }
1855
+ const components2 = [];
1856
+ for (const group of grouped.values()) {
1857
+ const present = keyColumns.filter((c) => group[0][c] !== void 0);
1858
+ const conditions = {};
1859
+ for (const c of present) conditions[c] = group.map((k) => k[c]);
1860
+ components2.push(compileDelete({ dialect, tableName, conditions, returning }));
1861
+ }
1862
+ return components2;
1863
+ }
1864
+
1865
+ // src/scp/makesql/compile-relation.ts
1866
+ function inferPgArrayType(values, sqlCast) {
1867
+ if (sqlCast) return `${sqlCast}[]`;
1868
+ if (values.length === 0) return "text[]";
1869
+ const sample = values[0];
1870
+ if (typeof sample === "number") {
1871
+ if (values.every((v) => Number.isInteger(v))) return "int[]";
1872
+ return "numeric[]";
1873
+ }
1874
+ if (typeof sample === "bigint") return "bigint[]";
1875
+ if (typeof sample === "boolean") return "boolean[]";
1876
+ if (sample instanceof Date) return "timestamp[]";
1877
+ return "text[]";
1878
+ }
1879
+ var PG_ARRAY_CAST_TOKEN = "@@PG_ARRAY_CAST@@";
1880
+ function resolvePgArrayCast(sql2, values) {
1881
+ const at = sql2.indexOf(PG_ARRAY_CAST_TOKEN);
1882
+ if (at < 0) return sql2;
1883
+ return sql2.slice(0, at) + inferPgArrayType(values) + sql2.slice(at + PG_ARRAY_CAST_TOKEN.length);
1884
+ }
1885
+ function pgArrayCastType(values, sqlCast, defer) {
1886
+ if (sqlCast) return `${sqlCast}[]`;
1887
+ if (defer) return PG_ARRAY_CAST_TOKEN;
1888
+ return inferPgArrayType(values);
1889
+ }
1890
+ function compileCompositeKeyStaticUnlimited(opts) {
1891
+ const { tableName, targetKeys } = opts;
1892
+ if (opts.dialect === "postgres") {
1893
+ const unnestParams = targetKeys.map((k) => `?::${pgArrayCastType([], opts.sqlCastMap?.get(k), opts.deferPgArrayCast)}`).join(", ");
1894
+ const unnestAlias = `_unnest_${tableName}`;
1895
+ const columnAliases = targetKeys.map((k) => `_unnest_${tableName}_${k}`).join(", ");
1896
+ const joinConditions = targetKeys.map((key) => `${tableName}.${key} = ${unnestAlias}._unnest_${tableName}_${key}`).join(" AND ");
1897
+ const joinClause = `JOIN unnest(${unnestParams}) AS ${unnestAlias}(${columnAliases}) ON ${joinConditions}`;
1898
+ return compileSelect({
1899
+ dialect: opts.dialect,
1900
+ tableName,
1901
+ select: opts.select,
1902
+ join: joinClause,
1903
+ joinParams: targetKeys.map(() => [null]),
1904
+ conditions: opts.conditions,
1905
+ order: opts.order
1906
+ });
1907
+ }
1908
+ const jsonSubquery = compositeJsonMembership(opts.dialect, tableName, targetKeys);
1909
+ const conditions = { ...opts.conditions, __raw__: [jsonSubquery, [[null]]] };
1910
+ return compileSelect({
1911
+ dialect: opts.dialect,
1912
+ tableName,
1913
+ select: opts.select,
1914
+ conditions,
1915
+ order: opts.order
1916
+ });
1917
+ }
1918
+ function compositeJsonMembership(dialect, tableName, targetKeys) {
1919
+ if (dialect === "mysql") {
1920
+ const cols = targetKeys.map((_, i) => `c${i}`);
1921
+ const jtCols = cols.map((c, i) => `${c} JSON PATH '$[${i}]'`).join(", ");
1922
+ const selectCols = cols.map((c) => `JSON_UNQUOTE(${c})`).join(", ");
1923
+ const keyTuple = targetKeys.map((k) => `${tableName}.${k}`).join(", ");
1924
+ return `(${keyTuple}) IN (SELECT ${selectCols} FROM JSON_TABLE(?, '$[*]' COLUMNS(${jtCols})) jt)`;
1925
+ }
1926
+ const match = targetKeys.map((k, i) => `json_extract(je.value, '$[${i}]') = ${tableName}.${k}`).join(" AND ");
1927
+ return `EXISTS (SELECT 1 FROM json_each(?) je WHERE ${match})`;
1928
+ }
1929
+ function compileSingleKeyUnlimited(opts) {
1930
+ if (opts.dialect === "postgres") {
1931
+ const sqlCast = opts.sqlCastMap?.get(opts.targetKey);
1932
+ const pgType = pgArrayCastType(opts.values, sqlCast, opts.deferPgArrayCast);
1933
+ const conditions2 = {
1934
+ __raw__: [`${opts.tableName}.${opts.targetKey} = ANY(?::${pgType})`, [opts.values]],
1935
+ ...opts.conditions
1936
+ };
1937
+ return compileSelect({
1938
+ dialect: opts.dialect,
1939
+ tableName: opts.tableName,
1940
+ select: opts.select,
1941
+ conditions: conditions2,
1942
+ order: opts.order
1943
+ });
1944
+ }
1945
+ const conditions = { ...opts.conditions, [opts.targetKey]: opts.values };
1946
+ return compileSelect({
1947
+ dialect: opts.dialect,
1948
+ tableName: opts.tableName,
1949
+ select: opts.select,
1950
+ conditions,
1951
+ order: opts.order
1952
+ });
1953
+ }
1954
+ function compileSingleKeyLimited(opts) {
1955
+ if (opts.dialect === "postgres") {
1956
+ const sqlCast = opts.sqlCastMap?.get(opts.targetKey);
1957
+ const pgType = pgArrayCastType(opts.values, sqlCast, opts.deferPgArrayCast);
1958
+ const lateralConditions = {
1959
+ __raw__: `${opts.tableName}.${opts.targetKey} = _keys.key`,
1960
+ ...opts.conditions
1961
+ };
1962
+ const inner = compileSelect({
1963
+ dialect: opts.dialect,
1964
+ tableName: opts.tableName,
1965
+ conditions: lateralConditions,
1966
+ order: opts.order,
1967
+ limit: opts.limit
1968
+ });
1969
+ const sql2 = `SELECT ${opts.tableName}.* FROM unnest(?::${pgType}) AS _keys(key) CROSS JOIN LATERAL (${inner.sql}) ${opts.tableName}`;
1970
+ return { sql: sql2, params: [opts.values, ...inner.params] };
1971
+ }
1972
+ const orderBy = opts.order || opts.targetKey;
1973
+ const cteConditions = { [opts.targetKey]: opts.values, ...opts.conditions };
1974
+ const cte = compileSelect({
1975
+ dialect: opts.dialect,
1976
+ tableName: opts.tableName,
1977
+ select: `*, ROW_NUMBER() OVER (PARTITION BY ${opts.targetKey} ORDER BY ${orderBy}) AS _rn`,
1978
+ conditions: cteConditions
1979
+ });
1980
+ return compileSelect({
1981
+ dialect: opts.dialect,
1982
+ tableName: "ranked",
1983
+ conditions: { __raw__: `_rn <= ${opts.limit}` },
1984
+ cte: { name: "ranked", sql: cte.sql, params: cte.params }
1985
+ });
1986
+ }
1987
+ function transpose(targetKeys, tuples) {
1988
+ return targetKeys.map((_, colIndex) => tuples.map((t) => t[colIndex]));
1989
+ }
1990
+ function compileCompositeKeyUnlimited(opts) {
1991
+ const { tableName, targetKeys, tuples } = opts;
1992
+ if (opts.dialect === "postgres") {
1993
+ const columnArrays = transpose(targetKeys, tuples);
1994
+ const unnestParams = columnArrays.map((arr, i) => `?::${inferPgArrayType(arr, opts.sqlCastMap?.get(targetKeys[i]))}`).join(", ");
1995
+ const unnestAlias = `_unnest_${tableName}`;
1996
+ const columnAliases = targetKeys.map((k) => `_unnest_${tableName}_${k}`).join(", ");
1997
+ const joinConditions = targetKeys.map((key) => `${tableName}.${key} = ${unnestAlias}._unnest_${tableName}_${key}`).join(" AND ");
1998
+ const joinClause = `JOIN unnest(${unnestParams}) AS ${unnestAlias}(${columnAliases}) ON ${joinConditions}`;
1999
+ return compileSelect({
2000
+ dialect: opts.dialect,
2001
+ tableName,
2002
+ select: opts.select,
2003
+ join: joinClause,
2004
+ joinParams: columnArrays,
2005
+ conditions: opts.conditions,
2006
+ order: opts.order
2007
+ });
2008
+ }
2009
+ const tuplePlaceholders = tuples.map(() => `(${targetKeys.map(() => "?").join(", ")})`).join(", ");
2010
+ const inClause = `(${targetKeys.join(", ")}) IN (${tuplePlaceholders})`;
2011
+ const conditions = { ...opts.conditions, __raw__: [inClause, tuples.flat()] };
2012
+ return compileSelect({
2013
+ dialect: opts.dialect,
2014
+ tableName,
2015
+ select: opts.select,
2016
+ conditions,
2017
+ order: opts.order
2018
+ });
2019
+ }
2020
+ function compileCompositeKeyLimited(opts) {
2021
+ const { tableName, targetKeys, tuples, limit } = opts;
2022
+ if (opts.dialect === "postgres") {
2023
+ const columnArrays = transpose(targetKeys, tuples);
2024
+ const unnestParams = columnArrays.map((arr, i) => `?::${inferPgArrayType(arr, opts.sqlCastMap?.get(targetKeys[i]))}`).join(", ");
2025
+ const keyAliases = targetKeys.map((_, i) => `key${i}`).join(", ");
2026
+ const keyConditions = targetKeys.map((key, i) => `${tableName}.${key} = _keys.key${i}`).join(" AND ");
2027
+ const lateralConditions = { __raw__: keyConditions, ...opts.conditions };
2028
+ const inner = compileSelect({
2029
+ dialect: opts.dialect,
2030
+ tableName,
2031
+ conditions: lateralConditions,
2032
+ order: opts.order,
2033
+ limit
2034
+ });
2035
+ const sql2 = `SELECT ${tableName}.* FROM unnest(${unnestParams}) AS _keys(${keyAliases}) CROSS JOIN LATERAL (${inner.sql}) ${tableName}`;
2036
+ return { sql: sql2, params: [...columnArrays, ...inner.params] };
2037
+ }
2038
+ const orderBy = opts.order || targetKeys.join(", ");
2039
+ const partitionBy = targetKeys.join(", ");
2040
+ const tuplePlaceholders = tuples.map(() => `(${targetKeys.map(() => "?").join(", ")})`).join(", ");
2041
+ const inClause = `(${targetKeys.join(", ")}) IN (${tuplePlaceholders})`;
2042
+ const cteParams = tuples.flat();
2043
+ const cteConditions = { __raw__: [inClause, cteParams], ...opts.conditions };
2044
+ const cte = compileSelect({
2045
+ dialect: opts.dialect,
2046
+ tableName,
2047
+ select: `*, ROW_NUMBER() OVER (PARTITION BY ${partitionBy} ORDER BY ${orderBy}) AS _rn`,
2048
+ conditions: cteConditions
2049
+ });
2050
+ return compileSelect({
2051
+ dialect: opts.dialect,
2052
+ tableName: "ranked",
2053
+ conditions: { __raw__: `_rn <= ${limit}` },
2054
+ cte: { name: "ranked", sql: cte.sql, params: cte.params }
2055
+ });
2056
+ }
2057
+ function compileCompositeKeyStaticLimited(opts) {
2058
+ const { tableName, targetKeys, limit } = opts;
2059
+ if (opts.dialect === "postgres") {
2060
+ const unnestParams = targetKeys.map((k) => `?::${pgArrayCastType([], opts.sqlCastMap?.get(k), opts.deferPgArrayCast)}`).join(", ");
2061
+ const keyAliases = targetKeys.map((_, i) => `key${i}`).join(", ");
2062
+ const keyConditions = targetKeys.map((key, i) => `${tableName}.${key} = _keys.key${i}`).join(" AND ");
2063
+ const lateralConditions = { __raw__: keyConditions, ...opts.conditions };
2064
+ const inner = compileSelect({
2065
+ dialect: opts.dialect,
2066
+ tableName,
2067
+ conditions: lateralConditions,
2068
+ order: opts.order,
2069
+ limit
2070
+ });
2071
+ const sql2 = `SELECT ${tableName}.* FROM unnest(${unnestParams}) AS _keys(${keyAliases}) CROSS JOIN LATERAL (${inner.sql}) ${tableName}`;
2072
+ return { sql: sql2, params: [...targetKeys.map(() => [null]), ...inner.params] };
2073
+ }
2074
+ const orderBy = opts.order || targetKeys.join(", ");
2075
+ const partitionBy = targetKeys.join(", ");
2076
+ const jsonSubquery = compositeJsonMembership(opts.dialect, tableName, targetKeys);
2077
+ const cteConditions = { __raw__: [jsonSubquery, [[null]]], ...opts.conditions };
2078
+ const cte = compileSelect({
2079
+ dialect: opts.dialect,
2080
+ tableName,
2081
+ select: `*, ROW_NUMBER() OVER (PARTITION BY ${partitionBy} ORDER BY ${orderBy}) AS _rn`,
2082
+ conditions: cteConditions
2083
+ });
2084
+ return compileSelect({
2085
+ dialect: opts.dialect,
2086
+ tableName: "ranked",
2087
+ conditions: { __raw__: `_rn <= ${limit}` },
2088
+ cte: { name: "ranked", sql: cte.sql, params: cte.params }
2089
+ });
2090
+ }
2091
+
2092
+ // src/scp/makesql/ir.ts
2093
+ function makeSqlComponentIR(entry2 = "Query") {
2094
+ const component = {
2095
+ name: entry2,
2096
+ inputPorts: {
2097
+ __sql: { type: "string", required: true },
2098
+ __sqlParams: { type: "arr" },
2099
+ __skip: { type: "bool" }
2100
+ },
2101
+ body: [
2102
+ {
2103
+ id: "q",
2104
+ component: MAKESQL,
2105
+ ports: {
2106
+ sql: { ref: ["__sql"] },
2107
+ params: { ref: ["__sqlParams"] },
2108
+ skip: { ref: ["__skip"] }
2109
+ }
2110
+ }
2111
+ ],
2112
+ output: { ref: ["q"] }
2113
+ };
2114
+ return { irVersion: 1, exprVersion: 2, components: [component] };
2115
+ }
2116
+ function makeSqlInput(node) {
2117
+ return {
2118
+ __sql: node.sql,
2119
+ __sqlParams: node.params,
2120
+ __skip: node.skip === true
2121
+ };
2122
+ }
2123
+
2124
+ // src/scp/makesql/tx.ts
2125
+ import { evaluateExpression } from "behavior-contracts";
2126
+
2127
+ // src/scp/errors.ts
2128
+ var SqlFailure = class extends Error {
2129
+ constructor(kind, policy, sqliteCode, message) {
2130
+ super(message);
2131
+ this.kind = kind;
2132
+ this.policy = policy;
2133
+ this.sqliteCode = sqliteCode;
2134
+ this.name = "SqlFailure";
2135
+ }
2136
+ kind;
2137
+ policy;
2138
+ sqliteCode;
2139
+ };
2140
+ function isSqliteError(e) {
2141
+ return typeof e === "object" && e !== null && typeof e.code === "string" && e.code.startsWith("SQLITE_");
2142
+ }
2143
+ function mapSqliteError(e) {
2144
+ if (!isSqliteError(e)) {
2145
+ const message2 = e instanceof Error ? e.message : String(e);
2146
+ return new SqlFailure("driver_error", "fail", void 0, `non-SQLite driver error: ${message2}`);
2147
+ }
2148
+ const { code, message } = e;
2149
+ const tagged = `[${code}] ${message}`;
2150
+ if (code === "SQLITE_CONSTRAINT_FOREIGNKEY") {
2151
+ return new SqlFailure("foreign_key_violation", "fail", code, tagged);
2152
+ }
2153
+ if (code.startsWith("SQLITE_CONSTRAINT")) {
2154
+ return new SqlFailure("constraint_violation", "fail", code, tagged);
2155
+ }
2156
+ if (code === "SQLITE_BUSY" || code === "SQLITE_LOCKED") {
2157
+ return new SqlFailure("retryable", "retry", code, tagged);
2158
+ }
2159
+ return new SqlFailure("driver_error", "fail", code, tagged);
2160
+ }
2161
+
2162
+ // src/scp/writes.ts
2163
+ var ENTITY_ROOT = "__entity";
2164
+ var PATH_RE = /^\$\.(input|entity)\.([A-Za-z_][A-Za-z0-9_]*)$/;
2165
+ var REF_PATH_RE = /^\$\.ref\.([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)$/;
2166
+ function parseEffectPath(value) {
2167
+ const m = PATH_RE.exec(value);
2168
+ if (m !== null) return { root: m[1], field: m[2] };
2169
+ const r = REF_PATH_RE.exec(value);
2170
+ if (r !== null) return { root: "ref", writeName: r[1], field: r[2] };
2171
+ throw new Error(
2172
+ `write-time relations: '${value}' is not a valid path-rooted value; use '$.input.<field>' (the Command input), '$.entity.<field>' (the sole written row), or '$.ref.<writeName>.<field>' (a named upstream write's RETURNING row).`
2173
+ );
2174
+ }
2175
+ function assertPaths(map) {
2176
+ for (const v of Object.values(map)) parseEffectPath(v);
2177
+ }
2178
+ var recorder = {
2179
+ lifecycle(effects = {}) {
2180
+ for (const r of effects.requires ?? []) assertPaths(r.keys);
2181
+ if (effects.idempotency !== void 0) parseEffectPath(effects.idempotency.token);
2182
+ for (const u of effects.unique ?? []) {
2183
+ u.scope.forEach((p) => parseEffectPath(p));
2184
+ u.fields.forEach((p) => parseEffectPath(p));
2185
+ }
2186
+ for (const d of effects.derive ?? []) assertPaths(d.keys);
2187
+ for (const e of effects.edges ?? []) {
2188
+ assertPaths(e.columns);
2189
+ if (e.where !== void 0) assertPaths(e.where);
2190
+ }
2191
+ for (const em of effects.emits ?? []) assertPaths(em.payload);
2192
+ return { effects: freezeEffects(effects) };
2193
+ },
2194
+ exists(table, keys) {
2195
+ return { kind: "requires", table, keys };
2196
+ },
2197
+ unique(spec) {
2198
+ return { kind: "unique", name: spec.name, guardTable: spec.guardTable, scope: spec.scope, fields: spec.fields };
2199
+ },
2200
+ increment(table, keys, attribute, amount) {
2201
+ if (!Number.isFinite(amount)) {
2202
+ throw new Error(`entityWrites: increment amount for '${table}.${attribute}' must be a finite number (got ${String(amount)})`);
2203
+ }
2204
+ return { kind: "derive", table, keys, attribute, amount };
2205
+ },
2206
+ edge(spec) {
2207
+ if (spec.relation === "fk" && spec.where === void 0) {
2208
+ throw new Error(`entityWrites: a 'fk' edge on '${spec.table}' requires a 'where' key map (the matched row's identity)`);
2209
+ }
2210
+ return {
2211
+ kind: "edge",
2212
+ relation: spec.relation,
2213
+ action: spec.action,
2214
+ table: spec.table,
2215
+ columns: spec.columns,
2216
+ ...spec.where !== void 0 ? { where: spec.where } : {}
2217
+ };
2218
+ },
2219
+ event(name, outboxTable, payload) {
2220
+ return { kind: "emit", name, outboxTable, payload };
2221
+ },
2222
+ idempotentBy(table, column, token) {
2223
+ return { kind: "idempotency", table, column, token };
2224
+ }
2225
+ };
2226
+ function freezeEffects(effects) {
2227
+ const out = {};
2228
+ if (effects.requires !== void 0) out.requires = effects.requires;
2229
+ if (effects.idempotency !== void 0) out.idempotency = effects.idempotency;
2230
+ if (effects.unique !== void 0) out.unique = effects.unique;
2231
+ if (effects.derive !== void 0) out.derive = effects.derive;
2232
+ if (effects.edges !== void 0) out.edges = effects.edges;
2233
+ if (effects.emits !== void 0) out.emits = effects.emits;
2234
+ return out;
2235
+ }
2236
+ function entityWrites(builder) {
2237
+ const shape = builder(recorder);
2238
+ if (shape.create === void 0 && shape.update === void 0 && shape.remove === void 0) {
2239
+ throw new Error(
2240
+ "entityWrites(...) must declare at least one lifecycle (`create` / `update` / `remove`)."
2241
+ );
2242
+ }
2243
+ return {
2244
+ ...shape.create !== void 0 ? { create: shape.create } : {},
2245
+ ...shape.update !== void 0 ? { update: shape.update } : {},
2246
+ ...shape.remove !== void 0 ? { remove: shape.remove } : {}
2247
+ };
2248
+ }
2249
+ function edgeWrites(builder) {
2250
+ const def = entityWrites(builder);
2251
+ for (const phase of ["create", "update", "remove"]) {
2252
+ const c = def[phase];
2253
+ if (c === void 0) continue;
2254
+ const e = c.effects;
2255
+ if (e.requires !== void 0 || e.unique !== void 0 || e.derive !== void 0 || e.emits !== void 0 || e.idempotency !== void 0) {
2256
+ throw new Error(
2257
+ `edgeWrites: the '${phase}' lifecycle may declare ONLY 'edges' effects (an edge entity carries only edge writes; use entityWrites for a full save contract).`
2258
+ );
2259
+ }
2260
+ }
2261
+ return def;
2262
+ }
2263
+ function lifecycleFor(def, phase) {
2264
+ return def[phase];
2265
+ }
2266
+
2267
+ // src/scp/makesql/tx.ts
2268
+ function literalize(value) {
2269
+ if (value === null || value === void 0) return null;
2270
+ if (Array.isArray(value)) return { arr: value.map((e) => literalize(e)) };
2271
+ if (typeof value === "object") {
2272
+ const obj = {};
2273
+ for (const [k, v] of Object.entries(value)) obj[k] = literalize(v);
2274
+ return { obj };
2275
+ }
2276
+ return value;
2277
+ }
2278
+ function pathToRef(value) {
2279
+ const p = parseEffectPath(value);
2280
+ if (p.root === "input") return { ref: [p.field] };
2281
+ if (p.root === "entity") return { ref: [ENTITY_ROOT, p.field] };
2282
+ return { ref: [p.writeName, p.field] };
2283
+ }
2284
+ function guardInsert(dialect, table, columns, placeholders) {
2285
+ const cols = `(${columns.join(", ")})`;
2286
+ if (dialect === "mysql") return `INSERT IGNORE INTO ${table} ${cols} VALUES (${placeholders})`;
2287
+ return `INSERT INTO ${table} ${cols} VALUES (${placeholders}) ON CONFLICT DO NOTHING`;
2288
+ }
2289
+ function makeIdGen() {
2290
+ let n = 0;
2291
+ return (role) => `tx_${role}_${n++}`;
2292
+ }
2293
+ function compileRequires(e, nextId) {
2294
+ const cols = Object.keys(e.keys);
2295
+ if (cols.length === 0) throw new Error(`write-plan: requires on '${e.table}' declares no keys`);
2296
+ const whereSql = v1EqualityWhereText(cols);
2297
+ const op = {
2298
+ sql: `SELECT 1 FROM ${e.table} WHERE ${whereSql}`,
2299
+ params: cols.map((c) => pathToRef(e.keys[c]))
2300
+ };
2301
+ return { id: nextId("requires"), role: "gate:requires", op, gate: "existsElseRollback", label: `requires ${e.table}` };
2302
+ }
2303
+ function compileIdempotency(e, nextId, dialect) {
2304
+ const op = {
2305
+ sql: guardInsert(dialect, e.table, [e.column], "?"),
2306
+ params: [pathToRef(e.token)]
2307
+ };
2308
+ return { id: nextId("idem"), role: "gate:idempotency", op, gate: "insertedElseNoop", label: `idempotency ${e.table}` };
2309
+ }
2310
+ function compileUnique(e, nextId, dialect) {
2311
+ const scopeCols = e.scope.map((_, i) => `s${i}`);
2312
+ const fieldCols = e.fields.map((_, i) => `f${i}`);
2313
+ const cols = ["name", ...scopeCols, ...fieldCols];
2314
+ const placeholders = cols.map(() => "?").join(", ");
2315
+ const params = [
2316
+ e.name,
2317
+ // literal discriminator (a closed-set string literal)
2318
+ ...e.scope.map(pathToRef),
2319
+ ...e.fields.map(pathToRef)
2320
+ ];
2321
+ const op = { sql: guardInsert(dialect, e.guardTable, cols, placeholders), params };
2322
+ return { id: nextId("unique"), role: "gate:unique", op, gate: "insertedElseRollback", label: `unique ${e.name}` };
2323
+ }
2324
+ function compileDerive(e, nextId) {
2325
+ const keyCols = Object.keys(e.keys);
2326
+ if (keyCols.length === 0) throw new Error(`write-plan: derive on '${e.table}' declares no keys`);
2327
+ const whereSql = v1EqualityWhereText(keyCols);
2328
+ const op = {
2329
+ sql: `UPDATE ${e.table} SET ${e.attribute} = ${e.attribute} + ? WHERE ${whereSql}`,
2330
+ // SET amount is the first param (before the WHERE keys), matching the v1 param order.
2331
+ params: [e.amount, ...keyCols.map((c) => pathToRef(e.keys[c]))]
2332
+ };
2333
+ return { id: nextId("derive"), role: "derive", op, label: `derive ${e.table}.${e.attribute}` };
2334
+ }
2335
+ function compileEdge(e, nextId) {
2336
+ if (e.relation === "m2m") {
2337
+ const cols = Object.keys(e.columns);
2338
+ if (e.action === "set") {
2339
+ const placeholders = cols.map(() => "?").join(", ");
2340
+ const op3 = {
2341
+ sql: `INSERT INTO ${e.table} (${cols.join(", ")}) VALUES (${placeholders})`,
2342
+ params: cols.map((c) => pathToRef(e.columns[c]))
2343
+ };
2344
+ return { id: nextId("edge"), role: "edge", op: op3, label: `edge m2m link ${e.table}` };
2345
+ }
2346
+ const whereSql2 = v1EqualityWhereText(cols);
2347
+ const op2 = {
2348
+ sql: `DELETE FROM ${e.table} WHERE ${whereSql2}`,
2349
+ params: cols.map((c) => pathToRef(e.columns[c]))
2350
+ };
2351
+ return { id: nextId("edge"), role: "edge", op: op2, label: `edge m2m unlink ${e.table}` };
2352
+ }
2353
+ const setCols = Object.keys(e.columns);
2354
+ const whereCols = Object.keys(e.where);
2355
+ const setClauses = setCols.map((c) => e.action === "set" ? `${c} = ?` : `${c} = NULL`);
2356
+ const setParams = e.action === "set" ? setCols.map((c) => pathToRef(e.columns[c])) : [];
2357
+ const whereSql = v1EqualityWhereText(whereCols);
2358
+ const op = {
2359
+ sql: `UPDATE ${e.table} SET ${setClauses.join(", ")} WHERE ${whereSql}`,
2360
+ params: [...setParams, ...whereCols.map((c) => pathToRef(e.where[c]))]
2361
+ };
2362
+ return { id: nextId("edge"), role: "edge", op, label: `edge fk ${e.action} ${e.table}` };
2363
+ }
2364
+ function compileEmit(e, nextId) {
2365
+ const payloadObj = {};
2366
+ for (const [k, v] of Object.entries(e.payload)) payloadObj[k] = pathToRef(v);
2367
+ const op = {
2368
+ sql: `INSERT INTO ${e.outboxTable} (type, payload) VALUES (?, ?)`,
2369
+ params: [e.name, { obj: payloadObj }]
2370
+ };
2371
+ return { id: nextId("emit"), role: "emit", op, label: `emit ${e.name}` };
2372
+ }
2373
+ var IN_SENTINEL = "@in";
2374
+ function stringPort(ports, name) {
2375
+ const v = ports[name];
2376
+ if (v === void 0) return void 0;
2377
+ if (typeof v !== "string") throw new Error(`compileWriteNode: port '${name}' must be a literal string in the IR (got ${JSON.stringify(v)})`);
2378
+ return v;
2379
+ }
2380
+ function collectFamily(ports, prefix) {
2381
+ const out = {};
2382
+ for (const k of Object.keys(ports)) {
2383
+ if (k.startsWith(`${prefix}.`)) out[k.slice(prefix.length + 1)] = ports[k];
2384
+ }
2385
+ return out;
2386
+ }
2387
+ function opKey(node) {
2388
+ if (node === null || typeof node !== "object" || Array.isArray(node)) return void 0;
2389
+ const keys = Object.keys(node);
2390
+ return keys.length === 1 ? keys[0] : void 0;
2391
+ }
2392
+ function columnOf(node, ctx) {
2393
+ const op = opKey(node);
2394
+ if (op !== "ref" && op !== "refOpt") throw new Error(`compileWriteNode: ${ctx}: the column operand must be a {ref:[\u2026]} path`);
2395
+ const path = node[op];
2396
+ if (!Array.isArray(path) || path.length === 0 || typeof path[path.length - 1] !== "string") {
2397
+ throw new Error(`compileWriteNode: ${ctx}: column ref path must be a non-empty string path`);
2398
+ }
2399
+ return path[path.length - 1];
2400
+ }
2401
+ function binOperands(node, op, at) {
2402
+ const args = node[op];
2403
+ if (!Array.isArray(args) || args.length !== 2) throw new Error(`compileWriteNode: ${at}: '${op}' expects exactly 2 operands`);
2404
+ return [args[0], args[1]];
2405
+ }
2406
+ var CMP_OPS = { lt: "<", le: "<=", gt: ">", ge: ">=", ne: "<>" };
2407
+ var PROBE = "__probe__";
2408
+ function v1ConditionText(conditions) {
2409
+ const probe = [];
2410
+ return new DBConditions(conditions).compile(probe);
2411
+ }
2412
+ function v1EqualityWhereText(columns) {
2413
+ const conditions = {};
2414
+ for (const c of columns) conditions[c] = PROBE;
2415
+ return v1ConditionText(conditions);
2416
+ }
2417
+ function lowerWhereMember(node, at) {
2418
+ const op = opKey(node);
2419
+ if (op === void 0) throw new Error(`compileWriteNode: ${at}: a where member must be a single-operator Expression node`);
2420
+ if (op === "eq") {
2421
+ const [col, val] = binOperands(node, op, at);
2422
+ const column = columnOf(col, at);
2423
+ if (val === null) return { sql: v1ConditionText({ [column]: null }), params: [] };
2424
+ return { sql: v1ConditionText({ [column]: PROBE }), params: [val] };
2425
+ }
2426
+ if (op in CMP_OPS) {
2427
+ const [col, val] = binOperands(node, op, at);
2428
+ const column = columnOf(col, at);
2429
+ if (op === "ne" && val === null) return { sql: v1ConditionText({ [`${column} IS NOT NULL`]: true }), params: [] };
2430
+ return { sql: v1ConditionText({ [`${column} ${CMP_OPS[op]} ?`]: PROBE }), params: [val] };
2431
+ }
2432
+ throw new Error(`compileWriteNode: ${at}: unsupported where operator '${op}' (write path supports eq/ne/lt/le/gt/ge)`);
2433
+ }
2434
+ function lowerWherePort(ports, at) {
2435
+ const v = ports.where;
2436
+ if (v === void 0) return { sql: "", params: [] };
2437
+ if (typeof v !== "object" || v === null || !("arr" in v) || !Array.isArray(v.arr)) {
2438
+ throw new Error(`compileWriteNode: ${at}: 'where' must be an {arr:[\u2026]} literal`);
2439
+ }
2440
+ const members = v.arr;
2441
+ const parts = [];
2442
+ const params = [];
2443
+ members.forEach((m, i) => {
2444
+ const f = lowerWhereMember(m, `${at}.where[${i}]`);
2445
+ parts.push(f.sql);
2446
+ params.push(...f.params);
2447
+ });
2448
+ return { sql: parts.join(" AND "), params };
2449
+ }
2450
+ function returningTail(ports) {
2451
+ const r = stringPort(ports, "returning");
2452
+ return r === void 0 ? "" : ` RETURNING ${r}`;
2453
+ }
2454
+ function pkPort(ports) {
2455
+ const pk = stringPort(ports, "pk");
2456
+ if (pk === void 0) return void 0;
2457
+ const columns = pk.split(",").map((c) => c.trim()).filter((c) => c.length > 0);
2458
+ if (columns.length === 0) return void 0;
2459
+ const ai = stringPort(ports, "autoInc");
2460
+ return { columns, autoInc: ai ?? null };
2461
+ }
2462
+ var MYSQL_PK_HINT_RE = /\s*\/\*scp:pk=[^*]*\*\//;
2463
+ function mysqlPkHint(op) {
2464
+ if (op.pk === void 0) return op;
2465
+ if (!/\breturning\b/i.test(op.sql)) return op;
2466
+ const hint = ` /*scp:pk=${op.pk.columns.join(",")};ai=${op.pk.autoInc ?? ""}*/`;
2467
+ return { ...op, sql: op.sql + hint };
2468
+ }
2469
+ function stripMysqlPkHint(sql2) {
2470
+ return sql2.replace(MYSQL_PK_HINT_RE, "");
2471
+ }
2472
+ function compileWriteNode(node) {
2473
+ const { component, ports } = node;
2474
+ const table = stringPort(ports, "table");
2475
+ if (table === void 0) throw new Error(`compileWriteNode: ${component} node requires a literal 'table' port`);
2476
+ switch (component) {
2477
+ case "Insert": {
2478
+ const values = collectFamily(ports, "values");
2479
+ const cols = Object.keys(values);
2480
+ if (cols.length === 0) throw new Error(`compileWriteNode: Insert requires at least one 'values.<field>' port`);
2481
+ const sorted = [...cols].sort();
2482
+ const placeholders = sorted.map(() => "?").join(", ");
2483
+ const sql2 = `INSERT INTO ${table} (${sorted.join(", ")}) VALUES (${placeholders})${returningTail(ports)}`;
2484
+ const pk = pkPort(ports);
2485
+ return { sql: sql2, params: sorted.map((c) => values[c]), ...pk !== void 0 ? { pk } : {} };
2486
+ }
2487
+ case "Update": {
2488
+ const set = collectFamily(ports, "set");
2489
+ const setCols = Object.keys(set);
2490
+ if (setCols.length === 0) throw new Error(`compileWriteNode: Update requires at least one 'set.<field>' port`);
2491
+ const where = lowerWherePort(ports, "Update");
2492
+ if (where.sql === "") throw new Error(`compileWriteNode: Update requires a 'where' port`);
2493
+ const setClauses = setCols.map((c) => `${c} = ?`).join(", ");
2494
+ const sql2 = `UPDATE ${table} SET ${setClauses} WHERE ${where.sql}${returningTail(ports)}`;
2495
+ return { sql: sql2, params: [...setCols.map((c) => set[c]), ...where.params] };
2496
+ }
2497
+ case "Delete": {
2498
+ const where = lowerWherePort(ports, "Delete");
2499
+ if (where.sql === "") throw new Error(`compileWriteNode: Delete requires a 'where' port`);
2500
+ const sql2 = `DELETE FROM ${table} WHERE ${where.sql}${returningTail(ports)}`;
2501
+ return { sql: sql2, params: where.params };
2502
+ }
2503
+ default:
2504
+ throw new Error(`compileWriteNode: catalog component '${component}' has no write compile (SQL writes: Insert/Update/Delete)`);
2505
+ }
2506
+ }
2507
+ function compileWriteGroup(base, lifecycle, nextId, dialect, seqRef) {
2508
+ const e = lifecycle.effects;
2509
+ const nodes = [];
2510
+ const mk = (stmt, isGate) => {
2511
+ const consumes = refHeadsOf(stmt).filter((h) => h !== ENTITY_ROOT && !isInputHead(h, stmt));
2512
+ return { stmt, seq: seqRef.n++, consumes, produces: stmt.binds ?? null, isGate };
2513
+ };
2514
+ for (const r of e.requires ?? []) nodes.push(mk(compileRequires(r, nextId), true));
2515
+ if (e.idempotency !== void 0) nodes.push(mk(compileIdempotency(e.idempotency, nextId, dialect), true));
2516
+ for (const u of e.unique ?? []) nodes.push(mk(compileUnique(u, nextId, dialect), true));
2517
+ const bodyId = nextId("body");
2518
+ const bodyStmt = {
2519
+ id: bodyId,
2520
+ role: "body",
2521
+ op: base.op,
2522
+ label: base.label,
2523
+ ...base.name !== void 0 ? { binds: base.name } : {}
2524
+ };
2525
+ nodes.push(mk(bodyStmt, false));
2526
+ for (const d of e.derive ?? []) nodes.push(mk(compileDerive(d, nextId), false));
2527
+ for (const ed of e.edges ?? []) nodes.push(mk(compileEdge(ed, nextId), false));
2528
+ for (const em of e.emits ?? []) nodes.push(mk(compileEmit(em, nextId), false));
2529
+ return { nodes, bodyId };
2530
+ }
2531
+ function topoOrder(nodes) {
2532
+ const byName = /* @__PURE__ */ new Map();
2533
+ for (const n of nodes) {
2534
+ if (n.produces !== null) {
2535
+ if (byName.has(n.produces)) {
2536
+ throw new Error(`write-plan: two writes both bind the name '${n.produces}' \u2014 write names must be unique in a composite Command.`);
2537
+ }
2538
+ byName.set(n.produces, n);
2539
+ }
2540
+ }
2541
+ const succ = /* @__PURE__ */ new Map();
2542
+ const indeg = /* @__PURE__ */ new Map();
2543
+ const nodeById = /* @__PURE__ */ new Map();
2544
+ for (const n of nodes) {
2545
+ nodeById.set(n.stmt.id, n);
2546
+ succ.set(n.stmt.id, /* @__PURE__ */ new Set());
2547
+ indeg.set(n.stmt.id, 0);
2548
+ }
2549
+ const addEdge = (fromId, toId) => {
2550
+ if (fromId === toId) return;
2551
+ const s = succ.get(fromId);
2552
+ if (!s.has(toId)) {
2553
+ s.add(toId);
2554
+ indeg.set(toId, indeg.get(toId) + 1);
2555
+ }
2556
+ };
2557
+ for (const n of nodes) {
2558
+ for (const dep of n.consumes) {
2559
+ const producer = byName.get(dep);
2560
+ if (producer === void 0) {
2561
+ throw new Error(
2562
+ `write-plan: statement '${n.stmt.label}' references '$.ref.${dep}.*' but no write in this Command binds the name '${dep}' \u2014 a dangling write reference (fail-closed; no silent skip).`
2563
+ );
2564
+ }
2565
+ addEdge(producer.stmt.id, n.stmt.id);
2566
+ }
2567
+ }
2568
+ const gates = nodes.filter((n) => n.isGate);
2569
+ const nonGates = nodes.filter((n) => !n.isGate);
2570
+ for (const g of gates) for (const b of nonGates) addEdge(g.stmt.id, b.stmt.id);
2571
+ const ordered = [];
2572
+ const ready = nodes.filter((n) => indeg.get(n.stmt.id) === 0).sort((a, b) => a.seq - b.seq);
2573
+ while (ready.length > 0) {
2574
+ const n = ready.shift();
2575
+ ordered.push(n.stmt);
2576
+ for (const depId of succ.get(n.stmt.id)) {
2577
+ indeg.set(depId, indeg.get(depId) - 1);
2578
+ if (indeg.get(depId) === 0) {
2579
+ const dn = nodeById.get(depId);
2580
+ let lo = 0;
2581
+ while (lo < ready.length && ready[lo].seq < dn.seq) lo++;
2582
+ ready.splice(lo, 0, dn);
2583
+ }
2584
+ }
2585
+ }
2586
+ if (ordered.length !== nodes.length) {
2587
+ const stuck = nodes.filter((n) => !ordered.includes(n.stmt)).map((n) => n.stmt.label);
2588
+ throw new Error(
2589
+ `write-plan: the write-time transaction DAG has a CYCLE (unresolvable ordering among [${stuck.join(", ")}]). This is either a mutual '$.ref' data dependency or a gate that depends on a body row (a gate cannot depend on a not-yet-written row). ESCALATE: the Command's write dependencies are contradictory \u2014 no order satisfies both the data dependencies and gate-first. (Fail-closed; the derivation never guesses an order.)`
2590
+ );
2591
+ }
2592
+ return ordered;
2593
+ }
2594
+ function deriveTransactionPlan(phase, bases, lifecycle, dialect = "sqlite") {
2595
+ if (bases.length === 0) {
2596
+ throw new Error("write-plan: a Command must declare at least one base write (Insert/Update/Delete).");
2597
+ }
2598
+ const nextId = makeIdGen();
2599
+ const seqRef = { n: 0 };
2600
+ const composite = bases.length > 1;
2601
+ const allNodes = [];
2602
+ const bodyIds = [];
2603
+ bases.forEach((base, i) => {
2604
+ const lc = base.effects !== void 0 ? { effects: base.effects } : i === 0 ? lifecycle : { effects: {} };
2605
+ const { nodes, bodyId } = compileWriteGroup(base, lc, nextId, dialect, seqRef);
2606
+ allNodes.push(...nodes);
2607
+ bodyIds.push(bodyId);
2608
+ });
2609
+ const statements = topoOrder(allNodes);
2610
+ const soleBody = bases[0];
2611
+ const soleBodyId = bodyIds[0];
2612
+ const usesEntity = referencesHead(statements, ENTITY_ROOT);
2613
+ const soleBodyReturns = /\breturning\b/i.test(soleBody.op.sql);
2614
+ if (usesEntity && composite) {
2615
+ throw new Error(
2616
+ `write-plan: a composite (multi-write) Command uses '$.entity.*' \u2014 ambiguous which write it denotes. Address each write's row by name via '$.ref.<writeName>.<field>' (fail-closed).`
2617
+ );
2618
+ }
2619
+ if (usesEntity && !soleBodyReturns) {
2620
+ throw new Error(
2621
+ `write-plan: a derive/edges/emits stage references '$.entity.*' but the body write ('${soleBody.label}') has no RETURNING clause \u2014 the written row cannot be exposed to later stages. Add a RETURNING to the base write (fail-closed; no absent-entity default).`
2622
+ );
2623
+ }
2624
+ const entityFrom = usesEntity || !composite && soleBodyReturns ? soleBodyId : null;
2625
+ for (const base of bases) {
2626
+ if (base.name === void 0) continue;
2627
+ if (referencesHead(statements, base.name) && !/\breturning\b/i.test(base.op.sql)) {
2628
+ throw new Error(
2629
+ `write-plan: write '${base.name}' is referenced as '$.ref.${base.name}.*' by a later statement but its op has no RETURNING clause \u2014 its row cannot be bound for downstream writes. Add a RETURNING to write '${base.name}' (fail-closed; no absent-row default).`
2630
+ );
2631
+ }
2632
+ }
2633
+ return { phase, entityFrom, statements, onIdempotentHit: "rollback" };
2634
+ }
2635
+ function deriveBatchPlan(phase, ops) {
2636
+ if (ops.length === 0) {
2637
+ throw new Error("write-plan: a batch write must declare at least one statement (createMany/updateMany/deleteMany produced none).");
2638
+ }
2639
+ const nextId = makeIdGen();
2640
+ const statements = ops.map((op, i) => ({
2641
+ id: nextId("body"),
2642
+ role: "body",
2643
+ op: { sql: op.sql, params: op.params.map((p) => literalize(p)) },
2644
+ label: op.label ?? `batch[${i}]`
2645
+ }));
2646
+ return { phase, entityFrom: null, statements, onIdempotentHit: "rollback" };
2647
+ }
2648
+ function refHeadsOf(stmt) {
2649
+ const heads = /* @__PURE__ */ new Set();
2650
+ collectRefHeads(stmt.op.params, heads);
2651
+ return [...heads];
2652
+ }
2653
+ function isInputHead(head, stmt) {
2654
+ return refPathLengthFor(stmt.op.params, head) === 1;
2655
+ }
2656
+ function refPathLengthFor(node, head) {
2657
+ let maxLen = 0;
2658
+ const walk = (n) => {
2659
+ if (n === null || typeof n !== "object") return;
2660
+ if (Array.isArray(n)) {
2661
+ for (const el of n) walk(el);
2662
+ return;
2663
+ }
2664
+ const keys = Object.keys(n);
2665
+ if (keys.length === 1 && keys[0] === "ref") {
2666
+ const path = n.ref;
2667
+ if (Array.isArray(path) && path[0] === head) maxLen = Math.max(maxLen, path.length);
2668
+ return;
2669
+ }
2670
+ for (const k of keys) walk(n[k]);
2671
+ };
2672
+ walk(node);
2673
+ return maxLen;
2674
+ }
2675
+ function referencesHead(statements, head) {
2676
+ const heads = /* @__PURE__ */ new Set();
2677
+ for (const s of statements) collectRefHeads(s.op.params, heads);
2678
+ return heads.has(head);
2679
+ }
2680
+ function collectRefHeads(node, heads) {
2681
+ if (node === null || typeof node !== "object") return;
2682
+ if (Array.isArray(node)) {
2683
+ for (const el of node) collectRefHeads(el, heads);
2684
+ return;
2685
+ }
2686
+ const keys = Object.keys(node);
2687
+ if (keys.length === 1 && keys[0] === "ref") {
2688
+ const path = node.ref;
2689
+ if (Array.isArray(path) && typeof path[0] === "string") heads.add(path[0]);
2690
+ return;
2691
+ }
2692
+ for (const k of keys) collectRefHeads(node[k], heads);
2693
+ }
2694
+ function toDriverParam(v) {
2695
+ if (typeof v === "bigint") {
2696
+ if (v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER)) return Number(v);
2697
+ return v;
2698
+ }
2699
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) return JSON.stringify(v);
2700
+ return v;
2701
+ }
2702
+ function renderStatement(op, scope, dialect) {
2703
+ const concrete = op.params.map((p) => evaluateExpression(p, scope));
2704
+ const node = { sql: op.sql, params: concrete };
2705
+ const assembled = assembleMakeSQL(node);
2706
+ return { sql: renderPlaceholders(assembled.sql, dialect), params: assembled.params.map((p) => toDriverParam(p)) };
2707
+ }
2708
+ function execStatement(db, op, scope, dialect) {
2709
+ const { sql: sql2, params } = renderStatement(op, scope, dialect);
2710
+ const stmt = db.prepare(sql2);
2711
+ const hasReturn = /\bselect\b/i.test(sql2.slice(0, 8)) || /\breturning\b/i.test(sql2);
2712
+ if (hasReturn) {
2713
+ const rows = stmt.all(...params);
2714
+ return { rows, changes: rows.length };
2715
+ }
2716
+ const info = stmt.run(...params);
2717
+ return { rows: [], changes: info.changes };
2718
+ }
2719
+ function gateShortCircuit(gate, result) {
2720
+ switch (gate) {
2721
+ case "existsElseRollback":
2722
+ return result.rows.length === 0 ? "requires_absent" : null;
2723
+ case "insertedElseRollback":
2724
+ return result.changes === 0 ? "unique_collision" : null;
2725
+ case "insertedElseNoop":
2726
+ return result.changes === 0 ? "idempotent_duplicate" : null;
2727
+ }
2728
+ }
2729
+ function executeTransaction(db, plan, input, dialect = "sqlite") {
2730
+ db.prepare("BEGIN").run();
2731
+ const executed = [];
2732
+ const scope = { ...input };
2733
+ let entity = null;
2734
+ const isBatch = plan.entityFrom === null && plan.statements.every((s) => s.gate === void 0 && s.binds === void 0 && s.role === "body");
2735
+ const returnedRows = [];
2736
+ try {
2737
+ for (const stmt of plan.statements) {
2738
+ const result = execStatement(db, stmt.op, scope, dialect);
2739
+ executed.push(stmt.id);
2740
+ if (stmt.gate !== void 0) {
2741
+ const reason = gateShortCircuit(stmt.gate, result);
2742
+ if (reason !== null) {
2743
+ db.prepare("ROLLBACK").run();
2744
+ return { committed: false, shortCircuit: { statementId: stmt.id, reason }, entity: null, executed };
2745
+ }
2746
+ }
2747
+ if (stmt.id === plan.entityFrom) {
2748
+ entity = result.rows.length > 0 ? result.rows[0] : null;
2749
+ if (entity !== null) scope[ENTITY_ROOT] = entity;
2750
+ }
2751
+ if (stmt.binds !== void 0 && result.rows.length > 0) {
2752
+ scope[stmt.binds] = result.rows[0];
2753
+ }
2754
+ if (isBatch && stmt.role === "body" && result.rows.length > 0) returnedRows.push(result.rows);
2755
+ }
2756
+ db.prepare("COMMIT").run();
2757
+ return { committed: true, entity, executed, ...returnedRows.length > 0 ? { returnedRows } : {} };
2758
+ } catch (e) {
2759
+ try {
2760
+ db.prepare("ROLLBACK").run();
2761
+ } catch {
2762
+ }
2763
+ throw mapSqliteError(e);
2764
+ }
2765
+ }
2766
+ function countingDriver(db) {
2767
+ const prepared = [];
2768
+ const wrapped = {
2769
+ prepare(sql2) {
2770
+ prepared.push(sql2);
2771
+ return db.prepare(sql2);
2772
+ }
2773
+ };
2774
+ return { db: wrapped, prepared };
2775
+ }
2776
+ function renderTxStatement(op, scope, dialect = "sqlite") {
2777
+ return renderStatement(op, scope, dialect);
2778
+ }
2779
+
2780
+ // src/scp/makesql/authoring-compile.ts
2781
+ import { evaluateExpression as evaluateExpression2 } from "behavior-contracts";
2782
+ function isMap(n) {
2783
+ return "map" in n;
2784
+ }
2785
+ function nodeRef(n) {
2786
+ return isMap(n) ? { component: n.map.component, ports: n.map.ports } : { component: n.component, ports: n.ports };
2787
+ }
2788
+ function stringPort2(ports, name) {
2789
+ const v = ports[name];
2790
+ if (v === void 0) return void 0;
2791
+ if (typeof v !== "string") {
2792
+ throw new Error(`authoring-compile: port '${name}' must be a literal string in the IR (got ${JSON.stringify(v)})`);
2793
+ }
2794
+ return v;
2795
+ }
2796
+ function arrPort(ports, name) {
2797
+ const v = ports[name];
2798
+ if (v === void 0) return void 0;
2799
+ if (typeof v === "object" && v !== null && "arr" in v && Array.isArray(v.arr)) {
2800
+ return v.arr;
2801
+ }
2802
+ throw new Error(`authoring-compile: port '${name}' must be an {arr:[...]} literal in the IR (got ${JSON.stringify(v)})`);
2803
+ }
2804
+ function stringArrayPort(ports, name) {
2805
+ const arr = arrPort(ports, name);
2806
+ if (arr === void 0) return void 0;
2807
+ return arr.map((e) => {
2808
+ if (typeof e !== "string") throw new Error(`authoring-compile: '${name}' entries must be literal strings (got ${JSON.stringify(e)})`);
2809
+ return e;
2810
+ });
2811
+ }
2812
+ function opKey2(node) {
2813
+ if (node === null || typeof node !== "object" || Array.isArray(node)) return void 0;
2814
+ const keys = Object.keys(node);
2815
+ return keys.length === 1 ? keys[0] : void 0;
2816
+ }
2817
+ function columnOf2(node, ctx) {
2818
+ const op = opKey2(node);
2819
+ if (op !== "ref" && op !== "refOpt") {
2820
+ throw new Error(`authoring-compile: ${ctx}: the column operand must be a {ref:[...]} / {refOpt:[...]} path, got ${JSON.stringify(node)}`);
2821
+ }
2822
+ const path = node[op];
2823
+ if (!Array.isArray(path) || path.length === 0 || typeof path[path.length - 1] !== "string") {
2824
+ throw new Error(`authoring-compile: ${ctx}: column ref path must be a non-empty string path`);
2825
+ }
2826
+ return path[path.length - 1];
2827
+ }
2828
+ function inSentinelColumn(node) {
2829
+ const op = opKey2(node);
2830
+ if (op !== "ref" && op !== "refOpt") return void 0;
2831
+ const path = node[op];
2832
+ if (Array.isArray(path) && path.length >= 2 && path[0] === IN_SENTINEL && typeof path[path.length - 1] === "string") {
2833
+ return path[path.length - 1];
2834
+ }
2835
+ return void 0;
2836
+ }
2837
+ function asInt(v) {
2838
+ if (typeof v === "bigint") return Number(v);
2839
+ if (typeof v === "number") return v;
2840
+ throw new Error(`authoring-compile: limit/offset must evaluate to an integer, got ${JSON.stringify(v)}`);
2841
+ }
2842
+ function binOperands2(node, op, at) {
2843
+ const args = node[op];
2844
+ if (!Array.isArray(args) || args.length !== 2) {
2845
+ throw new Error(`authoring-compile: ${at}: '${op}' expects exactly 2 operands`);
2846
+ }
2847
+ return [args[0], args[1]];
2848
+ }
2849
+ var CMP_OPS2 = { lt: "<", le: "<=", gt: ">", ge: ">=", ne: "<>" };
2850
+ function foldWhereMember(node, scope, into, at) {
2851
+ const op = opKey2(node);
2852
+ if (op === void 0) {
2853
+ throw new Error(`authoring-compile: ${at}: a where member must be a single-operator Expression node, got ${JSON.stringify(node)}`);
2854
+ }
2855
+ if (op === "cond") {
2856
+ const args = node.cond;
2857
+ if (!Array.isArray(args) || args.length !== 3 || args[2] !== null) {
2858
+ throw new Error(`authoring-compile: ${at}: a SKIP-optional condition must be {cond:[c, <member>, null]} (else = null)`);
2859
+ }
2860
+ const present = evaluateExpression2(args[0], scope);
2861
+ if (present !== null && present !== false) foldWhereMember(args[1], scope, into, `${at}.cond.then`);
2862
+ return;
2863
+ }
2864
+ if (op === "and") {
2865
+ const args = node.and;
2866
+ if (!Array.isArray(args) || args.length < 2) throw new Error(`authoring-compile: ${at}: 'and' group expects >= 2 members`);
2867
+ args.forEach((m, i) => foldWhereMember(m, scope, into, `${at}.and[${i}]`));
2868
+ return;
2869
+ }
2870
+ if (op === "or") {
2871
+ const args = node.or;
2872
+ if (!Array.isArray(args) || args.length < 2) throw new Error(`authoring-compile: ${at}: 'or' group expects >= 2 members`);
2873
+ const branches = args.map((m, i) => {
2874
+ const sub2 = {};
2875
+ foldWhereMember(m, scope, sub2, `${at}.or[${i}]`);
2876
+ return sub2;
2877
+ });
2878
+ const existing = into.__or__ ?? [];
2879
+ into.__or__ = [...existing, ...branches];
2880
+ return;
2881
+ }
2882
+ if (op === "eq") {
2883
+ const [col, val] = binOperands2(node, op, at);
2884
+ const inCol = inSentinelColumn(col);
2885
+ if (inCol !== void 0) {
2886
+ into[inCol] = evaluateExpression2(val, scope);
2887
+ return;
2888
+ }
2889
+ const value = evaluateExpression2(val, scope);
2890
+ into[columnOf2(col, at)] = value;
2891
+ return;
2892
+ }
2893
+ if (op in CMP_OPS2) {
2894
+ const [col, val] = binOperands2(node, op, at);
2895
+ const column = columnOf2(col, at);
2896
+ const value = evaluateExpression2(val, scope);
2897
+ if (op === "ne" && value === null) {
2898
+ into[`${column} IS NOT NULL`] = true;
2899
+ return;
2900
+ }
2901
+ into[`${column} ${CMP_OPS2[op]} ?`] = value;
2902
+ return;
2903
+ }
2904
+ throw new Error(`authoring-compile: ${at}: unsupported where operator '${op}' (supported: eq/ne/lt/le/gt/ge/and/or/cond; IN via ${IN_SENTINEL} column head; IS NULL via eq(col,null))`);
2905
+ }
2906
+ function foldWherePort(ports, scope, at) {
2907
+ const arr = arrPort(ports, "where");
2908
+ const conditions = {};
2909
+ if (arr === void 0) return conditions;
2910
+ arr.forEach((m, i) => foldWhereMember(m, scope, conditions, `${at}.where[${i}]`));
2911
+ return conditions;
2912
+ }
2913
+ function collectFamily2(ports, prefix, scope) {
2914
+ const out = {};
2915
+ for (const k of Object.keys(ports)) {
2916
+ if (k.startsWith(`${prefix}.`)) out[k.slice(prefix.length + 1)] = evaluateExpression2(ports[k], scope);
2917
+ }
2918
+ return out;
2919
+ }
2920
+ function compileAuthoredNode(node, scope, dialect) {
2921
+ const { component, ports } = nodeRef(node);
2922
+ const table = stringPort2(ports, "table");
2923
+ if (table === void 0) throw new Error(`authoring-compile: ${component} node requires a literal 'table' port`);
2924
+ const at = `${component}`;
2925
+ switch (component) {
2926
+ case "Select": {
2927
+ const select = stringArrayPort(ports, "select");
2928
+ const conditions = foldWherePort(ports, scope, at);
2929
+ return compileSelect({
2930
+ dialect,
2931
+ tableName: table,
2932
+ select: select ? select.join(", ") : void 0,
2933
+ conditions,
2934
+ order: stringPort2(ports, "order"),
2935
+ group: stringPort2(ports, "group"),
2936
+ limit: ports.limit !== void 0 ? asInt(evaluateExpression2(ports.limit, scope)) : void 0,
2937
+ offset: ports.offset !== void 0 ? asInt(evaluateExpression2(ports.offset, scope)) : void 0
2938
+ });
2939
+ }
2940
+ case "Insert": {
2941
+ const values = collectFamily2(ports, "values", scope);
2942
+ if (Object.keys(values).length === 0) throw new Error(`authoring-compile: Insert requires at least one 'values.<field>' port`);
2943
+ const returning = stringPort2(ports, "returning");
2944
+ const columns = Object.keys(values).sort();
2945
+ return compileInsert(dialect, { tableName: table, columns, records: [values], returning });
2946
+ }
2947
+ case "Update": {
2948
+ const set = collectFamily2(ports, "set", scope);
2949
+ if (Object.keys(set).length === 0) throw new Error(`authoring-compile: Update requires at least one 'set.<field>' port`);
2950
+ const conditions = foldWherePort(ports, scope, at);
2951
+ return compileUpdateSingle({
2952
+ dialect,
2953
+ tableName: table,
2954
+ serializedValues: set,
2955
+ conditions,
2956
+ returning: stringPort2(ports, "returning")
2957
+ });
2958
+ }
2959
+ case "Delete": {
2960
+ const conditions = foldWherePort(ports, scope, at);
2961
+ return compileDelete({ dialect, tableName: table, conditions, returning: stringPort2(ports, "returning") });
2962
+ }
2963
+ default:
2964
+ throw new Error(`authoring-compile: catalog component '${component}' has no makeSQL compile (SQL CRUD only: Select/Insert/Update/Delete)`);
2965
+ }
2966
+ }
2967
+ function compileRelationMap(node, input, dialect, parentKey) {
2968
+ const ports = node.map.ports;
2969
+ const table = stringPort2(ports, "table");
2970
+ if (table === void 0) throw new Error(`authoring-compile: relation map requires a literal 'table' port`);
2971
+ const select = stringArrayPort(ports, "select");
2972
+ const order = stringPort2(ports, "order");
2973
+ const whereArr = arrPort(ports, "where") ?? [];
2974
+ let targetKey;
2975
+ const filters = {};
2976
+ whereArr.forEach((m, i) => {
2977
+ const op = opKey2(m);
2978
+ if (op === "eq") {
2979
+ const [col] = binOperands2(m, "eq", `relationMap.where[${i}]`);
2980
+ const inCol = inSentinelColumn(col);
2981
+ const name = inCol ?? columnOf2(col, `relationMap.where[${i}]`);
2982
+ if (targetKey === void 0) {
2983
+ targetKey = name;
2984
+ return;
2985
+ }
2986
+ }
2987
+ foldWhereMember(m, {}, filters, `relationMap.where[${i}]`);
2988
+ });
2989
+ if (targetKey === void 0) throw new Error(`authoring-compile: relation map 'where' must carry a key eq(...) member`);
2990
+ const values = distinctKeys(input.parentRows, parentKey);
2991
+ const base = {
2992
+ dialect,
2993
+ tableName: table,
2994
+ conditions: Object.keys(filters).length > 0 ? filters : void 0,
2995
+ order,
2996
+ targetKey,
2997
+ values
2998
+ };
2999
+ return input.limit !== void 0 ? compileSingleKeyLimited({ ...base, limit: input.limit }) : compileSingleKeyUnlimited({ ...base, select: select ? select.join(", ") : void 0 });
3000
+ }
3001
+ function distinctKeys(rows, key) {
3002
+ const seen = /* @__PURE__ */ new Set();
3003
+ const out = [];
3004
+ for (const r of rows) {
3005
+ const v = r[key];
3006
+ if (v === null || v === void 0) continue;
3007
+ if (!seen.has(v)) {
3008
+ seen.add(v);
3009
+ out.push(v);
3010
+ }
3011
+ }
3012
+ return out;
3013
+ }
3014
+ function compileAuthoredBehavior(contract, scope, dialect, entry2) {
3015
+ const component = entry2 ? contract.components.find((c) => c.name === entry2) : contract.components[0];
3016
+ if (component === void 0) {
3017
+ throw new Error(`authoring-compile: entry component '${entry2 ?? "<first>"}' not found in contract`);
3018
+ }
3019
+ const relations = [];
3020
+ let primaryNode;
3021
+ for (const n of component.body) {
3022
+ if ("cond" in n) continue;
3023
+ if (isMap(n)) {
3024
+ relations.push(n);
3025
+ continue;
3026
+ }
3027
+ if (primaryNode === void 0) primaryNode = n;
3028
+ }
3029
+ if (primaryNode === void 0) {
3030
+ throw new Error(`authoring-compile: behavior '${component.name}' has no primary catalog node`);
3031
+ }
3032
+ const normalized = normalizeInput(component, scope);
3033
+ return {
3034
+ name: component.name,
3035
+ primary: compileAuthoredNode(primaryNode, normalized, dialect),
3036
+ relations
3037
+ };
3038
+ }
3039
+ function normalizeInput(component, input) {
3040
+ const optional = /* @__PURE__ */ new Set();
3041
+ for (const [port, schema] of Object.entries(component.inputPorts)) {
3042
+ if (schema.required !== true) optional.add(port);
3043
+ }
3044
+ for (const n of component.body) skipGuardHeads(n, optional);
3045
+ const out = { ...input };
3046
+ for (const head of optional) {
3047
+ if (!(head in out)) out[head] = null;
3048
+ }
3049
+ return out;
3050
+ }
3051
+ function skipGuardHeads(n, into) {
3052
+ if ("cond" in n) return;
3053
+ const ports = isMap(n) ? n.map.ports : n.ports;
3054
+ const where = ports.where;
3055
+ if (typeof where !== "object" || where === null || !("arr" in where)) return;
3056
+ const arr = where.arr;
3057
+ if (!Array.isArray(arr)) return;
3058
+ for (const m of arr) {
3059
+ if (opKey2(m) === "cond") {
3060
+ const cond = m.cond;
3061
+ if (Array.isArray(cond) && cond.length === 3) collectRefHeads2(cond[0], into);
3062
+ }
3063
+ }
3064
+ }
3065
+ function collectRefHeads2(node, into) {
3066
+ if (node === null || typeof node !== "object") return;
3067
+ if (Array.isArray(node)) {
3068
+ for (const e of node) collectRefHeads2(e, into);
3069
+ return;
3070
+ }
3071
+ const op = opKey2(node);
3072
+ if (op === "ref" || op === "refOpt") {
3073
+ const path = node[op];
3074
+ if (Array.isArray(path) && path.length > 0 && typeof path[0] === "string") into.add(path[0]);
3075
+ return;
3076
+ }
3077
+ for (const v of Object.values(node)) collectRefHeads2(v, into);
3078
+ }
3079
+
3080
+ // src/scp/makesql/static-bundle.ts
3081
+ import {
3082
+ evaluateExpression as evaluateExpression3,
3083
+ runBehavior,
3084
+ runBehaviorAsync
3085
+ } from "behavior-contracts";
3086
+
3087
+ // src/scp/authoring-sql.ts
3088
+ import { eq, ne, lt, le, gt, ge } from "behavior-contracts";
3089
+ var BETWEEN_SENTINEL = "@between";
3090
+ var LIKE_SENTINEL = "@like";
3091
+ var CAST_SENTINEL = "@cast";
3092
+ var DYNAMIC_SENTINEL = "@dynamic";
3093
+ var IMMEDIATE_SENTINEL = "@immediate";
3094
+ var TUPLE_SENTINEL = "@tuple";
3095
+ var SUBQUERY_SENTINEL = "@subquery";
3096
+ var EXISTS_SENTINEL = "@exists";
3097
+ function sentinelRef($, head, segs) {
3098
+ let node = $[head];
3099
+ for (const s of segs) node = node[s];
3100
+ return node;
3101
+ }
3102
+ function whereEq(col, value) {
3103
+ return eq(col, value);
3104
+ }
3105
+ function whereNe(col, value) {
3106
+ return ne(col, value);
3107
+ }
3108
+ function whereLt(col, value) {
3109
+ return lt(col, value);
3110
+ }
3111
+ function whereLe(col, value) {
3112
+ return le(col, value);
3113
+ }
3114
+ function whereGt(col, value) {
3115
+ return gt(col, value);
3116
+ }
3117
+ function whereGe(col, value) {
3118
+ return ge(col, value);
3119
+ }
3120
+ function whereIsNull(col) {
3121
+ return eq(col, null);
3122
+ }
3123
+ function whereIn(col, value) {
3124
+ return eq(col, value);
3125
+ }
3126
+ function inColumn($, name) {
3127
+ return $[IN_SENTINEL][name];
3128
+ }
3129
+ function whereBetween($, col, lo, hi) {
3130
+ return eq(sentinelRef($, BETWEEN_SENTINEL, [col]), [lo, hi]);
3131
+ }
3132
+ function whereLike($, col, pattern) {
3133
+ return eq(sentinelRef($, LIKE_SENTINEL, [col, "LIKE"]), pattern);
3134
+ }
3135
+ function whereILike($, col, pattern) {
3136
+ return eq(sentinelRef($, LIKE_SENTINEL, [col, "ILIKE"]), pattern);
3137
+ }
3138
+ function whereCast($, col, sqlType, value, op = "=") {
3139
+ return eq(sentinelRef($, CAST_SENTINEL, [col, sqlType, op]), value);
3140
+ }
3141
+ function whereDynamic($, col, template, values) {
3142
+ return eq(sentinelRef($, DYNAMIC_SENTINEL, [col, template]), values);
3143
+ }
3144
+ function whereImmediate($, col, sql2) {
3145
+ return eq(sentinelRef($, IMMEDIATE_SENTINEL, [col, sql2]), null);
3146
+ }
3147
+ function whereTupleIn($, columns, tuples) {
3148
+ return eq(sentinelRef($, TUPLE_SENTINEL, columns), tuples);
3149
+ }
3150
+ function whereInSubquery($, lhs, sub2, not2 = false) {
3151
+ return eq(sentinelRef($, SUBQUERY_SENTINEL, [lhs, not2 ? "NOT IN" : "IN"]), sub2);
3152
+ }
3153
+ function whereExists($, sub2, not2 = false) {
3154
+ return eq(sentinelRef($, EXISTS_SENTINEL, [not2 ? "NOT EXISTS" : "EXISTS"]), sub2);
3155
+ }
3156
+
3157
+ // src/scp/makesql/static-bundle.ts
3158
+ function isMap2(n) {
3159
+ return "map" in n;
3160
+ }
3161
+ function nodeRef2(n) {
3162
+ return isMap2(n) ? { component: n.map.component, ports: n.map.ports } : { component: n.component, ports: n.ports };
3163
+ }
3164
+ function stringPort3(ports, name) {
3165
+ const v = ports[name];
3166
+ if (v === void 0) return void 0;
3167
+ if (typeof v !== "string") throw new Error(`static-bundle: port '${name}' must be a literal string in the IR (got ${JSON.stringify(v)})`);
3168
+ return v;
3169
+ }
3170
+ function arrPort2(ports, name) {
3171
+ const v = ports[name];
3172
+ if (v === void 0) return void 0;
3173
+ if (typeof v === "object" && v !== null && "arr" in v && Array.isArray(v.arr)) {
3174
+ return v.arr;
3175
+ }
3176
+ throw new Error(`static-bundle: port '${name}' must be an {arr:[...]} literal in the IR (got ${JSON.stringify(v)})`);
3177
+ }
3178
+ function stringArrayPort2(ports, name) {
3179
+ const arr = arrPort2(ports, name);
3180
+ if (arr === void 0) return void 0;
3181
+ return arr.map((e) => {
3182
+ if (typeof e !== "string") throw new Error(`static-bundle: '${name}' entries must be literal strings (got ${JSON.stringify(e)})`);
3183
+ return e;
3184
+ });
3185
+ }
3186
+ function mapPort(ports, name) {
3187
+ const v = ports[name];
3188
+ if (v === void 0) return void 0;
3189
+ if (typeof v === "object" && v !== null && "obj" in v && typeof v.obj === "object") {
3190
+ const obj = v.obj;
3191
+ if (typeof obj.name !== "string" || typeof obj.sql !== "string") {
3192
+ throw new Error(`static-bundle: port '${name}' must be a {name, sql} literal map (got ${JSON.stringify(v)})`);
3193
+ }
3194
+ return { name: obj.name, sql: obj.sql };
3195
+ }
3196
+ throw new Error(`static-bundle: port '${name}' must be an {obj:{name, sql}} literal (got ${JSON.stringify(v)})`);
3197
+ }
3198
+ function exprArrayPort(ports, name) {
3199
+ const arr = arrPort2(ports, name);
3200
+ if (arr === void 0) return void 0;
3201
+ return arr;
3202
+ }
3203
+ function opKey3(node) {
3204
+ if (node === null || typeof node !== "object" || Array.isArray(node)) return void 0;
3205
+ const keys = Object.keys(node);
3206
+ return keys.length === 1 ? keys[0] : void 0;
3207
+ }
3208
+ function columnOf3(node, ctx) {
3209
+ const op = opKey3(node);
3210
+ if (op !== "ref" && op !== "refOpt") throw new Error(`static-bundle: ${ctx}: the column operand must be a {ref:[\u2026]} / {refOpt:[\u2026]} path`);
3211
+ const path = node[op];
3212
+ if (!Array.isArray(path) || path.length === 0 || typeof path[path.length - 1] !== "string") {
3213
+ throw new Error(`static-bundle: ${ctx}: column ref path must be a non-empty string path`);
3214
+ }
3215
+ return path[path.length - 1];
3216
+ }
3217
+ function inSentinelColumn2(node) {
3218
+ const op = opKey3(node);
3219
+ if (op !== "ref" && op !== "refOpt") return void 0;
3220
+ const path = node[op];
3221
+ if (Array.isArray(path) && path.length >= 2 && path[0] === IN_SENTINEL && typeof path[path.length - 1] === "string") {
3222
+ return path[path.length - 1];
3223
+ }
3224
+ return void 0;
3225
+ }
3226
+ function binOperands3(node, op, at) {
3227
+ const args = node[op];
3228
+ if (!Array.isArray(args) || args.length !== 2) throw new Error(`static-bundle: ${at}: '${op}' expects exactly 2 operands`);
3229
+ return [args[0], args[1]];
3230
+ }
3231
+ function sentinelPath(node) {
3232
+ const op = opKey3(node);
3233
+ if (op !== "ref" && op !== "refOpt") return void 0;
3234
+ const path = node[op];
3235
+ if (!Array.isArray(path) || path.length === 0) return void 0;
3236
+ return path.every((s) => typeof s === "string") ? path : void 0;
3237
+ }
3238
+ function litArr(node) {
3239
+ if (node !== null && typeof node === "object" && !Array.isArray(node) && "arr" in node) {
3240
+ const a = node.arr;
3241
+ if (Array.isArray(a)) return a;
3242
+ }
3243
+ return void 0;
3244
+ }
3245
+ function nestedSub(node, at) {
3246
+ if (node === null || typeof node !== "object" || Array.isArray(node) || !("obj" in node)) {
3247
+ throw new Error(`static-bundle: ${at}: subquery operand must be a nested makeSQL Fragment {obj:{sql, params?}}`);
3248
+ }
3249
+ const obj = node.obj;
3250
+ const sqlNode = obj.sql;
3251
+ if (typeof sqlNode !== "string") throw new Error(`static-bundle: ${at}: nested subquery 'sql' must be a literal string`);
3252
+ const params = obj.params === void 0 ? [] : litArr(obj.params) ?? undefinedThrow(at);
3253
+ return { sql: sqlNode, params };
3254
+ }
3255
+ function undefinedThrow(at) {
3256
+ throw new Error(`static-bundle: ${at}: nested subquery 'params' must be an {arr:[\u2026]} literal`);
3257
+ }
3258
+ function decodeSentinel(col, val, dialect, at) {
3259
+ const path = sentinelPath(col);
3260
+ if (path === void 0) return void 0;
3261
+ const head = path[0];
3262
+ if (head === BETWEEN_SENTINEL) {
3263
+ const [column] = [path[1]];
3264
+ const bounds = litArr(val);
3265
+ if (bounds === void 0 || bounds.length !== 2) throw new Error(`static-bundle: ${at}: BETWEEN expects a 2-element bounds array`);
3266
+ return { sql: v1ConditionText2({ [`${column} BETWEEN ? AND ?`]: [PROBE2, PROBE2] }, dialect), params: bounds };
3267
+ }
3268
+ if (head === LIKE_SENTINEL) {
3269
+ const [column, keyword] = [path[1], path[2]];
3270
+ return { sql: v1ConditionText2({ [`${column} ${keyword} ?`]: PROBE2 }, dialect), params: [val] };
3271
+ }
3272
+ if (head === CAST_SENTINEL) {
3273
+ const [column, sqlType, cmpOp] = [path[1], path[2], path[3]];
3274
+ return { sql: v1ConditionText2({ [column]: dbCast(PROBE2, sqlType, cmpOp) }, dialect), params: [val] };
3275
+ }
3276
+ if (head === DYNAMIC_SENTINEL) {
3277
+ const [column, template] = [path[1], path[2]];
3278
+ const values = litArr(val);
3279
+ const nPlaceholders = (template.match(/\?/g) ?? []).length;
3280
+ const probeVals = Array.from({ length: nPlaceholders }, () => PROBE2);
3281
+ const deferred = values ?? [val];
3282
+ if (deferred.length !== nPlaceholders) {
3283
+ throw new Error(`static-bundle: ${at}: dbDynamic template has ${nPlaceholders} '?' but ${deferred.length} value-specs`);
3284
+ }
3285
+ return { sql: v1ConditionText2({ [column]: dbDynamic(template, probeVals) }, dialect), params: deferred };
3286
+ }
3287
+ if (head === IMMEDIATE_SENTINEL) {
3288
+ const [column, sql2] = [path[1], path[2]];
3289
+ return { sql: v1ConditionText2({ [column]: dbImmediate(sql2) }, dialect), params: [] };
3290
+ }
3291
+ if (head === TUPLE_SENTINEL) {
3292
+ const columns = path.slice(1);
3293
+ const tuples = litArr(val);
3294
+ if (tuples === void 0) throw new Error(`static-bundle: ${at}: tuple-IN expects an array-of-tuples value`);
3295
+ const rows = tuples.map((t) => litArr(t) ?? throwTuple(at));
3296
+ const deferred = [];
3297
+ for (const row of rows) for (const el of row) deferred.push(el);
3298
+ const probeRows = rows.map((r) => r.map(() => PROBE2));
3299
+ return { sql: tupleInText(columns, probeRows, dialect), params: deferred };
3300
+ }
3301
+ if (head === SUBQUERY_SENTINEL) {
3302
+ const [lhs, keyword] = [path[1], path[2]];
3303
+ const sub2 = nestedSub(val, at);
3304
+ return { sql: `${lhs} ${keyword} (${sub2.sql})`, params: sub2.params };
3305
+ }
3306
+ if (head === EXISTS_SENTINEL) {
3307
+ const [keyword] = [path[1]];
3308
+ const sub2 = nestedSub(val, at);
3309
+ return { sql: `${keyword} (${sub2.sql})`, params: sub2.params };
3310
+ }
3311
+ return void 0;
3312
+ }
3313
+ function throwTuple(at) {
3314
+ throw new Error(`static-bundle: ${at}: each tuple-IN row must be an {arr:[\u2026]} literal`);
3315
+ }
3316
+ function tupleInText(columns, probeRows, dialect) {
3317
+ const cond = { __tuple__: dbTupleIn([...columns], probeRows) };
3318
+ return v1ConditionText2(cond, dialect);
3319
+ }
3320
+ var CMP_OPS3 = { lt: "<", le: "<=", gt: ">", ge: ">=", ne: "<>" };
3321
+ var PROBE2 = "__probe__";
3322
+ function v1ConditionText2(conditions, dialect) {
3323
+ const probe = [];
3324
+ return conditionsFor(conditions, dialect).compile(probe, formatterFor(dialect));
3325
+ }
3326
+ function v1GroupText(op, leafSqls) {
3327
+ const operator = op === "and" ? "AND" : "OR";
3328
+ const inner = new DBConditions({}, operator);
3329
+ for (const sql2 of leafSqls) inner.add({ __raw__: sql2 }, operator);
3330
+ const parent = new DBConditions({});
3331
+ parent.add(inner);
3332
+ const probe = [];
3333
+ return parent.compile(probe);
3334
+ }
3335
+ function jsonArraySpec(dialect, valueSpec) {
3336
+ return { __jsonArray: valueSpec, dialect };
3337
+ }
3338
+ function inListFragment(dialect, column, valueSpec) {
3339
+ if (dialect === "postgres") {
3340
+ const conditions2 = { __raw__: [`${column} = ANY(?)`, [PROBE2]] };
3341
+ return { sql: v1ConditionText2(conditions2, dialect), params: [jsonArraySpec(dialect, valueSpec)] };
3342
+ }
3343
+ const conditions = { [column]: [PROBE2] };
3344
+ return { sql: v1ConditionText2(conditions, dialect), params: [jsonArraySpec(dialect, valueSpec)] };
3345
+ }
3346
+ function lowerWhereMember2(node, dialect, at) {
3347
+ const op = opKey3(node);
3348
+ if (op === void 0) throw new Error(`static-bundle: ${at}: a where member must be a single-operator Expression node`);
3349
+ if (op === "and" || op === "or") {
3350
+ const args = node[op];
3351
+ if (!Array.isArray(args) || args.length < 2) throw new Error(`static-bundle: ${at}: '${op}' group expects >= 2 members`);
3352
+ const parts = [];
3353
+ const params = [];
3354
+ args.forEach((m, i) => {
3355
+ const f = lowerWhereMember2(m, dialect, `${at}.${op}[${i}]`);
3356
+ parts.push(f.sql);
3357
+ params.push(...f.params);
3358
+ });
3359
+ return { sql: v1GroupText(op, parts), params };
3360
+ }
3361
+ if (op === "eq") {
3362
+ const [col, val] = binOperands3(node, op, at);
3363
+ const inCol = inSentinelColumn2(col);
3364
+ if (inCol !== void 0) return inListFragment(dialect, inCol, val);
3365
+ const additive = decodeSentinel(col, val, dialect, at);
3366
+ if (additive !== void 0) return additive;
3367
+ const column = columnOf3(col, at);
3368
+ if (val === null) return { sql: v1ConditionText2({ [column]: null }, dialect), params: [] };
3369
+ return { sql: v1ConditionText2({ [column]: PROBE2 }, dialect), params: [val] };
3370
+ }
3371
+ if (op in CMP_OPS3) {
3372
+ const [col, val] = binOperands3(node, op, at);
3373
+ const column = columnOf3(col, at);
3374
+ if (op === "ne" && val === null) {
3375
+ return { sql: v1ConditionText2({ [`${column} IS NOT NULL`]: true }, dialect), params: [] };
3376
+ }
3377
+ return { sql: v1ConditionText2({ [`${column} ${CMP_OPS3[op]} ?`]: PROBE2 }, dialect), params: [val] };
3378
+ }
3379
+ throw new Error(`static-bundle: ${at}: unsupported where operator '${op}' (supported: eq/ne/lt/le/gt/ge/and/or; IN via ${IN_SENTINEL}; IS NULL via eq(col,null))`);
3380
+ }
3381
+ function lowerWherePort2(ports, dialect, at) {
3382
+ const arr = arrPort2(ports, "where");
3383
+ if (arr === void 0 || arr.length === 0) return [];
3384
+ const fragments = [];
3385
+ arr.forEach((m, i) => {
3386
+ const memberAt = `${at}.where[${i}]`;
3387
+ if (opKey3(m) === "cond") {
3388
+ const cargs = m.cond;
3389
+ if (!Array.isArray(cargs) || cargs.length !== 3 || cargs[2] !== null) {
3390
+ throw new Error(`static-bundle: ${memberAt}: a SKIP-optional condition must be {cond:[c, <member>, null]} (else = null)`);
3391
+ }
3392
+ const inner = lowerWhereMember2(cargs[1], dialect, `${memberAt}.cond.then`);
3393
+ fragments.push({ sql: inner.sql, params: inner.params, skip: skipFrom(cargs[0]) });
3394
+ return;
3395
+ }
3396
+ const f = lowerWhereMember2(m, dialect, memberAt);
3397
+ fragments.push({ sql: f.sql, params: f.params });
3398
+ });
3399
+ return fragments.map((f) => ({ sql: f.sql, params: f.params, ...f.skip !== void 0 ? { skip: f.skip } : {} }));
3400
+ }
3401
+ function skipFrom(guard) {
3402
+ return { not: [guard] };
3403
+ }
3404
+ function selectTail(desc, table) {
3405
+ const full = compileSelect(desc).sql;
3406
+ const head = `SELECT * FROM ${table}`;
3407
+ return full.startsWith(head) ? full.slice(head.length) : full;
3408
+ }
3409
+ function limitOffsetTail(kind, dialect, table) {
3410
+ const SENTINEL = 987654321;
3411
+ const desc = kind === "limit" ? { dialect, tableName: table, limit: SENTINEL } : { dialect, tableName: table, offset: SENTINEL };
3412
+ const tail = selectTail(desc, table);
3413
+ return tail.replace(String(SENTINEL), "?");
3414
+ }
3415
+ function compileSelectNode(node, dialect) {
3416
+ const { component, ports } = nodeRef2(node);
3417
+ if (component !== "Select" && component !== "Count") {
3418
+ throw new Error(`static-bundle: compileSelectNode only compiles Select/Count nodes (got '${component}')`);
3419
+ }
3420
+ const table = stringPort3(ports, "table");
3421
+ if (table === void 0) throw new Error(`static-bundle: ${component} node requires a literal 'table' port`);
3422
+ const isCount = component === "Count";
3423
+ const select = stringArrayPort2(ports, "select");
3424
+ const cols = isCount ? "COUNT(*) as count" : select && select.length > 0 ? select.join(", ") : "*";
3425
+ const statements = [];
3426
+ const cte = mapPort(ports, "cte");
3427
+ const join = stringPort3(ports, "join");
3428
+ const cteParams = exprArrayPort(ports, "cteParams");
3429
+ const joinParams = exprArrayPort(ports, "joinParams");
3430
+ const headDesc = { dialect, tableName: table, select: cols };
3431
+ if (cte !== void 0) headDesc.cte = { name: cte.name, sql: cte.sql, params: [] };
3432
+ if (join !== void 0) headDesc.join = join;
3433
+ const headParams = [...cteParams ?? [], ...joinParams ?? []];
3434
+ statements.push({ sql: compileSelect(headDesc).sql, params: headParams });
3435
+ const whereFrags = lowerWherePort2(ports, dialect, component);
3436
+ for (const frag of whereFrags) {
3437
+ statements.push({ sql: frag.sql, params: frag.params, whereFragment: true, ...frag.skip !== void 0 ? { skip: frag.skip } : {} });
3438
+ }
3439
+ if (isCount) return statements;
3440
+ const group = stringPort3(ports, "group");
3441
+ if (group !== void 0) statements.push({ sql: selectTail({ dialect, tableName: table, group }, table), params: [] });
3442
+ const order = stringPort3(ports, "order");
3443
+ if (order !== void 0) statements.push({ sql: selectTail({ dialect, tableName: table, order }, table), params: [] });
3444
+ if (ports.limit !== void 0) statements.push({ sql: limitOffsetTail("limit", dialect, table), params: [ports.limit] });
3445
+ if (ports.offset !== void 0) statements.push({ sql: limitOffsetTail("offset", dialect, table), params: [ports.offset] });
3446
+ const forUpdate = stringPort3(ports, "forUpdate");
3447
+ if (forUpdate === "true") statements.push({ sql: selectTail({ dialect, tableName: table, forUpdate: true }, table), params: [] });
3448
+ const append = stringPort3(ports, "append");
3449
+ if (append !== void 0) statements.push({ sql: selectTail({ dialect, tableName: table, append }, table), params: [] });
3450
+ return statements;
3451
+ }
3452
+ function compilePrimaryNode(node, dialect) {
3453
+ const component = "map" in node ? node.map.component : node.component;
3454
+ if (component === "Select" || component === "Count") return compileSelectNode(node, dialect);
3455
+ if (component === "Insert" || component === "Update" || component === "Delete") {
3456
+ const op = compileWriteNode(node);
3457
+ return [{ sql: op.sql, params: op.params }];
3458
+ }
3459
+ throw new Error(`static-bundle: catalog component '${component}' has no makeSQL compile (SQL CRUD only: Select/Count/Insert/Update/Delete)`);
3460
+ }
3461
+ function compileStaticBundle(contract, dialect, entry2) {
3462
+ const component = entry2 ? contract.components.find((c) => c.name === entry2) : contract.components[0];
3463
+ if (component === void 0) throw new Error(`static-bundle: entry component '${entry2 ?? "<first>"}' not found in contract`);
3464
+ let primary;
3465
+ for (const n of component.body) {
3466
+ if ("cond" in n) continue;
3467
+ if (isMap2(n)) continue;
3468
+ if (primary === void 0) primary = n;
3469
+ }
3470
+ if (primary === void 0) throw new Error(`static-bundle: behavior '${component.name}' has no primary catalog node`);
3471
+ const statements = compilePrimaryNode(primary, dialect);
3472
+ return {
3473
+ dialect,
3474
+ name: component.name,
3475
+ statements,
3476
+ optionalHeads: [...optionalHeadsOf(component, statements)]
3477
+ };
3478
+ }
3479
+ function optionalHeadsOf(component, statements) {
3480
+ const optional = /* @__PURE__ */ new Set();
3481
+ for (const [port, schema] of Object.entries(component.inputPorts)) {
3482
+ if (schema.required !== true) optional.add(port);
3483
+ }
3484
+ for (const n of component.body) skipGuardHeads2(n, optional);
3485
+ for (const stmt of statements) {
3486
+ for (const p of stmt.params) collectRefOptHeads(p, optional);
3487
+ if (stmt.skip !== void 0) collectRefOptHeads(stmt.skip, optional);
3488
+ }
3489
+ return optional;
3490
+ }
3491
+ function collectRefOptHeads(node, into) {
3492
+ if (node === null || typeof node !== "object") return;
3493
+ if (Array.isArray(node)) {
3494
+ for (const e of node) collectRefOptHeads(e, into);
3495
+ return;
3496
+ }
3497
+ const keys = Object.keys(node);
3498
+ if (keys.length === 1 && keys[0] === "refOpt") {
3499
+ const path = node.refOpt;
3500
+ if (Array.isArray(path) && path.length > 0 && typeof path[0] === "string") into.add(path[0]);
3501
+ return;
3502
+ }
3503
+ for (const v of Object.values(node)) collectRefOptHeads(v, into);
3504
+ }
3505
+ function skipGuardHeads2(n, into) {
3506
+ if ("cond" in n) return;
3507
+ const ports = isMap2(n) ? n.map.ports : n.ports;
3508
+ const where = ports.where;
3509
+ if (typeof where !== "object" || where === null || !("arr" in where)) return;
3510
+ const arr = where.arr;
3511
+ if (!Array.isArray(arr)) return;
3512
+ for (const m of arr) {
3513
+ if (opKey3(m) === "cond") {
3514
+ const cond = m.cond;
3515
+ if (Array.isArray(cond) && cond.length === 3) collectRefHeads3(cond[0], into);
3516
+ }
3517
+ }
3518
+ }
3519
+ function collectRefHeads3(node, into) {
3520
+ if (node === null || typeof node !== "object") return;
3521
+ if (Array.isArray(node)) {
3522
+ for (const e of node) collectRefHeads3(e, into);
3523
+ return;
3524
+ }
3525
+ const op = opKey3(node);
3526
+ if (op === "ref" || op === "refOpt") {
3527
+ const path = node[op];
3528
+ if (Array.isArray(path) && path.length > 0 && typeof path[0] === "string") into.add(path[0]);
3529
+ return;
3530
+ }
3531
+ for (const v of Object.values(node)) collectRefHeads3(v, into);
3532
+ }
3533
+ function toDriverParam2(v) {
3534
+ if (typeof v === "bigint") {
3535
+ if (v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER)) return Number(v);
3536
+ return v;
3537
+ }
3538
+ return v;
3539
+ }
3540
+ function evalSpec(spec, scope) {
3541
+ if (spec !== null && typeof spec === "object" && !Array.isArray(spec) && "__jsonArray" in spec) {
3542
+ const marker = spec;
3543
+ const arr = evaluateExpression3(marker.__jsonArray, scope);
3544
+ if (!Array.isArray(arr)) throw new Error("static-bundle: IN-list value-spec did not evaluate to an array");
3545
+ if (marker.dialect === "postgres") return arr.map((e) => toDriverParam2(e));
3546
+ const encodeElem = (e) => marker.dialect === "mysql" && typeof e === "boolean" ? e ? 1 : 0 : toDriverParam2(e);
3547
+ return JSON.stringify(arr.map(encodeElem));
3548
+ }
3549
+ return toDriverParam2(evaluateExpression3(spec, scope));
3550
+ }
3551
+ function renderStatements(statements, dialect, scope) {
3552
+ const nodes = [];
3553
+ let whereSeen = false;
3554
+ for (const stmt of statements) {
3555
+ if (stmt.skip !== void 0) {
3556
+ const drop = evaluateExpression3(stmt.skip, scope);
3557
+ if (drop !== null && drop !== false) continue;
3558
+ }
3559
+ let sql2 = stmt.sql;
3560
+ if (stmt.whereFragment === true) {
3561
+ sql2 = (whereSeen ? " AND " : " WHERE ") + stmt.sql;
3562
+ whereSeen = true;
3563
+ }
3564
+ const params = stmt.params.map((p) => evalSpec(p, scope));
3565
+ nodes.push({ sql: sql2, params });
3566
+ }
3567
+ const assembled = composeMakeSQL(nodes);
3568
+ return { sql: renderPlaceholders(assembled.sql, dialect), params: assembled.params };
3569
+ }
3570
+ function renderBundle(bundle, scope) {
3571
+ return renderStatements(bundle.statements, bundle.dialect, scope);
3572
+ }
3573
+ function normalizeInput2(bundle, input) {
3574
+ const out = { ...input };
3575
+ for (const head of bundle.optionalHeads) {
3576
+ if (!(head in out)) out[head] = null;
3577
+ }
3578
+ return out;
3579
+ }
3580
+ function executeStaticBundle(bundle, input, db) {
3581
+ const scope = normalizeInput2(bundle, input);
3582
+ const { sql: sql2, params } = renderBundle(bundle, scope);
3583
+ let stmt;
3584
+ try {
3585
+ stmt = db.prepare(sql2);
3586
+ return stmt.all(...params);
3587
+ } catch (e) {
3588
+ throw mapSqliteError(e);
3589
+ }
3590
+ }
3591
+ function executeStaticWrite(bundle, input, db) {
3592
+ const scope = normalizeInput2(bundle, input);
3593
+ const { sql: sql2, params } = renderBundle(bundle, scope);
3594
+ const hasReturn = /\breturning\b/i.test(sql2);
3595
+ try {
3596
+ const stmt = db.prepare(sql2);
3597
+ if (hasReturn) return stmt.all(...params);
3598
+ const info = stmt.run(...params);
3599
+ return [{ changes: info.changes, lastInsertRowid: toDriverParam2(BigInt(info.lastInsertRowid)) }];
3600
+ } catch (e) {
3601
+ throw mapSqliteError(e);
3602
+ }
3603
+ }
3604
+ var SCOPE_PORT = "__scope";
3605
+ var NODE_COMPONENT = "__makeSqlNode";
3606
+ function collectAllHeads(node, heads) {
3607
+ if (node === null || typeof node !== "object") return;
3608
+ if (Array.isArray(node)) {
3609
+ for (const el of node) collectAllHeads(el, heads);
3610
+ return;
3611
+ }
3612
+ const keys = Object.keys(node);
3613
+ if (keys.length === 1 && (keys[0] === "ref" || keys[0] === "refOpt")) {
3614
+ const path = node[keys[0]];
3615
+ if (Array.isArray(path) && typeof path[0] === "string") heads.add(path[0]);
3616
+ return;
3617
+ }
3618
+ for (const k of keys) collectAllHeads(node[k], heads);
3619
+ }
3620
+ function statementHeads(statements) {
3621
+ const heads = /* @__PURE__ */ new Set();
3622
+ for (const stmt of statements) {
3623
+ for (const p of stmt.params) collectAllHeads(p, heads);
3624
+ if (stmt.skip !== void 0) collectAllHeads(stmt.skip, heads);
3625
+ }
3626
+ return heads;
3627
+ }
3628
+ function scopePort(statements) {
3629
+ const obj = {};
3630
+ for (const head of statementHeads(statements)) {
3631
+ if (head === IN_SENTINEL) continue;
3632
+ obj[head] = { ref: [head] };
3633
+ }
3634
+ return { obj };
3635
+ }
3636
+ function compileNodeStatements(node, dialect) {
3637
+ const component = "map" in node ? node.map.component : node.component;
3638
+ if (component === "Select" || component === "Count") return compileSelectNode(node, dialect);
3639
+ const op = compileWriteNode(node);
3640
+ return [{ sql: op.sql, params: op.params }];
3641
+ }
3642
+ function compileReadGraph(contract, dialect, entry2) {
3643
+ const component = entry2 ? contract.components.find((c) => c.name === entry2) : contract.components[0];
3644
+ if (component === void 0) throw new Error(`static-bundle: entry component '${entry2 ?? "<first>"}' not found in contract`);
3645
+ const statementsById = {};
3646
+ for (const n of component.body) {
3647
+ if ("cond" in n) continue;
3648
+ statementsById[n.id] = compileNodeStatements(n, dialect);
3649
+ }
3650
+ const surrogateBody = component.body.map((n) => {
3651
+ if ("cond" in n) return n;
3652
+ const stmts = statementsById[n.id];
3653
+ if ("map" in n) {
3654
+ return { ...n, map: { ...n.map, component: NODE_COMPONENT, ports: { [SCOPE_PORT]: scopePort(stmts) } } };
3655
+ }
3656
+ return { ...n, component: NODE_COMPONENT, ports: { [SCOPE_PORT]: scopePort(stmts) } };
3657
+ });
3658
+ const surrogate = { ...component, body: surrogateBody };
3659
+ const ir = { irVersion: 1, exprVersion: 2, components: [surrogate] };
3660
+ return {
3661
+ dialect,
3662
+ name: component.name,
3663
+ ir,
3664
+ statementsById,
3665
+ optionalHeads: [...optionalHeadsOfComponent(component, dialect)]
3666
+ };
3667
+ }
3668
+ function executeReadGraph(graph, input, db) {
3669
+ const handle = (ports, ctx) => {
3670
+ const stmts = graph.statementsById[ctx.nodeId];
3671
+ if (stmts === void 0) return { error: `static-bundle: no statements for node '${ctx.nodeId}'` };
3672
+ const scope = ports[SCOPE_PORT];
3673
+ if (scope === null || typeof scope !== "object" || Array.isArray(scope)) {
3674
+ return { error: `static-bundle: node '${ctx.nodeId}' surrogate scope did not evaluate to an object` };
3675
+ }
3676
+ const { sql: sql2, params } = renderStatements(stmts, graph.dialect, scope);
3677
+ try {
3678
+ const rows = db.prepare(sql2).all(...params);
3679
+ return { ok: rows };
3680
+ } catch (e) {
3681
+ return { error: mapSqliteError(e).message };
3682
+ }
3683
+ };
3684
+ const handlers = { [NODE_COMPONENT]: handle };
3685
+ const normalized = { ...input };
3686
+ for (const head of graph.optionalHeads) if (!(head in normalized)) normalized[head] = null;
3687
+ try {
3688
+ return runBehavior(graph.ir, handlers, normalized, graph.name);
3689
+ } catch (e) {
3690
+ throw reErrorToSqlFailure(e);
3691
+ }
3692
+ }
3693
+ async function executeReadGraphAsync(graph, input, exec) {
3694
+ const handle = async (ports, ctx) => {
3695
+ const stmts = graph.statementsById[ctx.nodeId];
3696
+ if (stmts === void 0) return { error: `static-bundle: no statements for node '${ctx.nodeId}'` };
3697
+ const scope = ports[SCOPE_PORT];
3698
+ if (scope === null || typeof scope !== "object" || Array.isArray(scope)) {
3699
+ return { error: `static-bundle: node '${ctx.nodeId}' surrogate scope did not evaluate to an object` };
3700
+ }
3701
+ const { sql: sql2, params } = renderStatements(stmts, graph.dialect, scope);
3702
+ try {
3703
+ const rows = await exec(sql2, params);
3704
+ return { ok: rows };
3705
+ } catch (e) {
3706
+ return { error: mapSqliteError(e).message };
3707
+ }
3708
+ };
3709
+ const handlers = { [NODE_COMPONENT]: handle };
3710
+ const normalized = { ...input };
3711
+ for (const head of graph.optionalHeads) if (!(head in normalized)) normalized[head] = null;
3712
+ try {
3713
+ return await runBehaviorAsync(graph.ir, handlers, normalized, graph.name);
3714
+ } catch (e) {
3715
+ throw reErrorToSqlFailure(e);
3716
+ }
3717
+ }
3718
+ function executeReadBehavior(contract, input, dialect, db, entry2) {
3719
+ return executeReadGraph(compileReadGraph(contract, dialect, entry2), input, db);
3720
+ }
3721
+ function renderReadPrimary(graph, input) {
3722
+ const ids = Object.keys(graph.statementsById);
3723
+ const bodyIds = graph.ir.components[0].body.map((n) => n.id).filter((id) => ids.includes(id));
3724
+ const primaryId = bodyIds[0];
3725
+ if (primaryId === void 0) throw new Error("static-bundle: read graph has no primary node to render");
3726
+ const scope = { ...input };
3727
+ for (const head of graph.optionalHeads) if (!(head in scope)) scope[head] = null;
3728
+ return renderStatements(graph.statementsById[primaryId], graph.dialect, scope);
3729
+ }
3730
+ function optionalHeadsOfComponent(component, dialect) {
3731
+ const optional = /* @__PURE__ */ new Set();
3732
+ for (const [port, schema] of Object.entries(component.inputPorts)) {
3733
+ if (schema.required !== true) optional.add(port);
3734
+ }
3735
+ for (const n of component.body) skipGuardHeads2(n, optional);
3736
+ for (const n of component.body) {
3737
+ if ("cond" in n) continue;
3738
+ const stmts = compileNodeStatements(n, dialect);
3739
+ for (const stmt of stmts) {
3740
+ for (const p of stmt.params) collectRefOptHeads(p, optional);
3741
+ if (stmt.skip !== void 0) collectRefOptHeads(stmt.skip, optional);
3742
+ }
3743
+ }
3744
+ return optional;
3745
+ }
3746
+ function reErrorToSqlFailure(e) {
3747
+ const message = e instanceof Error ? e.message : String(e);
3748
+ const m = /(SQLITE_[A-Z_]+)/.exec(message);
3749
+ if (m) return mapSqliteError({ code: m[1], message });
3750
+ return e;
3751
+ }
3752
+
3753
+ // src/scp/makesql/pool-executor.ts
3754
+ function pgPoolExecutor(pool) {
3755
+ return async (sql2, params) => {
3756
+ const result = await pool.query(sql2, params);
3757
+ return result.rows;
3758
+ };
3759
+ }
3760
+ function mysqlPoolExecutor(pool) {
3761
+ return async (sql2, params) => {
3762
+ const [rows] = await pool.query(sql2, params);
3763
+ return rows;
3764
+ };
3765
+ }
3766
+
3767
+ // src/scp/catalog.ts
3768
+ import { classifyBehaviorEffect } from "behavior-contracts";
3769
+ var P = (type, required) => required ? { type, required } : { type };
3770
+ var SELECT_PORTS = {
3771
+ table: P("string", true),
3772
+ select: P("string[]", true),
3773
+ where: P("fragment"),
3774
+ order: P("string"),
3775
+ limit: P("expr"),
3776
+ offset: P("expr"),
3777
+ group: P("string"),
3778
+ // Additive read-tail/head ports (V0 R3/R4/R5/R6). SQL is v1-sourced from the matching
3779
+ // `SelectDesc` fields via `compileSelect` (`_buildSelectSQL`), wired in `compileSelectNode`:
3780
+ // - `forUpdate` (R3): a literal `'true'` string toggles the ` FOR UPDATE` tail.
3781
+ // - `join`/`joinParams` (R5): a literal JOIN clause text (`JOIN t ON …`) + its bound
3782
+ // value-specs (`expr[]`), spliced after `FROM <t>` (v1 `_buildSelectSQL` JOIN position).
3783
+ // - `cte`/`cteParams` (R4): a WITH-CTE `{name, sql}` literal + its bound value-specs,
3784
+ // prefixed as `WITH <name> AS (<sql>) ` (v1 WITH-wrap). `cteParams` bind FIRST (v1 param
3785
+ // order: CTE → JOIN → WHERE).
3786
+ // - `append` (R6): a raw trailing clause text (e.g. `HAVING …`) appended verbatim.
3787
+ forUpdate: P("string"),
3788
+ join: P("string"),
3789
+ joinParams: P("expr[]"),
3790
+ cte: P("map"),
3791
+ cteParams: P("expr[]"),
3792
+ append: P("string")
3793
+ };
3794
+ var WRITE_PORTS = {
3795
+ table: P("string", true),
3796
+ where: P("fragment"),
3797
+ returning: P("string"),
3798
+ onConflict: P("string"),
3799
+ onConflictAction: P("string"),
3800
+ // PRIMARY KEY descriptor for the MySQL RETURNING emulation (INSERT…RETURNING): `pk` is a
3801
+ // comma-separated PK column list, `autoInc` names the single AUTO_INCREMENT column (absent for a
3802
+ // client-supplied PK). Consumed by compileWriteNode → mysqlPkHint so the emulation re-selects by
3803
+ // the REAL PK. Inert on PG/SQLite (native RETURNING).
3804
+ pk: P("string"),
3805
+ autoInc: P("string")
3806
+ };
3807
+ var entry = (name, inputPorts, shape, portableToIR) => ({
3808
+ name,
3809
+ inputPorts,
3810
+ output: { shape },
3811
+ portableToIR,
3812
+ // Dynamic port families (where/set/values record ports) are validated by
3813
+ // assertComponentsInCatalog, not statically enumerable. Tx overrides back to
3814
+ // fail-closed (its one port is static).
3815
+ additionalPorts: true
3816
+ });
3817
+ var LITEDBMODEL_CATALOG = {
3818
+ // Read: `items` (a Select root yields a row list; per-row collapse to `item` is the
3819
+ // consumer's cardinality concern, mirrored from graphddb Query's `cardinality` port).
3820
+ Select: entry("Select", { ...SELECT_PORTS, cardinality: P("string") }, "items", true),
3821
+ // COUNT(*) aggregate read (v1 `DBModel._count`): `SELECT COUNT(*) as count FROM t[ WHERE …]`.
3822
+ // Only `table` (required) + the optional `where` fragment tree — v1's count carries no
3823
+ // projection/order/limit/offset (it counts the filtered rows). Output is a one-row `[{count}]`
3824
+ // list (the `items` shape, like every read); the consumer reads `count` — v1's `parseInt(count)`.
3825
+ Count: entry("Count", { table: P("string", true), where: P("fragment") }, "items", true),
3826
+ // Writes yield the RETURNING row list (`items`); a single-row write collapses at the
3827
+ // consumer boundary, same as reads.
3828
+ Insert: entry("Insert", WRITE_PORTS, "items", true),
3829
+ Update: entry("Update", WRITE_PORTS, "items", true),
3830
+ Delete: entry("Delete", WRITE_PORTS, "items", true),
3831
+ // The dynamic WHERE/SET fragment-tree component (§8). Its ports carry the fragment
3832
+ // tree; the compiler resolves SKIP/AND-OR structure to fragment existence rules.
3833
+ Fragment: entry("Fragment", { tree: P("fragment", true) }, "item", true),
3834
+ // The runtime-side atomic envelope: never referenced by the portable IR (a transaction
3835
+ // IS a component over the CRUD primitives) — runtime-only. Its single port is static,
3836
+ // so it keeps bc's fail-closed default (no additionalPorts opt-in).
3837
+ Tx: {
3838
+ ...entry("Tx", { statements: P("map[]", true) }, "item", false),
3839
+ additionalPorts: false
3840
+ }
3841
+ };
3842
+ function catalogEntry(name) {
3843
+ return LITEDBMODEL_CATALOG[name];
3844
+ }
3845
+ var CATALOG_NAMES = [
3846
+ "Select",
3847
+ "Count",
3848
+ "Insert",
3849
+ "Update",
3850
+ "Delete",
3851
+ "Fragment",
3852
+ "Tx"
3853
+ ];
3854
+ var WRITE_CATALOG_NAMES = [
3855
+ "Insert",
3856
+ "Update",
3857
+ "Delete",
3858
+ "Tx"
3859
+ ];
3860
+ var WRITE_SET = new Set(WRITE_CATALOG_NAMES);
3861
+ var WRITE_PORT_FAMILIES = [
3862
+ "values.",
3863
+ "set."
3864
+ ];
3865
+ function assertComponentsInCatalog(components2) {
3866
+ const errs = [];
3867
+ for (const c of components2) {
3868
+ for (const n of c.body) {
3869
+ if ("cond" in n) continue;
3870
+ const ref = "map" in n ? n.map : n;
3871
+ const entryDef = LITEDBMODEL_CATALOG[ref.component];
3872
+ if (entryDef === void 0) {
3873
+ errs.push(`${c.name}/${n.id}: references '${ref.component}', not a LITEDBMODEL_CATALOG entry (known: ${CATALOG_NAMES.join(", ")})`);
3874
+ continue;
3875
+ }
3876
+ const ports = ref.ports;
3877
+ for (const [p, s] of Object.entries(entryDef.inputPorts)) {
3878
+ if (s.required === true && !(p in ports)) {
3879
+ errs.push(`${c.name}/${n.id} (${ref.component}): required port '${p}' is not wired`);
3880
+ }
3881
+ }
3882
+ const isWriteItem = WRITE_SET.has(ref.component);
3883
+ for (const p of Object.keys(ports)) {
3884
+ if (p in entryDef.inputPorts) continue;
3885
+ if (isWriteItem && WRITE_PORT_FAMILIES.some((f) => p.startsWith(f))) continue;
3886
+ errs.push(`${c.name}/${n.id} (${ref.component}): port '${p}' is not declared by the catalog entry (nor a write record-family port)`);
3887
+ }
3888
+ }
3889
+ }
3890
+ if (errs.length > 0) {
3891
+ throw new Error(`litedbmodel catalog validation failed (${errs.length} violation${errs.length === 1 ? "" : "s"}):
3892
+ - ${errs.join("\n - ")}`);
3893
+ }
3894
+ }
3895
+ function deriveContractEffect(component) {
3896
+ return classifyBehaviorEffect(component, WRITE_CATALOG_NAMES) === "writing" ? "command" : "query";
3897
+ }
3898
+
3899
+ // src/scp/dialect.ts
3900
+ function toDollarPlaceholders(sql2) {
3901
+ let n = 0;
3902
+ let out = "";
3903
+ for (const ch of sql2) {
3904
+ if (ch === "?") {
3905
+ n += 1;
3906
+ out += `$${n}`;
3907
+ } else {
3908
+ out += ch;
3909
+ }
3910
+ }
3911
+ return out;
3912
+ }
3913
+ function onConflictClause(conflictColumns, action, excludedRef) {
3914
+ const target = conflictColumns.length > 0 ? ` (${conflictColumns.join(", ")})` : "";
3915
+ if (action === "ignore") {
3916
+ return ` ON CONFLICT${target} DO NOTHING`;
3917
+ }
3918
+ const sets = action.updateColumns.map((c) => `${c} = ${excludedRef(c)}`);
3919
+ return ` ON CONFLICT${target} DO UPDATE SET ${sets.join(", ")}`;
3920
+ }
3921
+ function guardInsertText(table, columns, placeholders, form) {
3922
+ const head = form === "insertIgnore" ? "INSERT IGNORE INTO" : "INSERT INTO";
3923
+ const tail = form === "trailingDoNothing" ? " ON CONFLICT DO NOTHING" : "";
3924
+ return `${head} ${table} (${columns.join(", ")}) VALUES (${placeholders})${tail}`;
3925
+ }
3926
+ var SQLITE = Object.freeze({
3927
+ name: "sqlite",
3928
+ ignoreIsTrailingClause: false,
3929
+ insertVerb(action) {
3930
+ return action === "ignore" ? "INSERT OR IGNORE INTO" : "INSERT INTO";
3931
+ },
3932
+ insertConflictClause(conflictColumns, action) {
3933
+ if (action === "ignore") return "";
3934
+ return onConflictClause(conflictColumns, action, (c) => `excluded.${c}`);
3935
+ },
3936
+ guardInsert(table, columns, placeholders) {
3937
+ return guardInsertText(table, columns, placeholders, "trailingDoNothing");
3938
+ },
3939
+ finalizePlaceholders(sql2) {
3940
+ return sql2;
3941
+ },
3942
+ orderByNulls(expr, dir, nulls) {
3943
+ return `${expr} ${dir} NULLS ${nulls}`;
3944
+ }
3945
+ });
3946
+ var POSTGRES = Object.freeze({
3947
+ name: "postgres",
3948
+ ignoreIsTrailingClause: true,
3949
+ insertVerb(_action) {
3950
+ return "INSERT INTO";
3951
+ },
3952
+ insertConflictClause(conflictColumns, action) {
3953
+ return onConflictClause(conflictColumns, action, (c) => `EXCLUDED.${c}`);
3954
+ },
3955
+ guardInsert(table, columns, placeholders) {
3956
+ return guardInsertText(table, columns, placeholders, "trailingDoNothing");
3957
+ },
3958
+ finalizePlaceholders(sql2) {
3959
+ return toDollarPlaceholders(sql2);
3960
+ },
3961
+ orderByNulls(expr, dir, nulls) {
3962
+ return `${expr} ${dir} NULLS ${nulls}`;
3963
+ }
3964
+ });
3965
+ var MYSQL = Object.freeze({
3966
+ name: "mysql",
3967
+ ignoreIsTrailingClause: false,
3968
+ insertVerb(action) {
3969
+ return action === "ignore" ? "INSERT IGNORE INTO" : "INSERT INTO";
3970
+ },
3971
+ insertConflictClause(_conflictColumns, action) {
3972
+ if (action === "ignore") return "";
3973
+ const sets = action.updateColumns.map((c) => `${c} = VALUES(${c})`);
3974
+ return ` ON DUPLICATE KEY UPDATE ${sets.join(", ")}`;
3975
+ },
3976
+ guardInsert(table, columns, placeholders) {
3977
+ return guardInsertText(table, columns, placeholders, "insertIgnore");
3978
+ },
3979
+ finalizePlaceholders(sql2) {
3980
+ return sql2;
3981
+ },
3982
+ orderByNulls(expr, dir, nulls) {
3983
+ const flagDir = nulls === "FIRST" ? "DESC" : "ASC";
3984
+ return `${expr} IS NULL ${flagDir}, ${expr} ${dir}`;
3985
+ }
3986
+ });
3987
+ var DIALECTS = Object.freeze({
3988
+ sqlite: SQLITE,
3989
+ postgres: POSTGRES,
3990
+ mysql: MYSQL
3991
+ });
3992
+ function dialectFor(name) {
3993
+ const d = DIALECTS[name];
3994
+ if (d === void 0) {
3995
+ throw new Error(`scp dialect: unknown dialect '${String(name)}' (known: ${Object.keys(DIALECTS).join(", ")})`);
3996
+ }
3997
+ return d;
3998
+ }
3999
+
4000
+ // src/scp/guard.ts
4001
+ import { assertPortable, FORBIDDEN_OBJECT_KEY, PORTABLE_EXPR_OPERATORS, PortabilityError } from "behavior-contracts";
4002
+ function assertExprPortable(node, path = "$") {
4003
+ assertPortable(node, path);
4004
+ walkExpr(node, path);
4005
+ }
4006
+ function walkExpr(node, path) {
4007
+ if (node === null || typeof node !== "object") return;
4008
+ if (Array.isArray(node)) {
4009
+ node.forEach((el, i) => walkExpr(el, `${path}[${i}]`));
4010
+ return;
4011
+ }
4012
+ const keys = Object.keys(node);
4013
+ if (keys.length === 1) {
4014
+ const op = keys[0];
4015
+ if (!PORTABLE_EXPR_OPERATORS.has(op)) {
4016
+ throw new PortabilityError(path, `Portability Guard: '${op}' is not a portable Expression IR operator (closed set: ${[...PORTABLE_EXPR_OPERATORS].join(", ")})`);
4017
+ }
4018
+ const arg = node[op];
4019
+ if (op === "obj") {
4020
+ if (arg === null || typeof arg !== "object" || Array.isArray(arg)) {
4021
+ throw new PortabilityError(`${path}.obj`, `Portability Guard: {obj: ...} expects an object (at ${path}.obj)`);
4022
+ }
4023
+ for (const [k, v] of Object.entries(arg)) {
4024
+ if (k === FORBIDDEN_OBJECT_KEY) {
4025
+ throw new PortabilityError(`${path}.obj`, `Portability Guard: object key "${FORBIDDEN_OBJECT_KEY}" is forbidden (fail-closed) at ${path}.obj`);
4026
+ }
4027
+ walkExpr(v, `${path}.obj.${k}`);
4028
+ }
4029
+ return;
4030
+ }
4031
+ walkExpr(arg, `${path}.${op}`);
4032
+ return;
4033
+ }
4034
+ for (const k of keys) walkExpr(node[k], `${path}.${k}`);
4035
+ }
4036
+ function assertComponentPortable(component) {
4037
+ const at = `component '${component.name}'`;
4038
+ for (const n of component.body) {
4039
+ if ("map" in n) {
4040
+ assertExprPortable(n.map.over, `${at}/${n.id}.map.over`);
4041
+ assertPortsPortable(n.map.ports, `${at}/${n.id}.map.ports`);
4042
+ if (n.map.when !== void 0) assertExprPortable(n.map.when, `${at}/${n.id}.map.when`);
4043
+ } else if ("cond" in n) {
4044
+ assertExprPortable(n.cond.if, `${at}/${n.id}.cond.if`);
4045
+ assertExprPortable(n.cond.then, `${at}/${n.id}.cond.then`);
4046
+ assertExprPortable(n.cond.else, `${at}/${n.id}.cond.else`);
4047
+ } else {
4048
+ assertPortsPortable(n.ports, `${at}/${n.id}.ports`);
4049
+ }
4050
+ }
4051
+ assertExprPortable(component.output, `${at}.output`);
4052
+ }
4053
+ function assertPortsPortable(ports, path) {
4054
+ if (ports === null || typeof ports !== "object" || Array.isArray(ports)) {
4055
+ assertExprPortable(ports, path);
4056
+ return;
4057
+ }
4058
+ for (const [name, value] of Object.entries(ports)) {
4059
+ assertExprPortable(value, `${path}.${name}`);
4060
+ }
4061
+ }
4062
+ function assertComponentGraphPortable(ir) {
4063
+ for (const c of ir.components) assertComponentPortable(c);
4064
+ }
4065
+
4066
+ // src/scp/authoring.ts
4067
+ import {
4068
+ catalogComponents,
4069
+ compileBehaviors,
4070
+ SemanticBehavior
4071
+ } from "behavior-contracts";
4072
+ var boundComponents;
4073
+ function components() {
4074
+ if (boundComponents === void 0) {
4075
+ boundComponents = catalogComponents(LITEDBMODEL_CATALOG);
4076
+ }
4077
+ return boundComponents;
4078
+ }
4079
+ function lowerBehaviorClass(cls, options) {
4080
+ const ir = compileBehaviors(cls, {
4081
+ ...options.inputPorts !== void 0 ? { inputPorts: options.inputPorts } : {},
4082
+ ...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {}
4083
+ });
4084
+ assertComponentsInCatalog(ir.components);
4085
+ assertComponentGraphPortable(ir);
4086
+ const methods = {};
4087
+ for (const component of ir.components) {
4088
+ methods[component.name] = {
4089
+ name: component.name,
4090
+ effect: deriveContractEffect(component),
4091
+ component
4092
+ };
4093
+ }
4094
+ return {
4095
+ className: cls.name ?? "<anonymous>",
4096
+ ir,
4097
+ components: ir.components,
4098
+ methods
4099
+ };
4100
+ }
4101
+ function publishBehaviors(cls, options = {}) {
4102
+ return lowerBehaviorClass(cls, options);
4103
+ }
4104
+ function compileEager(name, fn, options = {}) {
4105
+ const cls = makeEagerClass(name, fn);
4106
+ return lowerBehaviorClass(cls, options);
4107
+ }
4108
+ function makeEagerClass(name, fn) {
4109
+ const L = components();
4110
+ const cls = class extends SemanticBehavior {
4111
+ };
4112
+ const method = function($) {
4113
+ return fn($, L);
4114
+ };
4115
+ Object.defineProperty(method, "toString", { value: () => fn.toString(), configurable: true });
4116
+ Object.defineProperty(cls.prototype, name, {
4117
+ value: method,
4118
+ writable: true,
4119
+ enumerable: false,
4120
+ configurable: true
4121
+ });
4122
+ Object.defineProperty(cls, "name", { value: name, configurable: true });
4123
+ return cls;
4124
+ }
4125
+
4126
+ // src/scp/find-filter-guard.ts
4127
+ var FindFilterLeakError = class extends Error {
4128
+ constructor(modelName, missingKeys) {
4129
+ super(
4130
+ `FIND_FILTER leak: model '${modelName}' declares an implicit scope predicate on [${missingKeys.join(", ")}] that is NOT present in the SCP-authored WHERE. The SCP compile cannot auto-apply FIND_FILTER (no model context at compile time), so the scope predicate would be silently dropped (soft-delete / tenant scope leak). Fold the FIND_FILTER predicates into the authored L.Select({ where }) explicitly (e.g. whereEq/whereIsNull on ${missingKeys.join(", ")}).`
4131
+ );
4132
+ this.modelName = modelName;
4133
+ this.missingKeys = missingKeys;
4134
+ this.name = "FindFilterLeakError";
4135
+ }
4136
+ modelName;
4137
+ missingKeys;
4138
+ };
4139
+ function findFilterKeys(filter) {
4140
+ if (!filter || filter.length === 0) return [];
4141
+ return Object.keys(condsToRecord(filter));
4142
+ }
4143
+ function assertFindFilterFolded(model, authoredWhereKeys) {
4144
+ const required = findFilterKeys(model.FIND_FILTER);
4145
+ if (required.length === 0) return;
4146
+ const present = new Set(authoredWhereKeys);
4147
+ const missing = required.filter((k) => !present.has(k));
4148
+ if (missing.length > 0) {
4149
+ throw new FindFilterLeakError(model.name ?? "<anonymous model>", missing);
4150
+ }
4151
+ }
4152
+
4153
+ // src/scp/relation.ts
4154
+ var RELATION_KEYS_HEAD = "__keys";
4155
+ function compileRelationOp(decl) {
4156
+ if (decl.kind !== "hasMany" && decl.limit !== void 0) {
4157
+ throw new Error(`relation '${decl.name}': a per-parent 'limit' is only valid for hasMany (got ${decl.kind})`);
4158
+ }
4159
+ if (decl.limit !== void 0 && (!Number.isInteger(decl.limit) || decl.limit < 0)) {
4160
+ throw new Error(`relation '${decl.name}': per-parent limit must be a non-negative integer (got ${String(decl.limit)})`);
4161
+ }
4162
+ if (decl.limit !== void 0 && decl.order === void 0) {
4163
+ throw new Error(
4164
+ `relation '${decl.name}': a per-parent 'limit' requires an explicit 'order' (the per-parent window needs a deterministic ordering to decide which ${decl.limit} rows each parent keeps)`
4165
+ );
4166
+ }
4167
+ const dialect = decl.dialect ?? "sqlite";
4168
+ const composite = isCompositeDecl(decl);
4169
+ const sql2 = compiledBatchSql(decl, dialect);
4170
+ const conn = decl.connection !== void 0 ? { connection: decl.connection } : {};
4171
+ if (composite) {
4172
+ return {
4173
+ name: decl.name,
4174
+ kind: decl.kind,
4175
+ parentKeys: [...decl.parentKeys],
4176
+ targetKeys: [...decl.targetKeys],
4177
+ dialect,
4178
+ ...conn,
4179
+ sql: sql2
4180
+ };
4181
+ }
4182
+ return {
4183
+ name: decl.name,
4184
+ kind: decl.kind,
4185
+ parentKey: decl.parentKey,
4186
+ targetKey: decl.targetKey,
4187
+ dialect,
4188
+ ...conn,
4189
+ sql: sql2
4190
+ };
4191
+ }
4192
+ function isCompositeDecl(decl) {
4193
+ const hasArrays = decl.parentKeys !== void 0 || decl.targetKeys !== void 0;
4194
+ if (!hasArrays) {
4195
+ if (decl.parentKey === void 0 || decl.targetKey === void 0) {
4196
+ throw new Error(`relation '${decl.name}': a single-key relation requires 'parentKey' and 'targetKey'`);
4197
+ }
4198
+ return false;
4199
+ }
4200
+ if (decl.parentKeys === void 0 || decl.targetKeys === void 0) {
4201
+ throw new Error(`relation '${decl.name}': a composite-key relation requires BOTH 'parentKeys' and 'targetKeys'`);
4202
+ }
4203
+ if (decl.parentKeys.length === 0 || decl.parentKeys.length !== decl.targetKeys.length) {
4204
+ throw new Error(`relation '${decl.name}': 'parentKeys' and 'targetKeys' must be non-empty and equal-length (got ${decl.parentKeys.length} vs ${decl.targetKeys.length})`);
4205
+ }
4206
+ if (decl.parentKey !== void 0 || decl.targetKey !== void 0) {
4207
+ throw new Error(`relation '${decl.name}': cannot mix single-key ('parentKey'/'targetKey') with composite ('parentKeys'/'targetKeys')`);
4208
+ }
4209
+ return true;
4210
+ }
4211
+ function compiledBatchSql(decl, dialect) {
4212
+ if (decl.parentKeys !== void 0) {
4213
+ const compositeBase = {
4214
+ dialect,
4215
+ tableName: decl.targetTable,
4216
+ select: decl.select.join(", "),
4217
+ order: decl.order,
4218
+ targetKeys: [...decl.targetKeys],
4219
+ deferPgArrayCast: true
4220
+ };
4221
+ const node2 = decl.limit !== void 0 ? compileCompositeKeyStaticLimited({ ...compositeBase, limit: decl.limit }) : compileCompositeKeyStaticUnlimited(compositeBase);
4222
+ return assembleMakeSQL(node2).sql;
4223
+ }
4224
+ const placeholderKeys = [null];
4225
+ const base = {
4226
+ dialect,
4227
+ tableName: decl.targetTable,
4228
+ select: decl.select.join(", "),
4229
+ order: decl.order,
4230
+ targetKey: decl.targetKey,
4231
+ values: placeholderKeys,
4232
+ // The keys are UNKNOWN at symbolic compile (placeholder set), so the PG `= ANY(?::<T>[])`
4233
+ // element type is DEFERRED to render (#46) — resolved from the real deduped keys via
4234
+ // `inferPgArrayType`, reproducing v1's live-correct cast (`::int[]` for int keys). Baking a
4235
+ // compile-time `text[]` here was the #43 regression (`integer = text` on real PG).
4236
+ deferPgArrayCast: true
4237
+ };
4238
+ const node = decl.limit !== void 0 ? compileSingleKeyLimited({ ...base, limit: decl.limit }) : compileSingleKeyUnlimited(base);
4239
+ return assembleMakeSQL(node).sql;
4240
+ }
4241
+ function parentKeyCols(op) {
4242
+ return op.parentKeys ?? [op.parentKey];
4243
+ }
4244
+ function targetKeyCols(op) {
4245
+ return op.targetKeys ?? [op.targetKey];
4246
+ }
4247
+ function keyIdentity(values) {
4248
+ return values.map((v) => String(v)).join("\0");
4249
+ }
4250
+ function bindKeys(op, tuples) {
4251
+ const composite = op.parentKeys !== void 0;
4252
+ if (op.dialect === "postgres") {
4253
+ if (!composite) return [tuples.map((t) => t[0])];
4254
+ return parentKeyCols(op).map((_, col) => tuples.map((t) => t[col]));
4255
+ }
4256
+ const payload = composite ? tuples.map((t) => [...t]) : tuples.map((t) => t[0]);
4257
+ return [JSON.stringify(payload)];
4258
+ }
4259
+ function runRelationOp(op, parents, db) {
4260
+ const pCols = parentKeyCols(op);
4261
+ const keys = dedupeKeys(parents, pCols);
4262
+ const batch = /* @__PURE__ */ new Map();
4263
+ let cast = op.sql;
4264
+ if (op.dialect === "postgres") {
4265
+ for (let col = 0; col < pCols.length; col++) cast = resolvePgArrayCast(cast, keys.map((t) => t[col]));
4266
+ }
4267
+ const sql2 = renderPlaceholders(cast, op.dialect);
4268
+ if (keys.length === 0) return { sql: sql2, keys, batch };
4269
+ const tCols = targetKeyCols(op);
4270
+ const rows = db.prepare(sql2).all(...bindKeys(op, keys));
4271
+ for (const row of rows) {
4272
+ const k = keyIdentity(tCols.map((c) => row[c]));
4273
+ const list = batch.get(k);
4274
+ if (list === void 0) batch.set(k, [row]);
4275
+ else list.push(row);
4276
+ }
4277
+ return { sql: sql2, keys, batch };
4278
+ }
4279
+ function dedupeKeys(parents, keyCols) {
4280
+ const seen = /* @__PURE__ */ new Set();
4281
+ const out = [];
4282
+ for (const p of parents) {
4283
+ const tuple = keyCols.map((c) => p[c]);
4284
+ if (tuple.some((v) => v === void 0 || v === null)) continue;
4285
+ const s = keyIdentity(tuple);
4286
+ if (seen.has(s)) continue;
4287
+ seen.add(s);
4288
+ out.push(tuple);
4289
+ }
4290
+ return out;
4291
+ }
4292
+ function distributeToParent(op, parent, batch) {
4293
+ const tuple = parentKeyCols(op).map((c) => parent[c]);
4294
+ const rows = tuple.some((v) => v === void 0 || v === null) ? void 0 : batch.get(keyIdentity(tuple));
4295
+ if (op.kind === "hasMany") return rows ?? [];
4296
+ return rows !== void 0 && rows.length > 0 ? rows[0] : null;
4297
+ }
4298
+
4299
+ // src/scp/typed-object.ts
4300
+ var RELATION_CONTEXT = /* @__PURE__ */ Symbol("litedbmodel:relation-context");
4301
+ var RelationContext = class {
4302
+ siblings;
4303
+ ops;
4304
+ db;
4305
+ hydrate;
4306
+ /** CROSS-DB (V0 R1): name → driver registry; a tagged relation routes to `connections[tag]`. */
4307
+ connections;
4308
+ /** Per-relation resolved batch (name → grouping), memoized: one batch query per relation. */
4309
+ resolved = /* @__PURE__ */ new Map();
4310
+ constructor(siblings, ops, db, hydrate, connections) {
4311
+ this.siblings = siblings;
4312
+ this.ops = ops;
4313
+ this.db = db;
4314
+ this.hydrate = hydrate;
4315
+ this.connections = connections;
4316
+ }
4317
+ /** The driver a relation runs against: its tagged cross-DB connection, else the primary `db`. */
4318
+ driverFor(op) {
4319
+ return op.connection === void 0 ? this.db : driverForOp(op, this.connections);
4320
+ }
4321
+ /**
4322
+ * Resolve `relationName` for `parent` (lazy access). The relation is batch-loaded across
4323
+ * ALL siblings on first access and memoized, so N sibling accesses issue ONE query. Returns
4324
+ * the hydrated child(ren) per cardinality (`hasMany` → array, else single-or-null).
4325
+ */
4326
+ resolve(relationName, parent) {
4327
+ const op = this.ops[relationName];
4328
+ if (op === void 0) {
4329
+ throw new Error(`relation '${relationName}' is not declared on this result set`);
4330
+ }
4331
+ let batch = this.resolved.get(relationName);
4332
+ if (batch === void 0) {
4333
+ batch = runRelationOp(op, this.siblings, this.driverFor(op)).batch;
4334
+ this.resolved.set(relationName, batch);
4335
+ }
4336
+ return applyHydrate(distributeToParent(op, parent, batch), this.hydrate);
4337
+ }
4338
+ };
4339
+ function driverForOp(op, connections) {
4340
+ const d = connections?.[op.connection];
4341
+ if (d === void 0) {
4342
+ throw new Error(`cross-DB relation '${op.name}': no driver registered for connection '${op.connection}' (pass it in ReadOptions.connections)`);
4343
+ }
4344
+ return d;
4345
+ }
4346
+ function readRelationContext(o) {
4347
+ const v = o[RELATION_CONTEXT];
4348
+ return v instanceof RelationContext ? v : void 0;
4349
+ }
4350
+ function applyHydrate(value, hydrate) {
4351
+ if (hydrate === void 0) return value;
4352
+ if (value === null) return null;
4353
+ if (Array.isArray(value)) return value.map((v) => v === null ? null : hydrate(v));
4354
+ return hydrate(value);
4355
+ }
4356
+ function buildResultSet(rawRows, ops, db, options = {}) {
4357
+ const selected = options.with ?? {};
4358
+ const hydrate = options.hydrate;
4359
+ const proto = {};
4360
+ for (const name of Object.keys(ops)) {
4361
+ if (selected[name] === true) continue;
4362
+ Object.defineProperty(proto, name, {
4363
+ enumerable: false,
4364
+ configurable: true,
4365
+ get() {
4366
+ const ctx2 = readRelationContext(this);
4367
+ if (ctx2 === void 0) {
4368
+ return Promise.reject(
4369
+ new Error(
4370
+ `lazy relation '${name}': the batch context was dropped (spread/clone/JSON); re-read through the query API or declaratively select it via { with: { ${name}: true } }`
4371
+ )
4372
+ );
4373
+ }
4374
+ return Promise.resolve(ctx2.resolve(name, this));
4375
+ }
4376
+ });
4377
+ }
4378
+ const rows = rawRows.map((raw) => {
4379
+ const o = Object.create(proto);
4380
+ for (const k of Object.keys(raw)) o[k] = raw[k];
4381
+ return o;
4382
+ });
4383
+ const ctx = new RelationContext(rows, ops, db, hydrate, options.connections);
4384
+ for (const o of rows) {
4385
+ Object.defineProperty(o, RELATION_CONTEXT, {
4386
+ value: ctx,
4387
+ enumerable: false,
4388
+ writable: false,
4389
+ configurable: false
4390
+ });
4391
+ }
4392
+ for (const name of Object.keys(selected)) {
4393
+ const op = ops[name];
4394
+ if (op === void 0) throw new Error(`declarative select: relation '${name}' is not declared on this model`);
4395
+ const relDb = op.connection === void 0 ? db : driverForOp(op, options.connections);
4396
+ const { batch } = runRelationOp(op, rows, relDb);
4397
+ for (const o of rows) {
4398
+ o[name] = applyHydrate(distributeToParent(op, o, batch), hydrate);
4399
+ }
4400
+ }
4401
+ if (hydrate === void 0) return rows;
4402
+ return rows.map((o) => hydrate(o));
4403
+ }
4404
+
4405
+ // src/scp/runtime.ts
4406
+ function primaryNodeOf(component) {
4407
+ for (const n of component.body) {
4408
+ if ("cond" in n) continue;
4409
+ if ("map" in n) continue;
4410
+ return n;
4411
+ }
4412
+ return void 0;
4413
+ }
4414
+ function isWriteComponent(component) {
4415
+ const p = primaryNodeOf(component);
4416
+ if (p === void 0 || "map" in p) return false;
4417
+ const c = p.component;
4418
+ return c === "Insert" || c === "Update" || c === "Delete";
4419
+ }
4420
+ function executeBehavior(contract, input, options) {
4421
+ return executeBundle(compileBundle(contract, options.entry, options.relations, options.dialect), input, options);
4422
+ }
4423
+ function compileBundle(contract, entry2, relations = [], dialectName = "sqlite") {
4424
+ const component = entry2 ? contract.components.find((c) => c.name === entry2) : contract.components[0];
4425
+ if (component === void 0) throw new Error(`scp runtime: entry component '${entry2 ?? "<first>"}' not found in contract`);
4426
+ const relationOps = {};
4427
+ for (const decl of relations) {
4428
+ if (relationOps[decl.name] !== void 0) {
4429
+ throw new Error(`scp runtime: duplicate relation declaration '${decl.name}'`);
4430
+ }
4431
+ relationOps[decl.name] = compileRelationOp({ ...decl, dialect: decl.dialect ?? dialectName });
4432
+ }
4433
+ if (isWriteComponent(component)) {
4434
+ const writeNode = primaryNodeOf(component);
4435
+ const op = compileWriteNode(writeNode);
4436
+ return {
4437
+ dialect: dialectName,
4438
+ name: component.name,
4439
+ statement: { sql: op.sql, params: op.params },
4440
+ optionalHeads: [],
4441
+ relations: relationOps
4442
+ };
4443
+ }
4444
+ const readGraph = compileReadGraph(contract, dialectName, entry2);
4445
+ return {
4446
+ dialect: dialectName,
4447
+ name: readGraph.name,
4448
+ readGraph,
4449
+ optionalHeads: readGraph.optionalHeads,
4450
+ relations: relationOps
4451
+ };
4452
+ }
4453
+ function executeBundle(bundle, input, options) {
4454
+ if (bundle.statement !== void 0 && bundle.transaction === void 0) {
4455
+ const view = {
4456
+ dialect: bundle.dialect,
4457
+ name: bundle.name,
4458
+ statements: [bundle.statement],
4459
+ optionalHeads: bundle.optionalHeads
4460
+ };
4461
+ return executeStaticWrite(view, input, options.db);
4462
+ }
4463
+ if (bundle.readGraph === void 0) {
4464
+ throw new Error(`scp runtime: bundle '${bundle.name}' carries neither a read graph nor a write statement`);
4465
+ }
4466
+ return executeReadGraph(bundle.readGraph, input, options.db);
4467
+ }
4468
+ async function executeBundleAsync(bundle, input, options) {
4469
+ if (bundle.readGraph === void 0) {
4470
+ throw new Error(
4471
+ `scp runtime: executeBundleAsync requires a READ bundle ('${bundle.name}' carries no read graph); the write-tx path stays synchronous + serial (use executeBundle / executeTransactionBundle).`
4472
+ );
4473
+ }
4474
+ return executeReadGraphAsync(bundle.readGraph, input, options.exec);
4475
+ }
4476
+ async function executeBehaviorAsync(contract, input, options) {
4477
+ return executeBundleAsync(compileBundle(contract, options.entry, options.relations, options.dialect), input, options);
4478
+ }
4479
+ function baseWriteNodeOf(component) {
4480
+ const writes = component.body.filter(
4481
+ (n) => !("cond" in n) && !("map" in n) && (n.component === "Insert" || n.component === "Update" || n.component === "Delete")
4482
+ );
4483
+ if (writes.length === 0) {
4484
+ throw new Error(`scp write: Command '${component.name}' has no base write (Insert/Update/Delete) node`);
4485
+ }
4486
+ if (writes.length > 1) {
4487
+ throw new Error(
4488
+ `scp write: Command '${component.name}' has ${writes.length} base write nodes; WS5 initial scope is a SINGLE-statement Command + write-time relations (multi-write DAG is WS8, spec \xA76/\xA713).`
4489
+ );
4490
+ }
4491
+ return writes[0];
4492
+ }
4493
+ function compileWriteBundle(contract, entry2, writes, phase, dialectName = "sqlite") {
4494
+ const component = contract.components.find((c) => c.name === entry2);
4495
+ if (component === void 0) throw new Error(`scp write: entry component '${entry2}' not found in contract`);
4496
+ const lifecycle = lifecycleFor(writes, phase);
4497
+ if (lifecycle === void 0) {
4498
+ throw new Error(`scp write: the '${phase}' lifecycle is not declared in the entityWrites save contract`);
4499
+ }
4500
+ const writeNode = baseWriteNodeOf(component);
4501
+ const baseOp = dialectName === "mysql" ? mysqlPkHint(compileWriteNode(writeNode)) : compileWriteNode(writeNode);
4502
+ const base = { op: baseOp, label: `${writeNode.component}` };
4503
+ const plan = deriveTransactionPlan(phase, [base], lifecycle, dialectName);
4504
+ return {
4505
+ dialect: dialectName,
4506
+ name: component.name,
4507
+ statement: { sql: baseOp.sql, params: baseOp.params },
4508
+ optionalHeads: [],
4509
+ relations: {},
4510
+ transaction: plan
4511
+ };
4512
+ }
4513
+ function compileCompositeWriteBundle(contract, entries, phase, dialectName = "sqlite") {
4514
+ if (entries.length < 2) {
4515
+ throw new Error("scp write: a composite write bundle needs at least 2 named write members (use compileWriteBundle for a single write).");
4516
+ }
4517
+ const bases = [];
4518
+ let firstBaseOp;
4519
+ let firstName = "";
4520
+ for (const e of entries) {
4521
+ const component = contract.components.find((c) => c.name === e.entry);
4522
+ if (component === void 0) throw new Error(`scp write: entry component '${e.entry}' not found in contract`);
4523
+ if (firstBaseOp === void 0) firstName = component.name;
4524
+ const writeNode = baseWriteNodeOf(component);
4525
+ const baseOp = compileWriteNode(writeNode);
4526
+ if (firstBaseOp === void 0) firstBaseOp = baseOp;
4527
+ bases.push({ op: baseOp, label: `${writeNode.component} ${e.name}`, name: e.name, effects: e.lifecycle.effects });
4528
+ }
4529
+ const plan = deriveTransactionPlan(phase, bases, { effects: {} }, dialectName);
4530
+ return {
4531
+ dialect: dialectName,
4532
+ name: firstName,
4533
+ statement: { sql: firstBaseOp.sql, params: firstBaseOp.params },
4534
+ optionalHeads: [],
4535
+ relations: {},
4536
+ transaction: plan
4537
+ };
4538
+ }
4539
+ function flattenBatchOp(node, label) {
4540
+ const assembled = assembleMakeSQL(node);
4541
+ return { sql: assembled.sql, params: assembled.params, label };
4542
+ }
4543
+ function compileCreateManyBundle(name, options, dialectName = "sqlite") {
4544
+ const components2 = compileInsertMany(dialectName, options);
4545
+ const ops = components2.map((c, i) => {
4546
+ const flat = flattenBatchOp(c, `createMany group ${i}`);
4547
+ if (dialectName === "mysql" && options.pk !== void 0 && options.returning !== void 0) {
4548
+ return { ...flat, ...mysqlPkHint({ sql: flat.sql, params: flat.params, pk: options.pk }) };
4549
+ }
4550
+ return flat;
4551
+ });
4552
+ const plan = deriveBatchPlan("create", ops);
4553
+ return { dialect: dialectName, name, statement: { sql: ops[0]?.sql ?? "", params: ops[0]?.params ?? [] }, optionalHeads: [], relations: {}, transaction: plan };
4554
+ }
4555
+ function compileUpdateManyBundle(name, options, dialectName = "sqlite") {
4556
+ const op = flattenBatchOp(compileUpdateMany(dialectName, options), "updateMany");
4557
+ const plan = deriveBatchPlan("update", [op]);
4558
+ return { dialect: dialectName, name, statement: { sql: op.sql, params: op.params }, optionalHeads: [], relations: {}, transaction: plan };
4559
+ }
4560
+ function compileDeleteManyBundle(name, options, dialectName = "sqlite") {
4561
+ const components2 = compileDeleteMany({ dialect: dialectName, ...options });
4562
+ const ops = components2.map((c, i) => flattenBatchOp(c, `deleteMany group ${i}`));
4563
+ if (ops.length === 0) {
4564
+ return { dialect: dialectName, name, statement: { sql: "", params: [] }, optionalHeads: [], relations: {}, transaction: { phase: "remove", entityFrom: null, statements: [], onIdempotentHit: "rollback" } };
4565
+ }
4566
+ const plan = deriveBatchPlan("remove", ops);
4567
+ return { dialect: dialectName, name, statement: { sql: ops[0].sql, params: ops[0].params }, optionalHeads: [], relations: {}, transaction: plan };
4568
+ }
4569
+ function executeCompositeCommand(contract, entries, phase, input, options) {
4570
+ const bundle = compileCompositeWriteBundle(contract, entries, phase, options.dialect);
4571
+ return executeTransactionBundle(bundle, input, options);
4572
+ }
4573
+ function executeCommand(contract, writes, phase, input, options) {
4574
+ const bundle = compileWriteBundle(contract, options.entry, writes, phase, options.dialect);
4575
+ return executeTransactionBundle(bundle, input, options);
4576
+ }
4577
+ function executeTransactionBundle(bundle, input, options) {
4578
+ if (bundle.transaction === void 0) {
4579
+ throw new Error("scp write: this bundle carries no transaction plan (not a write-time-relations Command bundle)");
4580
+ }
4581
+ return executeTransaction(options.db, bundle.transaction, input, bundle.dialect);
4582
+ }
4583
+ function read(contract, input, options) {
4584
+ return readBundle(compileBundle(contract, options.entry, options.relations, options.dialect), input, options);
4585
+ }
4586
+ function readBundle(bundle, input, options) {
4587
+ const out = executeBundle(bundle, input, options);
4588
+ if (!Array.isArray(out)) {
4589
+ throw new Error(
4590
+ `scp read: the read behavior output is not a row list (got ${out === null ? "null" : typeof out}); the typed-object read surface expects a Select-shaped output`
4591
+ );
4592
+ }
4593
+ const rawRows = out;
4594
+ const readOpts = {
4595
+ ...options.with !== void 0 ? { with: options.with } : {},
4596
+ ...options.hydrate !== void 0 ? { hydrate: options.hydrate } : {},
4597
+ // CROSS-DB relations (V0 R1): forward the connection registry so a tagged relation routes to
4598
+ // its target DB. Absent for a single-DB read (every relation runs on the primary `db`).
4599
+ ...options.connections !== void 0 ? { connections: options.connections } : {}
4600
+ };
4601
+ return buildResultSet(rawRows, bundle.relations, options.db, readOpts);
4602
+ }
4603
+
4604
+ // src/scp/codegen.ts
4605
+ import { generateModule } from "behavior-contracts";
4606
+ var CODEGEN_LANGUAGES = ["typescript", "python", "go", "rust", "php"];
4607
+ function bundleToPortableIR(bundle) {
4608
+ if (bundle.readGraph !== void 0) return bundle.readGraph.ir;
4609
+ return makeSqlComponentIR(bundle.name);
4610
+ }
4611
+ function companionOf(bundle) {
4612
+ const base = {
4613
+ dialect: bundle.dialect,
4614
+ optionalHeads: bundle.optionalHeads,
4615
+ relations: bundle.relations,
4616
+ ...bundle.readGraph !== void 0 ? { readGraph: bundle.readGraph } : {},
4617
+ ...bundle.statement !== void 0 ? { statement: bundle.statement } : {}
4618
+ };
4619
+ return bundle.transaction === void 0 ? base : { ...base, transaction: bundle.transaction };
4620
+ }
4621
+ function assertLanguageSupported(language, registered) {
4622
+ if (!CODEGEN_LANGUAGES.includes(language)) {
4623
+ throw new Error(
4624
+ `litedbmodel codegen: language '${language}' is not a supported target (supported: ${CODEGEN_LANGUAGES.join(", ")})`
4625
+ );
4626
+ }
4627
+ if (!registered.includes(language)) {
4628
+ throw new Error(
4629
+ `litedbmodel codegen: bc's shared generator does not register an emitter for '${language}' (bc registered: ${[...registered].sort().join(", ")}). This is a bc#13 capability limit \u2014 ESCALATE to bc (bc#22 pattern), never fork a litedbmodel-local generator (codegen \u306F\u4E0A\u6D41\u6240\u6709, spec \xA79).`
4630
+ );
4631
+ }
4632
+ }
4633
+ function generateCodegenArtifact(bundle, language, registeredLanguages, runtimeImport) {
4634
+ assertLanguageSupported(language, registeredLanguages);
4635
+ const ir = bundleToPortableIR(bundle);
4636
+ const module = generateModule(ir, runtimeImport === void 0 ? { language } : { language, runtimeImport });
4637
+ return { language, module, companion: companionOf(bundle), ir, bundle };
4638
+ }
4639
+ function codegenExecuteBundleForTest(artifact, input, db) {
4640
+ return executeBundle(artifact.bundle, input, { db });
4641
+ }
4642
+
4643
+ // src/scp/index.ts
4644
+ import {
4645
+ SemanticBehavior as SemanticBehavior2,
4646
+ behavior,
4647
+ when,
4648
+ concat,
4649
+ add,
4650
+ sub,
4651
+ mul,
4652
+ div,
4653
+ mod,
4654
+ neg,
4655
+ eq as eq2,
4656
+ ne as ne2,
4657
+ lt as lt2,
4658
+ le as le2,
4659
+ gt as gt2,
4660
+ ge as ge2,
4661
+ and,
4662
+ or,
4663
+ not,
4664
+ coalesce,
4665
+ len,
4666
+ opt
4667
+ } from "behavior-contracts";
4668
+ export {
4669
+ CATALOG_NAMES,
4670
+ CODEGEN_LANGUAGES,
4671
+ ENTITY_ROOT,
4672
+ FindFilterLeakError,
4673
+ LITEDBMODEL_CATALOG,
4674
+ LITEDBMODEL_MAKESQL_CATALOG,
4675
+ MAKESQL,
4676
+ MYSQL,
4677
+ PG_ARRAY_CAST_TOKEN,
4678
+ POSTGRES,
4679
+ RELATION_CONTEXT,
4680
+ RELATION_KEYS_HEAD,
4681
+ RelationContext,
4682
+ SQLITE,
4683
+ SemanticBehavior2 as SemanticBehavior,
4684
+ SqlFailure,
4685
+ WRITE_CATALOG_NAMES,
4686
+ WRITE_PORT_FAMILIES,
4687
+ add,
4688
+ and,
4689
+ andTrailing,
4690
+ assembleMakeSQL,
4691
+ assertComponentGraphPortable,
4692
+ assertComponentPortable,
4693
+ assertComponentsInCatalog,
4694
+ assertExprPortable,
4695
+ assertFindFilterFolded,
4696
+ assertLanguageSupported,
4697
+ behavior,
4698
+ buildResultSet,
4699
+ builderFor,
4700
+ bundleToPortableIR,
4701
+ catalogEntry,
4702
+ coalesce,
4703
+ codegenExecuteBundleForTest,
4704
+ compileAuthoredBehavior,
4705
+ compileAuthoredNode,
4706
+ compileBundle,
4707
+ compileCompositeKeyLimited,
4708
+ compileCompositeKeyStaticUnlimited,
4709
+ compileCompositeKeyUnlimited,
4710
+ compileCompositeWriteBundle,
4711
+ compileCreateManyBundle,
4712
+ compileDelete as compileDeleteMakeSQL,
4713
+ compileDeleteMany,
4714
+ compileDeleteManyBundle,
4715
+ compileEager,
4716
+ compileInsert as compileInsertMakeSQL,
4717
+ compileReadGraph,
4718
+ compileRelationMap,
4719
+ compileRelationOp,
4720
+ compileSelect as compileSelectMakeSQL,
4721
+ compileSelectNode,
4722
+ compileSingleKeyLimited,
4723
+ compileSingleKeyUnlimited,
4724
+ compileStaticBundle,
4725
+ compileUpdateMany,
4726
+ compileUpdateManyBundle,
4727
+ compileUpdateSingle,
4728
+ compileWhere,
4729
+ compileWriteBundle,
4730
+ compileWriteNode,
4731
+ components,
4732
+ composeMakeSQL,
4733
+ concat,
4734
+ countingDriver,
4735
+ deriveContractEffect,
4736
+ deriveTransactionPlan,
4737
+ dialectFor,
4738
+ distributeToParent,
4739
+ div,
4740
+ edgeWrites,
4741
+ entityWrites,
4742
+ eq2 as eq,
4743
+ executeBehavior,
4744
+ executeBehaviorAsync,
4745
+ executeBundle,
4746
+ executeBundleAsync,
4747
+ executeCommand,
4748
+ executeCompositeCommand,
4749
+ executeReadBehavior,
4750
+ executeReadGraph,
4751
+ executeReadGraphAsync,
4752
+ executeStaticBundle,
4753
+ executeStaticWrite,
4754
+ executeTransaction,
4755
+ executeTransactionBundle,
4756
+ findFilterKeys,
4757
+ formatterFor,
4758
+ ge2 as ge,
4759
+ generateCodegenArtifact,
4760
+ gt2 as gt,
4761
+ inColumn,
4762
+ inferPgArrayType,
4763
+ isMakeSQL,
4764
+ le2 as le,
4765
+ len,
4766
+ lifecycleFor,
4767
+ lt2 as lt,
4768
+ makeSqlCatalogEntry,
4769
+ makeSqlComponentIR,
4770
+ makeSqlHandler,
4771
+ makeSqlHandlerSync,
4772
+ makeSqlInput,
4773
+ mapSqliteError,
4774
+ mod,
4775
+ mul,
4776
+ mysqlPkHint,
4777
+ mysqlPoolExecutor,
4778
+ ne2 as ne,
4779
+ neg,
4780
+ noCastFormatter,
4781
+ not,
4782
+ opt,
4783
+ or,
4784
+ parseEffectPath,
4785
+ pgCastFormatter,
4786
+ pgPoolExecutor,
4787
+ publishBehaviors,
4788
+ read,
4789
+ readBundle,
4790
+ readRelationContext,
4791
+ renderPlaceholders,
4792
+ renderPorts,
4793
+ renderReadPrimary,
4794
+ renderTxStatement,
4795
+ resolvePgArrayCast,
4796
+ runRelationOp,
4797
+ stripMysqlPkHint,
4798
+ sub,
4799
+ toDollarPlaceholders,
4800
+ when,
4801
+ whereBetween,
4802
+ whereCast,
4803
+ whereClause,
4804
+ whereDynamic,
4805
+ whereEq,
4806
+ whereExists,
4807
+ whereGe,
4808
+ whereGt,
4809
+ whereILike,
4810
+ whereImmediate,
4811
+ whereIn,
4812
+ whereInSubquery,
4813
+ whereIsNull,
4814
+ whereLe,
4815
+ whereLike,
4816
+ whereLt,
4817
+ whereNe,
4818
+ whereTupleIn
4819
+ };