tempest-db-js 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -14,6 +14,142 @@ function toCondNode(input) {
14
14
  function wrap(node) {
15
15
  return { [CONDITION]: true, node };
16
16
  }
17
+ function toExprNode(operand) {
18
+ return isExpression(operand) ? operand.node : { kind: "value", value: operand };
19
+ }
20
+ function assertValueOperands(op, operands) {
21
+ if (operands.some(isExpression)) {
22
+ throw new Error(
23
+ `The "${op}" operator binds its operands, so it takes values, not expressions.`
24
+ );
25
+ }
26
+ }
27
+ function isExpression(value) {
28
+ return value instanceof Expression;
29
+ }
30
+ var Expression = class {
31
+ constructor(node) {
32
+ this.node = node;
33
+ }
34
+ node;
35
+ /** Compare this expression against another expression or a bound value. */
36
+ compare(op, operand) {
37
+ return wrap({ kind: "compare", left: this.node, op, right: toExprNode(operand) });
38
+ }
39
+ /** `=` (or `IS NULL` for a null value). */
40
+ eq(operand) {
41
+ return this.compare("eq", operand);
42
+ }
43
+ /** `<>` (or `IS NOT NULL` for a null value). */
44
+ ne(operand) {
45
+ return this.compare("ne", operand);
46
+ }
47
+ /** `>`. */
48
+ gt(operand) {
49
+ return this.compare("gt", operand);
50
+ }
51
+ /** `>=`. */
52
+ gte(operand) {
53
+ return this.compare("gte", operand);
54
+ }
55
+ /** `<`. */
56
+ lt(operand) {
57
+ return this.compare("lt", operand);
58
+ }
59
+ /** `<=`. */
60
+ lte(operand) {
61
+ return this.compare("lte", operand);
62
+ }
63
+ /** `LIKE` — `%` and `_` in the operand are wildcards. */
64
+ like(pattern) {
65
+ return this.compare("like", pattern);
66
+ }
67
+ /** `ILIKE` — case-insensitive **pattern** matching, wildcards included. */
68
+ ilike(pattern) {
69
+ return this.compare("ilike", pattern);
70
+ }
71
+ /** Case-insensitive equality (`lower(a) = lower(b)`), with no wildcards. */
72
+ ieq(operand) {
73
+ return this.compare("ieq", operand);
74
+ }
75
+ /**
76
+ * `IN (...)` over a list of values.
77
+ *
78
+ * @param values The values to test against.
79
+ * @returns The condition.
80
+ * @throws Error When an entry is an {@link Expression} — a list operand is
81
+ * bound, so an expression there would be serialized as a parameter instead of
82
+ * rendered as SQL.
83
+ */
84
+ in(values) {
85
+ assertValueOperands("in", values);
86
+ return this.compare("in", values);
87
+ }
88
+ /**
89
+ * `NOT IN (...)` over a list of values.
90
+ *
91
+ * @param values The values to exclude.
92
+ * @returns The condition.
93
+ * @throws Error When an entry is an {@link Expression} (see {@link Expression.in}).
94
+ */
95
+ notIn(values) {
96
+ assertValueOperands("notIn", values);
97
+ return this.compare("notIn", values);
98
+ }
99
+ /**
100
+ * `BETWEEN lo AND hi` (inclusive).
101
+ *
102
+ * @param lo The lower bound.
103
+ * @param hi The upper bound.
104
+ * @returns The condition.
105
+ * @throws Error When a bound is an {@link Expression} (see {@link Expression.in}).
106
+ */
107
+ between(lo, hi) {
108
+ assertValueOperands("between", [lo, hi]);
109
+ return this.compare("between", [lo, hi]);
110
+ }
111
+ /** `IS NULL` (true) / `IS NOT NULL` (false). */
112
+ isNull(value = true) {
113
+ return this.compare("isNull", value);
114
+ }
115
+ };
116
+ function col(name) {
117
+ return new Expression({ kind: "column", name });
118
+ }
119
+ function val(value) {
120
+ return new Expression({ kind: "value", value });
121
+ }
122
+ var FUNCTION_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
123
+ function toArg(arg) {
124
+ return typeof arg === "string" ? { kind: "column", name: arg } : arg.node;
125
+ }
126
+ function call(name, ...args) {
127
+ if (!FUNCTION_NAME.test(name)) {
128
+ throw new Error(
129
+ `fn.call() takes a plain SQL function name; got ${JSON.stringify(name)}.`
130
+ );
131
+ }
132
+ return new Expression({ kind: "fn", name, args: args.map(toArg) });
133
+ }
134
+ var fn = {
135
+ /** `lower(x)`. */
136
+ lower: (arg) => call("lower", arg),
137
+ /** `upper(x)`. */
138
+ upper: (arg) => call("upper", arg),
139
+ /** `trim(x)`. */
140
+ trim: (arg) => call("trim", arg),
141
+ /** `length(x)`. */
142
+ length: (arg) => call("length", arg),
143
+ /** `abs(x)`. */
144
+ abs: (arg) => call("abs", arg),
145
+ /** `coalesce(a, b, ...)`. */
146
+ coalesce: (...args) => call("coalesce", ...args),
147
+ /**
148
+ * Any other SQL function, by name. Portability is the caller's problem —
149
+ * `date_trunc` is PostgreSQL, `strftime` is SQLite.
150
+ */
151
+ call
152
+ };
17
153
  function and(...inputs) {
18
154
  return wrap({
19
155
  kind: "and",
@@ -31,6 +167,9 @@ function not(input) {
31
167
  }
32
168
 
33
169
  // src/query.ts
170
+ function isSubquery(value) {
171
+ return typeof value === "object" && value !== null && value.node?.kind === "select";
172
+ }
34
173
  var OPERATORS = [
35
174
  "eq",
36
175
  "ne",
@@ -40,14 +179,18 @@ var OPERATORS = [
40
179
  "lte",
41
180
  "like",
42
181
  "ilike",
182
+ "ieq",
43
183
  "in",
44
184
  "notIn",
45
185
  "between",
46
- "isNull"
186
+ "isNull",
187
+ "contains",
188
+ "containedBy",
189
+ "overlaps"
47
190
  ];
48
191
  var Agg = class {
49
- constructor(fn, column2) {
50
- this.fn = fn;
192
+ constructor(fn2, column2) {
193
+ this.fn = fn2;
51
194
  this.column = column2;
52
195
  }
53
196
  fn;
@@ -76,12 +219,41 @@ var SelectBuilder = class _SelectBuilder {
76
219
  node;
77
220
  source;
78
221
  with(patch) {
79
- return new _SelectBuilder({ ...this.node, ...patch }, this.source);
222
+ return new _SelectBuilder(
223
+ { ...this.node, ...patch },
224
+ this.source
225
+ );
80
226
  }
81
227
  /** Add a WHERE filter: the object form (keys typed) or an `and`/`or`/`not`. */
82
228
  where(input) {
83
229
  return this.with({ where: toCondNode(input) });
84
230
  }
231
+ /**
232
+ * Filter by the result of the aggregation (`HAVING`).
233
+ *
234
+ * Only available on a grouped builder — `.having()` before `.aggregate()` is a
235
+ * compile error, not invalid SQL at runtime. Keys are the aggregate aliases you
236
+ * named plus the grouped columns; `WHERE` still filters rows *before* grouping,
237
+ * which is a different question.
238
+ *
239
+ * @param input The condition, keyed by alias or grouped column.
240
+ * @returns A builder carrying the `HAVING` clause.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * select(Outbound)
245
+ * .where({ status: "queued" })
246
+ * .aggregate(["consumer"], { n: count() })
247
+ * .having({ n: { gt: 100 } });
248
+ * // ... GROUP BY "consumer" HAVING COUNT(*) > $2
249
+ * ```
250
+ */
251
+ having(input) {
252
+ return new _SelectBuilder(
253
+ { ...this.node, having: toCondNode(input) },
254
+ this.source
255
+ );
256
+ }
85
257
  /** Emit `SELECT DISTINCT` — drop duplicate rows. */
86
258
  distinct() {
87
259
  return this.with({ distinct: true });
@@ -113,7 +285,15 @@ var SelectBuilder = class _SelectBuilder {
113
285
  this.source
114
286
  );
115
287
  }
116
- /** Order by a column of `Full`. */
288
+ /**
289
+ * Order by a column of the model, or — on a grouped query — by an aggregate
290
+ * alias. Unlike `HAVING`, every dialect accepts the output alias in `ORDER BY`,
291
+ * so the alias is emitted as written.
292
+ *
293
+ * @param column A model column, or a projected alias.
294
+ * @param direction `"asc"` (default) or `"desc"`.
295
+ * @returns A builder carrying the ordering term.
296
+ */
117
297
  orderBy(column2, direction = "asc") {
118
298
  return this.with({
119
299
  orderBy: [...this.node.orderBy, { column: column2, direction }]
@@ -127,7 +307,87 @@ var SelectBuilder = class _SelectBuilder {
127
307
  offset(n) {
128
308
  return this.with({ offset: n });
129
309
  }
310
+ /**
311
+ * Narrow this SELECT to a single column and mark it as a subquery, so it can be
312
+ * the operand of `in` / `notIn`.
313
+ *
314
+ * The whole query — `where`, `orderBy`, `limit`, and a locking clause — is
315
+ * embedded in the outer statement, which is what collapses the claim-a-batch
316
+ * pattern into one round trip instead of selecting ids and sending them back.
317
+ *
318
+ * @param column The single column to project (checked against the model).
319
+ * @returns A subquery carrying that column's type.
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * update(Outbound)
324
+ * .set({ status: "sending", attempts: sql.raw("attempts + 1") })
325
+ * .where({
326
+ * id: {
327
+ * in: select(Outbound)
328
+ * .where({ status: "queued" })
329
+ * .orderBy("nextAttemptAt")
330
+ * .limit(10)
331
+ * .forUpdate({ skipLocked: true })
332
+ * .asSubquery("id"),
333
+ * },
334
+ * })
335
+ * .returning();
336
+ * ```
337
+ */
338
+ asSubquery(column2) {
339
+ return { node: { ...this.node, columns: [column2] } };
340
+ }
341
+ /**
342
+ * Lock the selected rows for update (`SELECT ... FOR UPDATE`), à la
343
+ * SQLAlchemy's `with_for_update()`.
344
+ *
345
+ * `{ skipLocked: true }` is the job-queue claim: competing workers each take a
346
+ * disjoint batch instead of blocking on — or worse, double-processing — the
347
+ * same rows.
348
+ *
349
+ * PostgreSQL and MySQL 8.0+ only. SQLite has no row-level locking, and its
350
+ * dialect throws rather than emitting a `SELECT` that silently locks nothing —
351
+ * a lock that does not exist only fails under production concurrency.
352
+ *
353
+ * @param options `skipLocked` / `noWait` wait behavior, and `of` to restrict
354
+ * the lock to specific tables.
355
+ * @returns A builder carrying the locking clause.
356
+ * @throws Error When both `skipLocked` and `noWait` are set.
357
+ *
358
+ * @example
359
+ * ```ts
360
+ * const batch = await session.execute(
361
+ * select(Outbound)
362
+ * .where({ status: "queued" })
363
+ * .orderBy("nextAttemptAt")
364
+ * .limit(10)
365
+ * .forUpdate({ skipLocked: true }),
366
+ * ).all();
367
+ * ```
368
+ */
369
+ forUpdate(options) {
370
+ return this.with({ lock: buildLock("update", options) });
371
+ }
372
+ /**
373
+ * Take a shared read lock on the selected rows (`SELECT ... FOR SHARE`), the
374
+ * weaker counterpart of {@link SelectBuilder.forUpdate}.
375
+ *
376
+ * @param options `skipLocked` / `noWait` wait behavior, and `of` tables.
377
+ * @returns A builder carrying the locking clause.
378
+ * @throws Error When both `skipLocked` and `noWait` are set.
379
+ */
380
+ forShare(options) {
381
+ return this.with({ lock: buildLock("share", options) });
382
+ }
130
383
  };
384
+ function buildLock(strength, options) {
385
+ if (options?.skipLocked && options?.noWait) {
386
+ throw new Error("forUpdate/forShare accept skipLocked or noWait, not both.");
387
+ }
388
+ const wait = options?.skipLocked ? "skipLocked" : options?.noWait ? "noWait" : "block";
389
+ return { strength, wait, of: options?.of ?? [] };
390
+ }
131
391
  function select(model, columns) {
132
392
  return new SelectBuilder(
133
393
  {
@@ -140,13 +400,223 @@ function select(model, columns) {
140
400
  where: void 0,
141
401
  orderBy: [],
142
402
  limit: void 0,
143
- offset: void 0
403
+ offset: void 0,
404
+ names: columnNamesOf(model) ?? void 0
144
405
  },
145
406
  model
146
407
  );
147
408
  }
148
409
 
410
+ // src/serialize.ts
411
+ var ValidationError = class extends Error {
412
+ constructor(table, issues) {
413
+ super(`Validation failed for ${table}:
414
+ - ${issues.join("\n - ")}`);
415
+ this.table = table;
416
+ this.issues = issues;
417
+ this.name = "ValidationError";
418
+ }
419
+ table;
420
+ issues;
421
+ };
422
+ function toBase64(bytes) {
423
+ if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
424
+ let binary = "";
425
+ for (const byte of bytes) binary += String.fromCharCode(byte);
426
+ return btoa(binary);
427
+ }
428
+ function fromBase64(value) {
429
+ if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64"));
430
+ const binary = atob(value);
431
+ const bytes = new Uint8Array(binary.length);
432
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
433
+ return bytes;
434
+ }
435
+ function encodeValue(column2, value) {
436
+ if (value === null || value === void 0) return null;
437
+ switch (column2.type.kind) {
438
+ case "bigint":
439
+ return typeof value === "bigint" ? value.toString() : value;
440
+ case "date":
441
+ case "datetime":
442
+ case "timestamp":
443
+ return value instanceof Date ? value.toISOString() : value;
444
+ case "blob":
445
+ return value instanceof Uint8Array ? toBase64(value) : value;
446
+ default:
447
+ return value;
448
+ }
449
+ }
450
+ function decodeValue(column2, value) {
451
+ if (value === null || value === void 0) return null;
452
+ switch (column2.type.kind) {
453
+ case "bigint":
454
+ return typeof value === "bigint" ? value : BigInt(value);
455
+ case "date":
456
+ case "datetime":
457
+ case "timestamp":
458
+ return value instanceof Date ? value : new Date(value);
459
+ case "blob":
460
+ return value instanceof Uint8Array ? value : fromBase64(value);
461
+ case "json":
462
+ return typeof value === "string" ? JSON.parse(value) : value;
463
+ case "array": {
464
+ const element = column2.type.meta.element;
465
+ const items = typeof value === "string" ? JSON.parse(value) : value;
466
+ if (!Array.isArray(items)) return items;
467
+ if (!element) return items;
468
+ return items.map((item) => decodeValue({ type: element }, item));
469
+ }
470
+ case "numeric":
471
+ return typeof value === "string" ? value : String(value);
472
+ case "boolean":
473
+ return typeof value === "boolean" ? value : value === 1 || value === "true";
474
+ case "smallint":
475
+ case "integer":
476
+ case "real":
477
+ case "double":
478
+ return typeof value === "number" ? value : Number(value);
479
+ default:
480
+ return value;
481
+ }
482
+ }
483
+ function toDict(model, row) {
484
+ const columns = columnsOf(model);
485
+ const out = {};
486
+ for (const name of Object.keys(columns)) {
487
+ out[name] = row[name] ?? null;
488
+ }
489
+ return out;
490
+ }
491
+ function toJSON(model, row) {
492
+ const columns = columnsOf(model);
493
+ const out = {};
494
+ for (const [name, col2] of Object.entries(columns)) {
495
+ out[name] = encodeValue(col2, row[name] ?? null);
496
+ }
497
+ return out;
498
+ }
499
+ function stringify(model, row) {
500
+ return JSON.stringify(toJSON(model, row));
501
+ }
502
+ function fromDict(model, data) {
503
+ const columns = columnsOf(model);
504
+ const out = {};
505
+ const issues = [];
506
+ for (const [name, col2] of Object.entries(columns)) {
507
+ const present = name in data && data[name] !== void 0 && data[name] !== null;
508
+ if (!present) {
509
+ const required = col2.flags.notNull && !col2.flags.hasDefault;
510
+ if (required) {
511
+ issues.push(`missing required column "${name}"`);
512
+ continue;
513
+ }
514
+ out[name] = null;
515
+ continue;
516
+ }
517
+ try {
518
+ out[name] = decodeValue(col2, data[name]);
519
+ } catch (error) {
520
+ issues.push(`column "${name}": ${error.message}`);
521
+ }
522
+ }
523
+ if (issues.length > 0) {
524
+ throw new ValidationError(model.tablename, issues);
525
+ }
526
+ return out;
527
+ }
528
+ function parse(model, json) {
529
+ return fromDict(model, JSON.parse(json));
530
+ }
531
+ function decoderFor(type) {
532
+ switch (type.kind) {
533
+ case "bigint":
534
+ return (v) => v == null ? null : typeof v === "bigint" ? v : BigInt(v);
535
+ case "date":
536
+ case "datetime":
537
+ case "timestamp":
538
+ return (v) => v == null ? null : v instanceof Date ? v : new Date(v);
539
+ case "blob":
540
+ return (v) => v == null ? null : v instanceof Uint8Array ? v : fromBase64(v);
541
+ case "json":
542
+ return (v) => v == null ? null : typeof v === "string" ? JSON.parse(v) : v;
543
+ case "array": {
544
+ const element = type.meta.element;
545
+ const inner = element ? decoderFor(element) : null;
546
+ if (!inner) return null;
547
+ return (v) => v == null ? null : Array.isArray(v) ? v.map(inner) : v;
548
+ }
549
+ case "numeric":
550
+ return (v) => v == null ? null : typeof v === "string" ? v : String(v);
551
+ case "boolean":
552
+ return (v) => v == null ? null : typeof v === "boolean" ? v : v === 1 || v === "true";
553
+ case "smallint":
554
+ case "integer":
555
+ case "real":
556
+ case "double":
557
+ return (v) => v == null ? null : typeof v === "number" ? v : Number(v);
558
+ default:
559
+ return null;
560
+ }
561
+ }
562
+ var mapperCache = /* @__PURE__ */ new WeakMap();
563
+ function mapperFor(model) {
564
+ const cached = mapperCache.get(model);
565
+ if (cached) return cached;
566
+ const props = columnPropsOf(model);
567
+ const names = props ? Object.fromEntries(Object.entries(props).map(([db, prop]) => [prop, db])) : null;
568
+ const decoders = /* @__PURE__ */ new Map();
569
+ for (const [prop, col2] of Object.entries(columnsOf(model))) {
570
+ const decoder = decoderFor(col2.type);
571
+ if (decoder) decoders.set(names?.[prop] ?? prop, decoder);
572
+ }
573
+ const mapper = { props, decoders };
574
+ mapperCache.set(model, mapper);
575
+ return mapper;
576
+ }
577
+ function coerceRow(model, raw) {
578
+ const { props, decoders } = mapperFor(model);
579
+ const out = {};
580
+ for (const name of Object.keys(raw)) {
581
+ const decode2 = decoders.get(name);
582
+ out[props?.[name] ?? name] = decode2 ? decode2(raw[name]) : raw[name];
583
+ }
584
+ return out;
585
+ }
586
+
149
587
  // src/mutations.ts
588
+ var STRUCTURED_KINDS = /* @__PURE__ */ new Set(["json", "array", "blob"]);
589
+ function isBindableScalar(value) {
590
+ if (value === null || value === void 0) return true;
591
+ const type = typeof value;
592
+ if (type === "string" || type === "number" || type === "bigint" || type === "boolean") {
593
+ return true;
594
+ }
595
+ return value instanceof Date || value instanceof Uint8Array;
596
+ }
597
+ function assertWritableValues(model, values, clause) {
598
+ const columns = columnsOf(model);
599
+ const issues = [];
600
+ for (const [key, value] of Object.entries(values)) {
601
+ const col2 = columns[key];
602
+ if (!col2) {
603
+ issues.push(`${clause}: "${key}" is not a column of ${model.tablename}`);
604
+ continue;
605
+ }
606
+ if (isSqlExpression(value) || isBindableScalar(value)) continue;
607
+ if (typeof value === "object" && STRUCTURED_KINDS.has(col2.type.kind)) continue;
608
+ issues.push(
609
+ `${clause}: "${key}" got ${describeValue(value)}, which cannot be bound to a ${col2.type.kind} column \u2014 use sql.raw()/sql.expr\`...\` for a SQL expression`
610
+ );
611
+ }
612
+ if (issues.length > 0) throw new ValidationError(model.tablename, issues);
613
+ }
614
+ function describeValue(value) {
615
+ if (typeof value === "function") return "a function";
616
+ if (Array.isArray(value)) return "an array";
617
+ if (typeof value === "symbol") return "a symbol";
618
+ return "an object";
619
+ }
150
620
  var InsertBuilder = class _InsertBuilder {
151
621
  constructor(node, source) {
152
622
  this.node = node;
@@ -157,28 +627,66 @@ var InsertBuilder = class _InsertBuilder {
157
627
  with(patch) {
158
628
  return new _InsertBuilder({ ...this.node, ...patch }, this.source);
159
629
  }
160
- /** Provide one row or many rows to insert, typed by the insert shape. */
630
+ /**
631
+ * Provide one row or many rows to insert, typed by the insert shape.
632
+ *
633
+ * @param rows One row, or an array of rows.
634
+ * @returns A builder carrying the rows.
635
+ * @throws ValidationError When a value is not a column value the dialect can
636
+ * bind (see the `sql` helpers for writing an expression instead).
637
+ */
161
638
  values(rows) {
162
639
  const list = Array.isArray(rows) ? rows : [rows];
640
+ for (const row of list) assertWritableValues(this.source, row, "values");
163
641
  return this.with({ values: list });
164
642
  }
165
643
  /**
166
644
  * On a unique/PK conflict on `target`, do nothing (skip the row).
167
645
  *
168
646
  * @param target The conflicting column(s) — a unique or primary key.
647
+ * @param options Pass `where` to name the predicate of a **partial** unique
648
+ * index, which PostgreSQL requires in order to match it as a conflict target.
649
+ * @returns A builder carrying the conflict clause.
650
+ *
651
+ * @example
652
+ * ```ts
653
+ * insert(Outbound)
654
+ * .values(data)
655
+ * .onConflictDoNothing(["consumer", "idempotencyKey"], {
656
+ * where: { idempotencyKey: { isNull: false } },
657
+ * })
658
+ * .returning();
659
+ * ```
169
660
  */
170
- onConflictDoNothing(target) {
171
- return this.with({ onConflict: { target, update: "nothing" } });
661
+ onConflictDoNothing(target, options) {
662
+ return this.with({
663
+ onConflict: {
664
+ target,
665
+ update: "nothing",
666
+ targetWhere: options?.where ? toCondNode(options.where) : void 0
667
+ }
668
+ });
172
669
  }
173
670
  /**
174
671
  * On a unique/PK conflict on `target`, overwrite the given columns (upsert).
175
672
  *
176
673
  * @param target The conflicting column(s) — a unique or primary key.
177
674
  * @param set The columns to update with new values.
675
+ * @param options `indexWhere` names the predicate of a partial unique index
676
+ * (the conflict target); `updateWhere` further restricts which conflicting
677
+ * rows are rewritten.
678
+ * @returns A builder carrying the conflict clause.
679
+ * @throws ValidationError When a `set` value cannot be bound.
178
680
  */
179
- onConflictDoUpdate(target, set) {
681
+ onConflictDoUpdate(target, set, options) {
682
+ assertWritableValues(this.source, set, "set");
180
683
  return this.with({
181
- onConflict: { target, update: set }
684
+ onConflict: {
685
+ target,
686
+ update: set,
687
+ targetWhere: options?.indexWhere ? toCondNode(options.indexWhere) : void 0,
688
+ updateWhere: options?.updateWhere ? toCondNode(options.updateWhere) : void 0
689
+ }
182
690
  });
183
691
  }
184
692
  returning(columns) {
@@ -191,7 +699,8 @@ function insert(model) {
191
699
  kind: "insert",
192
700
  table: model.tablename,
193
701
  values: [],
194
- returning: null
702
+ returning: null,
703
+ names: columnNamesOf(model) ?? void 0
195
704
  },
196
705
  model
197
706
  );
@@ -206,8 +715,27 @@ var UpdateBuilder = class _UpdateBuilder {
206
715
  with(patch) {
207
716
  return new _UpdateBuilder({ ...this.node, ...patch }, this.source);
208
717
  }
209
- /** The columns to write. Partial — only the given columns change. */
718
+ /**
719
+ * The columns to write. Partial — only the given columns change.
720
+ *
721
+ * A value is bound as a parameter unless it is a {@link sql} expression, which
722
+ * is rendered inline instead — that is how a counter is written without a
723
+ * read-modify-write race.
724
+ *
725
+ * @param values The column → value map.
726
+ * @returns A builder carrying the assignments.
727
+ * @throws ValidationError When a value is not a column value the dialect can
728
+ * bind (a bare object, an array on a scalar column, a function).
729
+ *
730
+ * @example
731
+ * ```ts
732
+ * update(Outbound)
733
+ * .set({ attempts: sql.raw("attempts + 1"), updatedAt: sql.now() })
734
+ * .where({ id });
735
+ * ```
736
+ */
210
737
  set(values) {
738
+ assertWritableValues(this.source, values, "set");
211
739
  return this.with({ set: values });
212
740
  }
213
741
  /** Restrict the rows to update. Marks the builder safe to execute. */
@@ -233,7 +761,8 @@ function update(model) {
233
761
  set: {},
234
762
  where: void 0,
235
763
  guarded: false,
236
- returning: null
764
+ returning: null,
765
+ names: columnNamesOf(model) ?? void 0
237
766
  },
238
767
  model
239
768
  );
@@ -270,7 +799,8 @@ function del(model) {
270
799
  table: model.tablename,
271
800
  where: void 0,
272
801
  guarded: false,
273
- returning: null
802
+ returning: null,
803
+ names: columnNamesOf(model) ?? void 0
274
804
  },
275
805
  model
276
806
  );
@@ -365,173 +895,28 @@ function parseDatabaseUrl(url) {
365
895
  if (!dialect) {
366
896
  throw new InvalidDatabaseUrl(url, `unknown dialect ${JSON.stringify(base)}`);
367
897
  }
368
- const rest = url.slice(schemeEnd + 1);
369
- if (dialect === "sqlite") return parseSqlite(url, driver, rest);
370
- return parseNetworkUrl(url, driver, rest, dialect);
371
- }
372
- function detectDialect(url) {
373
- return parseDatabaseUrl(url).dialect;
374
- }
375
-
376
- // src/serialize.ts
377
- var ValidationError = class extends Error {
378
- constructor(table, issues) {
379
- super(`Validation failed for ${table}:
380
- - ${issues.join("\n - ")}`);
381
- this.table = table;
382
- this.issues = issues;
383
- this.name = "ValidationError";
384
- }
385
- table;
386
- issues;
387
- };
388
- function toBase64(bytes) {
389
- if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
390
- let binary = "";
391
- for (const byte of bytes) binary += String.fromCharCode(byte);
392
- return btoa(binary);
393
- }
394
- function fromBase64(value) {
395
- if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64"));
396
- const binary = atob(value);
397
- const bytes = new Uint8Array(binary.length);
398
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
399
- return bytes;
400
- }
401
- function encodeValue(column2, value) {
402
- if (value === null || value === void 0) return null;
403
- switch (column2.type.kind) {
404
- case "bigint":
405
- return typeof value === "bigint" ? value.toString() : value;
406
- case "date":
407
- case "datetime":
408
- case "timestamp":
409
- return value instanceof Date ? value.toISOString() : value;
410
- case "blob":
411
- return value instanceof Uint8Array ? toBase64(value) : value;
412
- default:
413
- return value;
414
- }
415
- }
416
- function decodeValue(column2, value) {
417
- if (value === null || value === void 0) return null;
418
- switch (column2.type.kind) {
419
- case "bigint":
420
- return typeof value === "bigint" ? value : BigInt(value);
421
- case "date":
422
- case "datetime":
423
- case "timestamp":
424
- return value instanceof Date ? value : new Date(value);
425
- case "blob":
426
- return value instanceof Uint8Array ? value : fromBase64(value);
427
- case "json":
428
- return typeof value === "string" ? JSON.parse(value) : value;
429
- case "numeric":
430
- return typeof value === "string" ? value : String(value);
431
- case "boolean":
432
- return typeof value === "boolean" ? value : value === 1 || value === "true";
433
- case "smallint":
434
- case "integer":
435
- case "real":
436
- case "double":
437
- return typeof value === "number" ? value : Number(value);
438
- default:
439
- return value;
440
- }
441
- }
442
- function toDict(model, row) {
443
- const columns = columnsOf(model);
444
- const out = {};
445
- for (const name of Object.keys(columns)) {
446
- out[name] = row[name] ?? null;
447
- }
448
- return out;
449
- }
450
- function toJSON(model, row) {
451
- const columns = columnsOf(model);
452
- const out = {};
453
- for (const [name, col] of Object.entries(columns)) {
454
- out[name] = encodeValue(col, row[name] ?? null);
455
- }
456
- return out;
457
- }
458
- function stringify(model, row) {
459
- return JSON.stringify(toJSON(model, row));
460
- }
461
- function fromDict(model, data) {
462
- const columns = columnsOf(model);
463
- const out = {};
464
- const issues = [];
465
- for (const [name, col] of Object.entries(columns)) {
466
- const present = name in data && data[name] !== void 0 && data[name] !== null;
467
- if (!present) {
468
- const required = col.flags.notNull && !col.flags.hasDefault;
469
- if (required) {
470
- issues.push(`missing required column "${name}"`);
471
- continue;
472
- }
473
- out[name] = null;
474
- continue;
475
- }
476
- try {
477
- out[name] = decodeValue(col, data[name]);
478
- } catch (error) {
479
- issues.push(`column "${name}": ${error.message}`);
480
- }
481
- }
482
- if (issues.length > 0) {
483
- throw new ValidationError(model.tablename, issues);
484
- }
485
- return out;
486
- }
487
- function parse(model, json) {
488
- return fromDict(model, JSON.parse(json));
489
- }
490
- function decoderForKind(kind) {
491
- switch (kind) {
492
- case "bigint":
493
- return (v) => v == null ? null : typeof v === "bigint" ? v : BigInt(v);
494
- case "date":
495
- case "datetime":
496
- case "timestamp":
497
- return (v) => v == null ? null : v instanceof Date ? v : new Date(v);
498
- case "blob":
499
- return (v) => v == null ? null : v instanceof Uint8Array ? v : fromBase64(v);
500
- case "json":
501
- return (v) => v == null ? null : typeof v === "string" ? JSON.parse(v) : v;
502
- case "numeric":
503
- return (v) => v == null ? null : typeof v === "string" ? v : String(v);
504
- case "boolean":
505
- return (v) => v == null ? null : typeof v === "boolean" ? v : v === 1 || v === "true";
506
- case "smallint":
507
- case "integer":
508
- case "real":
509
- case "double":
510
- return (v) => v == null ? null : typeof v === "number" ? v : Number(v);
511
- default:
512
- return null;
513
- }
514
- }
515
- var decoderCache = /* @__PURE__ */ new WeakMap();
516
- function decodersFor(model) {
517
- const cached = decoderCache.get(model);
518
- if (cached) return cached;
519
- const map = /* @__PURE__ */ new Map();
520
- for (const [name, col] of Object.entries(columnsOf(model))) {
521
- const decoder = decoderForKind(col.type.kind);
522
- if (decoder) map.set(name, decoder);
523
- }
524
- decoderCache.set(model, map);
525
- return map;
898
+ const rest = url.slice(schemeEnd + 1);
899
+ if (dialect === "sqlite") return parseSqlite(url, driver, rest);
900
+ return parseNetworkUrl(url, driver, rest, dialect);
526
901
  }
527
- function coerceRow(model, raw) {
528
- const decoders = decodersFor(model);
529
- const out = {};
530
- for (const name of Object.keys(raw)) {
531
- const decode2 = decoders.get(name);
532
- out[name] = decode2 ? decode2(raw[name]) : raw[name];
902
+ function detectDialect(url) {
903
+ return parseDatabaseUrl(url).dialect;
904
+ }
905
+
906
+ // src/expressions.ts
907
+ function renderPortableToken(token, dialect) {
908
+ switch (token) {
909
+ case "now":
910
+ return dialect === "postgresql" ? "now()" : "CURRENT_TIMESTAMP";
911
+ case "current_date":
912
+ return "CURRENT_DATE";
913
+ case "current_time":
914
+ return "CURRENT_TIME";
915
+ case "uuidv4":
916
+ if (dialect === "postgresql") return "gen_random_uuid()";
917
+ if (dialect === "mysql") return "(UUID())";
918
+ return "(lower(hex(randomblob(16))))";
533
919
  }
534
- return out;
535
920
  }
536
921
 
537
922
  // src/dialect.ts
@@ -554,6 +939,20 @@ var Params = class {
554
939
  return this.placeholder(this.values.length);
555
940
  }
556
941
  };
942
+ function insertHasExpression(node) {
943
+ for (const row of node.values) {
944
+ for (const value of Object.values(row)) {
945
+ if (isSqlExpression(value)) return true;
946
+ }
947
+ }
948
+ const update2 = node.onConflict?.update;
949
+ if (update2 && update2 !== "nothing") {
950
+ for (const value of Object.values(update2)) {
951
+ if (isSqlExpression(value)) return true;
952
+ }
953
+ }
954
+ return false;
955
+ }
557
956
  var BaseDialect = class _BaseDialect {
558
957
  /**
559
958
  * INSERT SQL templates keyed by structure (dialect|table|columns|rowCount|
@@ -563,6 +962,30 @@ var BaseDialect = class _BaseDialect {
563
962
  static insertTemplates = /* @__PURE__ */ new Map();
564
963
  /** Quoted-identifier cache (see {@link quoteId}). Shared across dialects. */
565
964
  static quotedIds = /* @__PURE__ */ new Map();
965
+ /**
966
+ * Validate a subquery operand before it is rendered, for dialects that restrict
967
+ * what an `IN (SELECT ...)` may contain. The default accepts everything.
968
+ *
969
+ * @param _node The subquery's AST.
970
+ * @throws Error When the dialect cannot execute this subquery.
971
+ */
972
+ checkSubquery(_node) {
973
+ }
974
+ /**
975
+ * The SQL operator for an array containment/overlap test.
976
+ *
977
+ * Only PostgreSQL has native arrays; the other dialects throw rather than
978
+ * emitting an operator that means something else there.
979
+ *
980
+ * @param op The array operator name.
981
+ * @returns The SQL operator text.
982
+ * @throws Error On a dialect without native array support.
983
+ */
984
+ arrayOperator(op) {
985
+ throw new Error(
986
+ `The "${op}" operator needs native array support, which ${this.name} does not have.`
987
+ );
988
+ }
566
989
  /**
567
990
  * Quote an identifier (column/table) for the active dialect.
568
991
  *
@@ -601,51 +1024,199 @@ var BaseDialect = class _BaseDialect {
601
1024
  }
602
1025
  return { sql: sql2, params: params.values };
603
1026
  }
604
- /** Render a qualified `alias.column` ref as `"alias"."column"`. */
605
- qualify(ref) {
1027
+ /**
1028
+ * Render a qualified `alias.column` ref as `"alias"."column"`, translating the
1029
+ * property name to the real column name for that alias's model.
1030
+ *
1031
+ * @param ref The `alias.property` reference (a bare name is left unqualified).
1032
+ * @param names The node's per-alias name maps, if any source renames columns.
1033
+ * @returns The quoted, qualified identifier.
1034
+ */
1035
+ qualify(ref, names) {
606
1036
  const dot = ref.indexOf(".");
607
1037
  if (dot === -1) return this.quoteId(ref);
608
- return `${this.quoteId(ref.slice(0, dot))}.${this.quoteId(ref.slice(dot + 1))}`;
1038
+ const alias = ref.slice(0, dot);
1039
+ const prop = ref.slice(dot + 1);
1040
+ return `${this.quoteId(alias)}.${this.columnId(prop, names?.[alias])}`;
1041
+ }
1042
+ /**
1043
+ * Quote a column identifier, translating the model property name to the real
1044
+ * database column name first.
1045
+ *
1046
+ * `names` is `undefined` for a model that renames nothing — the overwhelmingly
1047
+ * common case — so this stays a single lookup plus the memoized quote.
1048
+ *
1049
+ * @param prop The model property name as written in the builder.
1050
+ * @param names The node's property → column map, if any.
1051
+ * @returns The quoted database identifier.
1052
+ */
1053
+ columnId(prop, names) {
1054
+ return this.quoteId(names?.[prop] ?? prop);
1055
+ }
1056
+ /**
1057
+ * Render a {@link SqlExpression} inline, binding the parameters it carries.
1058
+ *
1059
+ * This is what keeps `set({ attempts: sql.raw("attempts + 1") })` an expression
1060
+ * instead of a bound object: the fragment goes into the statement text, and
1061
+ * only a `sql.expr` template's interpolations become parameters.
1062
+ *
1063
+ * @param expr The branded expression.
1064
+ * @param params The parameter collector for the statement being compiled.
1065
+ * @returns The SQL text of the expression.
1066
+ */
1067
+ renderExpression(expr, params) {
1068
+ const token = expr.expression;
1069
+ if (typeof token === "string") return renderPortableToken(token, this.name);
1070
+ if ("raw" in token) return token.raw;
1071
+ const parts = token.parts;
1072
+ let sql2 = parts[0] ?? "";
1073
+ for (let i = 1; i < parts.length; i++) {
1074
+ sql2 += `${params.bind(expr.params[i - 1])}${parts[i]}`;
1075
+ }
1076
+ return sql2;
1077
+ }
1078
+ /** Render one write value: a SQL expression inline, anything else as a parameter. */
1079
+ renderValue(value, params) {
1080
+ return isSqlExpression(value) ? this.renderExpression(value, params) : params.bind(value);
1081
+ }
1082
+ /**
1083
+ * Render a row-level locking clause (`FOR UPDATE ...`).
1084
+ *
1085
+ * Standard on PostgreSQL and MySQL 8.0+; SQLite overrides it to throw.
1086
+ *
1087
+ * @param lock The locking clause from the node.
1088
+ * @returns The SQL text, leading space included.
1089
+ */
1090
+ renderLock(lock) {
1091
+ const strength = lock.strength === "update" ? "FOR UPDATE" : "FOR SHARE";
1092
+ const of = lock.of.length > 0 ? ` OF ${lock.of.map((t) => this.quoteId(t)).join(", ")}` : "";
1093
+ const wait = lock.wait === "skipLocked" ? " SKIP LOCKED" : lock.wait === "noWait" ? " NOWAIT" : "";
1094
+ return ` ${strength}${of}${wait}`;
609
1095
  }
610
1096
  // ---- statements -------------------------------------------------------
1097
+ /**
1098
+ * Compile a SELECT.
1099
+ *
1100
+ * Two alias rules differ between clauses and are handled here: PostgreSQL does
1101
+ * NOT accept a `SELECT` alias in `HAVING`, so an aggregate key is re-emitted as
1102
+ * its expression (`COUNT(*) > $1`), a form every dialect accepts; `ORDER BY`,
1103
+ * by contrast, accepts the output alias everywhere, so it is emitted as
1104
+ * written.
1105
+ *
1106
+ * @param node The select AST.
1107
+ * @param params The parameter collector.
1108
+ * @returns The SQL text.
1109
+ */
611
1110
  compileSelect(node, params) {
1111
+ const names = node.names;
612
1112
  let cols;
613
1113
  if (node.aggregates.length > 0) {
614
- const groupSel = node.groupBy.map((c) => this.quoteId(c));
1114
+ const groupSel = node.groupBy.map((c) => this.columnId(c, names));
615
1115
  const aggSel = node.aggregates.map((a) => {
616
- const inner = a.column === "*" ? "*" : this.quoteId(a.column);
1116
+ const inner = a.column === "*" ? "*" : this.columnId(a.column, names);
617
1117
  return `${a.fn.toUpperCase()}(${inner}) AS ${this.quoteId(a.alias)}`;
618
1118
  });
619
1119
  cols = [...groupSel, ...aggSel].join(", ");
620
1120
  } else {
621
- cols = node.columns === "*" ? "*" : node.columns.map((c) => this.quoteId(c)).join(", ");
1121
+ cols = node.columns === "*" ? "*" : node.columns.map((c) => this.columnId(c, names)).join(", ");
622
1122
  }
623
1123
  let sql2 = `SELECT ${node.distinct ? "DISTINCT " : ""}${cols} FROM ${this.quoteId(node.table)}`;
624
- const where = this.compileCondition(node.where, params, (k) => this.quoteId(k));
1124
+ const where = this.compileCondition(
1125
+ node.where,
1126
+ params,
1127
+ (k) => this.columnId(k, names)
1128
+ );
625
1129
  if (where) sql2 += ` WHERE ${where}`;
626
1130
  if (node.groupBy.length > 0) {
627
- sql2 += ` GROUP BY ${node.groupBy.map((c) => this.quoteId(c)).join(", ")}`;
1131
+ sql2 += ` GROUP BY ${node.groupBy.map((c) => this.columnId(c, names)).join(", ")}`;
1132
+ }
1133
+ const aggByAlias = new Map(node.aggregates.map((a) => [a.alias, a]));
1134
+ if (node.having) {
1135
+ const having = this.compileCondition(node.having, params, (key) => {
1136
+ const agg = aggByAlias.get(key);
1137
+ if (!agg) return this.columnId(key, names);
1138
+ const inner = agg.column === "*" ? "*" : this.columnId(agg.column, names);
1139
+ return `${agg.fn.toUpperCase()}(${inner})`;
1140
+ });
1141
+ if (having) sql2 += ` HAVING ${having}`;
628
1142
  }
629
1143
  if (node.orderBy.length > 0) {
630
- const terms = node.orderBy.map(
631
- (t) => `${this.quoteId(t.column)} ${t.direction === "desc" ? "DESC" : "ASC"}`
632
- ).join(", ");
1144
+ const terms = node.orderBy.map((t) => {
1145
+ const id = aggByAlias.has(t.column) ? this.quoteId(t.column) : this.columnId(t.column, names);
1146
+ return `${id} ${t.direction === "desc" ? "DESC" : "ASC"}`;
1147
+ }).join(", ");
633
1148
  sql2 += ` ORDER BY ${terms}`;
634
1149
  }
635
1150
  if (node.limit !== void 0) sql2 += ` LIMIT ${params.bind(node.limit)}`;
636
1151
  if (node.offset !== void 0) sql2 += ` OFFSET ${params.bind(node.offset)}`;
1152
+ if (node.lock) {
1153
+ if (node.distinct || node.groupBy.length > 0 || node.aggregates.length > 0) {
1154
+ throw new Error(
1155
+ "FOR UPDATE / FOR SHARE cannot be combined with DISTINCT or an aggregate query \u2014 lock the underlying rows in a separate SELECT."
1156
+ );
1157
+ }
1158
+ sql2 += this.renderLock(node.lock);
1159
+ }
637
1160
  return sql2;
638
1161
  }
1162
+ /**
1163
+ * Compile an INSERT.
1164
+ *
1165
+ * Takes the cached fast path only when the statement text is a pure function of
1166
+ * its structure. A SQL expression among the values, or a conflict predicate,
1167
+ * makes the text depend on the values themselves — those compile uncached, in
1168
+ * SQL order, so placeholder positions stay correct.
1169
+ */
639
1170
  compileInsert(node, params) {
640
1171
  const columns = node.values.length > 0 ? Object.keys(node.values[0]) : [];
1172
+ const conflict = node.onConflict;
1173
+ const cacheable = conflict?.targetWhere === void 0 && conflict?.updateWhere === void 0 && !insertHasExpression(node);
1174
+ if (!cacheable) return this.compileInsertDirect(node, columns, params);
641
1175
  for (const row of node.values) {
642
1176
  for (const c of columns) params.bind(row[c] ?? null);
643
1177
  }
644
- const conflictCols = node.onConflict && node.onConflict.update !== "nothing" ? Object.keys(node.onConflict.update) : [];
1178
+ const conflictCols = conflict && conflict.update !== "nothing" ? Object.keys(conflict.update) : [];
645
1179
  for (const c of conflictCols) {
646
- params.bind((node.onConflict?.update)[c]);
1180
+ params.bind((conflict?.update)[c]);
1181
+ }
1182
+ return this.insertTemplate(node, columns, conflictCols, params);
1183
+ }
1184
+ /**
1185
+ * Compile an INSERT without the template cache, rendering clauses in statement
1186
+ * order so every parameter is bound at the position it appears.
1187
+ *
1188
+ * @param node The insert node.
1189
+ * @param columns The column keys shared by every row.
1190
+ * @param params The parameter collector.
1191
+ * @returns The SQL text.
1192
+ */
1193
+ compileInsertDirect(node, columns, params) {
1194
+ const names = node.names;
1195
+ const colSql = columns.map((c) => this.columnId(c, names)).join(", ");
1196
+ const rowsSql = node.values.map((row) => {
1197
+ const cells = columns.map(
1198
+ (c) => this.renderValue(row[c] ?? null, params)
1199
+ );
1200
+ return `(${cells.join(", ")})`;
1201
+ }).join(", ");
1202
+ let sql2 = `INSERT INTO ${this.quoteId(node.table)} (${colSql}) VALUES ${rowsSql}`;
1203
+ if (node.onConflict) {
1204
+ const update2 = node.onConflict.update;
1205
+ const conflictCols = update2 === "nothing" ? [] : Object.keys(update2);
1206
+ let cursor = 0;
1207
+ sql2 += this.renderConflict(
1208
+ node.onConflict,
1209
+ conflictCols,
1210
+ () => {
1211
+ const key = conflictCols[cursor++];
1212
+ return this.renderValue(update2[key], params);
1213
+ },
1214
+ names,
1215
+ params
1216
+ );
647
1217
  }
648
- return this.insertTemplate(node, columns, conflictCols);
1218
+ sql2 += this.compileReturning(node.returning, names);
1219
+ return sql2;
649
1220
  }
650
1221
  /**
651
1222
  * The INSERT SQL template for a given structure, cached across calls.
@@ -655,13 +1226,14 @@ var BaseDialect = class _BaseDialect {
655
1226
  * deterministic from the counts (a fresh statement always starts binding at 1).
656
1227
  * So a per-row insert loop compiles the string once and reuses it every row.
657
1228
  */
658
- insertTemplate(node, columns, conflictCols) {
1229
+ insertTemplate(node, columns, conflictCols, params) {
659
1230
  const returningKey = node.returning === null ? "" : node.returning === "*" ? "*" : node.returning.join(",");
660
1231
  const conflictKey = node.onConflict ? `${node.onConflict.target.join(",")}>${node.onConflict.update === "nothing" ? "nothing" : conflictCols.join(",")}` : "";
661
1232
  const key = `${this.name}|${node.table}|${columns.join(",")}|${node.values.length}|${returningKey}|${conflictKey}`;
662
1233
  const cached = _BaseDialect.insertTemplates.get(key);
663
1234
  if (cached !== void 0) return cached;
664
- const colSql = columns.map((c) => this.quoteId(c)).join(", ");
1235
+ const names = node.names;
1236
+ const colSql = columns.map((c) => this.columnId(c, names)).join(", ");
665
1237
  let position = 0;
666
1238
  const rowsSql = node.values.map(() => `(${columns.map(() => this.placeholder(++position)).join(", ")})`).join(", ");
667
1239
  let sql2 = `INSERT INTO ${this.quoteId(node.table)} (${colSql}) VALUES ${rowsSql}`;
@@ -669,57 +1241,91 @@ var BaseDialect = class _BaseDialect {
669
1241
  sql2 += this.renderConflict(
670
1242
  node.onConflict,
671
1243
  conflictCols,
672
- () => this.placeholder(++position)
1244
+ () => this.placeholder(++position),
1245
+ names,
1246
+ params
673
1247
  );
674
1248
  }
675
- sql2 += this.compileReturning(node.returning);
1249
+ sql2 += this.compileReturning(node.returning, names);
676
1250
  _BaseDialect.insertTemplates.set(key, sql2);
677
1251
  return sql2;
678
1252
  }
679
1253
  /**
680
1254
  * Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
681
- * `ON CONFLICT (...) DO NOTHING | DO UPDATE SET ...`; MySQL overrides this.
1255
+ * `ON CONFLICT (...) [WHERE predicate] DO NOTHING | DO UPDATE SET ... [WHERE ...]`;
1256
+ * MySQL overrides this.
1257
+ *
1258
+ * The index predicate is rendered before the `DO UPDATE` assignments because
1259
+ * that is where it sits in the statement, so its parameters bind first.
682
1260
  *
683
1261
  * @param onConflict The conflict clause from the node.
684
1262
  * @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
685
- * @param nextPlaceholder Yields the next positional placeholder (advances the count).
1263
+ * @param nextValue Yields the SQL for the next `DO UPDATE` assignment value.
1264
+ * @param names The node's property → column map, if any.
1265
+ * @param params The parameter collector, for the predicates.
1266
+ * @returns The SQL text, leading space included.
686
1267
  */
687
- renderConflict(onConflict, conflictCols, nextPlaceholder) {
688
- const target = onConflict.target.map((c) => this.quoteId(c)).join(", ");
689
- if (onConflict.update === "nothing") return ` ON CONFLICT (${target}) DO NOTHING`;
690
- const assignments = conflictCols.map((c) => `${this.quoteId(c)} = ${nextPlaceholder()}`).join(", ");
691
- return ` ON CONFLICT (${target}) DO UPDATE SET ${assignments}`;
1268
+ renderConflict(onConflict, conflictCols, nextValue, names, params) {
1269
+ const idFor = (key) => this.columnId(key, names);
1270
+ const target = onConflict.target.map(idFor).join(", ");
1271
+ const indexWhere = this.compileCondition(onConflict.targetWhere, params, idFor);
1272
+ const targetSql = indexWhere ? `(${target}) WHERE ${indexWhere}` : `(${target})`;
1273
+ if (onConflict.update === "nothing") return ` ON CONFLICT ${targetSql} DO NOTHING`;
1274
+ const assignments = conflictCols.map((c) => `${idFor(c)} = ${nextValue()}`).join(", ");
1275
+ let sql2 = ` ON CONFLICT ${targetSql} DO UPDATE SET ${assignments}`;
1276
+ const updateWhere = this.compileCondition(onConflict.updateWhere, params, idFor);
1277
+ if (updateWhere) sql2 += ` WHERE ${updateWhere}`;
1278
+ return sql2;
692
1279
  }
693
1280
  compileUpdate(node, params) {
694
- const sets = Object.entries(node.set).map(([col, value]) => `${this.quoteId(col)} = ${params.bind(value)}`).join(", ");
1281
+ const names = node.names;
1282
+ const sets = Object.entries(node.set).map(
1283
+ ([col2, value]) => `${this.columnId(col2, names)} = ${this.renderValue(value, params)}`
1284
+ ).join(", ");
695
1285
  let sql2 = `UPDATE ${this.quoteId(node.table)} SET ${sets}`;
696
- const where = this.compileCondition(node.where, params, (k) => this.quoteId(k));
1286
+ const where = this.compileCondition(
1287
+ node.where,
1288
+ params,
1289
+ (k) => this.columnId(k, names)
1290
+ );
697
1291
  if (where) sql2 += ` WHERE ${where}`;
698
- sql2 += this.compileReturning(node.returning);
1292
+ sql2 += this.compileReturning(node.returning, names);
699
1293
  return sql2;
700
1294
  }
701
1295
  compileDelete(node, params) {
1296
+ const names = node.names;
702
1297
  let sql2 = `DELETE FROM ${this.quoteId(node.table)}`;
703
- const where = this.compileCondition(node.where, params, (k) => this.quoteId(k));
1298
+ const where = this.compileCondition(
1299
+ node.where,
1300
+ params,
1301
+ (k) => this.columnId(k, names)
1302
+ );
704
1303
  if (where) sql2 += ` WHERE ${where}`;
705
- sql2 += this.compileReturning(node.returning);
1304
+ sql2 += this.compileReturning(node.returning, names);
706
1305
  return sql2;
707
1306
  }
708
1307
  compileJoin(node, params) {
1308
+ const names = node.names;
709
1309
  const cols = node.selections.map((s) => {
710
1310
  const ref = `${s.alias}.${s.column}`;
711
- return `${this.qualify(ref)} AS ${this.quoteId(ref)}`;
1311
+ return `${this.qualify(ref, names)} AS ${this.quoteId(ref)}`;
712
1312
  }).join(", ");
713
1313
  let sql2 = `SELECT ${cols} FROM ${this.quoteId(node.base.table)} AS ${this.quoteId(node.base.alias)}`;
714
1314
  for (const j of node.joins) {
715
1315
  const kw = j.kind === "left" ? "LEFT JOIN" : "INNER JOIN";
716
- const on = j.on.map(([l, r]) => `${this.qualify(l)} = ${this.qualify(r)}`).join(" AND ");
1316
+ const on = j.on.map(([l, r]) => `${this.qualify(l, names)} = ${this.qualify(r, names)}`).join(" AND ");
717
1317
  sql2 += ` ${kw} ${this.quoteId(j.table)} AS ${this.quoteId(j.alias)} ON ${on}`;
718
1318
  }
719
- const where = this.compileCondition(node.where, params, (k) => this.qualify(k));
1319
+ const where = this.compileCondition(
1320
+ node.where,
1321
+ params,
1322
+ (k) => this.qualify(k, names)
1323
+ );
720
1324
  if (where) sql2 += ` WHERE ${where}`;
721
1325
  if (node.orderBy.length > 0) {
722
- const terms = node.orderBy.map((t) => `${this.qualify(t.ref)} ${t.direction === "desc" ? "DESC" : "ASC"}`).join(", ");
1326
+ const terms = node.orderBy.map(
1327
+ (t) => `${this.qualify(t.ref, names)} ${t.direction === "desc" ? "DESC" : "ASC"}`
1328
+ ).join(", ");
723
1329
  sql2 += ` ORDER BY ${terms}`;
724
1330
  }
725
1331
  if (node.limit !== void 0) sql2 += ` LIMIT ${params.bind(node.limit)}`;
@@ -727,10 +1333,10 @@ var BaseDialect = class _BaseDialect {
727
1333
  return sql2;
728
1334
  }
729
1335
  // ---- clauses ----------------------------------------------------------
730
- compileReturning(returning) {
1336
+ compileReturning(returning, names) {
731
1337
  if (returning === null) return "";
732
1338
  if (returning === "*") return " RETURNING *";
733
- return ` RETURNING ${returning.map((c) => this.quoteId(c)).join(", ")}`;
1339
+ return ` RETURNING ${returning.map((c) => this.columnId(c, names)).join(", ")}`;
734
1340
  }
735
1341
  /**
736
1342
  * Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
@@ -767,6 +1373,83 @@ var BaseDialect = class _BaseDialect {
767
1373
  const inner = this.compileCondition(node.part, params, idFor);
768
1374
  return inner ? `NOT (${inner})` : "";
769
1375
  }
1376
+ case "compare": {
1377
+ const left = this.renderExpr(node.left, params, idFor);
1378
+ if (node.right.kind === "value") {
1379
+ return this.compileOperator(left, node.op, node.right.value, params);
1380
+ }
1381
+ return this.compileExprOperator(
1382
+ left,
1383
+ node.op,
1384
+ this.renderExpr(node.right, params, idFor)
1385
+ );
1386
+ }
1387
+ }
1388
+ }
1389
+ /**
1390
+ * Render one side of a comparison.
1391
+ *
1392
+ * A column reference goes through `idFor`, so an explicit `.name()` mapping and
1393
+ * join qualification apply here exactly as they do in the object form of
1394
+ * `where` — `col()` is not a way around them. Only a `value` node binds.
1395
+ *
1396
+ * @param node The expression AST.
1397
+ * @param params The parameter collector.
1398
+ * @param idFor The identifier resolver for the enclosing statement.
1399
+ * @returns The SQL text of the expression.
1400
+ */
1401
+ renderExpr(node, params, idFor) {
1402
+ switch (node.kind) {
1403
+ case "column":
1404
+ return idFor(node.name);
1405
+ case "value":
1406
+ return params.bind(node.value);
1407
+ case "fn": {
1408
+ const args = node.args.map((a) => this.renderExpr(a, params, idFor)).join(", ");
1409
+ return `${node.name}(${args})`;
1410
+ }
1411
+ }
1412
+ }
1413
+ /**
1414
+ * Compile a comparison whose right-hand side is another expression rather than
1415
+ * a bound value (`total > paid`, `lower(a) = lower(b)`).
1416
+ *
1417
+ * The list and null operators are excluded: `IN`, `BETWEEN` and `IS NULL` take
1418
+ * a value operand, and accepting an expression there would silently compile to
1419
+ * something else.
1420
+ *
1421
+ * @param left The rendered left-hand side.
1422
+ * @param op The operator name.
1423
+ * @param right The rendered right-hand side.
1424
+ * @returns The SQL text of the predicate.
1425
+ * @throws Error When the operator needs a value operand.
1426
+ */
1427
+ compileExprOperator(left, op, right) {
1428
+ switch (op) {
1429
+ case "eq":
1430
+ return `${left} = ${right}`;
1431
+ case "ne":
1432
+ return `${left} <> ${right}`;
1433
+ case "gt":
1434
+ return `${left} > ${right}`;
1435
+ case "gte":
1436
+ return `${left} >= ${right}`;
1437
+ case "lt":
1438
+ return `${left} < ${right}`;
1439
+ case "lte":
1440
+ return `${left} <= ${right}`;
1441
+ case "like":
1442
+ return `${left} LIKE ${right}`;
1443
+ case "ilike":
1444
+ return this.ilike(left, right);
1445
+ case "ieq":
1446
+ return `lower(${left}) = lower(${right})`;
1447
+ case "contains":
1448
+ case "containedBy":
1449
+ case "overlaps":
1450
+ return `${left} ${this.arrayOperator(op)} ${right}`;
1451
+ default:
1452
+ throw new Error(`The "${op}" operator takes a value operand, not an expression.`);
770
1453
  }
771
1454
  }
772
1455
  compileOperator(id, op, operand, params) {
@@ -787,6 +1470,14 @@ var BaseDialect = class _BaseDialect {
787
1470
  return `${id} LIKE ${params.bind(operand)}`;
788
1471
  case "ilike":
789
1472
  return this.ilike(id, params.bind(operand));
1473
+ case "ieq":
1474
+ return operand === null ? `${id} IS NULL` : `lower(${id}) = lower(${params.bind(operand)})`;
1475
+ case "contains":
1476
+ return `${id} ${this.arrayOperator("contains")} ${params.bind(operand)}`;
1477
+ case "containedBy":
1478
+ return `${id} ${this.arrayOperator("containedBy")} ${params.bind(operand)}`;
1479
+ case "overlaps":
1480
+ return `${id} ${this.arrayOperator("overlaps")} ${params.bind(operand)}`;
790
1481
  case "in":
791
1482
  return this.compileIn(id, operand, params, false);
792
1483
  case "notIn":
@@ -801,12 +1492,33 @@ var BaseDialect = class _BaseDialect {
801
1492
  throw new Error(`Unknown operator ${JSON.stringify(op)}`);
802
1493
  }
803
1494
  }
804
- compileIn(id, values, params, negate) {
1495
+ /**
1496
+ * Compile `IN` / `NOT IN`, whose operand is either a value list or a
1497
+ * single-column subquery.
1498
+ *
1499
+ * The subquery is rendered at the position it appears in the outer statement
1500
+ * and shares the same parameter collector, so its own placeholders land in the
1501
+ * right order — and it keeps its own `names` map, since the inner model may use
1502
+ * a different naming convention than the outer one.
1503
+ *
1504
+ * @param id The quoted column identifier being tested.
1505
+ * @param operand A list of values, or a {@link Subquery}.
1506
+ * @param params The parameter collector for the statement being compiled.
1507
+ * @param negate True for `NOT IN`.
1508
+ * @returns The SQL text of the predicate.
1509
+ */
1510
+ compileIn(id, operand, params, negate) {
1511
+ const keyword = negate ? "NOT IN" : "IN";
1512
+ if (isSubquery(operand)) {
1513
+ this.checkSubquery(operand.node);
1514
+ return `${id} ${keyword} (${this.compileSelect(operand.node, params)})`;
1515
+ }
1516
+ const values = operand;
805
1517
  if (values.length === 0) {
806
1518
  return negate ? "1 = 1" : "1 = 0";
807
1519
  }
808
1520
  const list = values.map((v) => params.bind(v)).join(", ");
809
- return `${id} ${negate ? "NOT IN" : "IN"} (${list})`;
1521
+ return `${id} ${keyword} (${list})`;
810
1522
  }
811
1523
  };
812
1524
  var SqliteDialect = class extends BaseDialect {
@@ -817,6 +1529,16 @@ var SqliteDialect = class extends BaseDialect {
817
1529
  ilike(column2, param) {
818
1530
  return `${column2} LIKE ${param}`;
819
1531
  }
1532
+ /**
1533
+ * SQLite has no row-level locking, so a lock request is an error rather than a
1534
+ * silently unlocked `SELECT` — a lock that does not exist only shows up as
1535
+ * duplicated work under production concurrency.
1536
+ */
1537
+ renderLock() {
1538
+ throw new Error(
1539
+ "SQLite has no row-level locking \u2014 FOR UPDATE / FOR SHARE is unsupported. Serialize the claim inside a transaction instead."
1540
+ );
1541
+ }
820
1542
  };
821
1543
  var PostgresDialect = class extends BaseDialect {
822
1544
  name = "postgresql";
@@ -826,6 +1548,11 @@ var PostgresDialect = class extends BaseDialect {
826
1548
  ilike(column2, param) {
827
1549
  return `${column2} ILIKE ${param}`;
828
1550
  }
1551
+ arrayOperator(op) {
1552
+ if (op === "contains") return "@>";
1553
+ if (op === "containedBy") return "<@";
1554
+ return "&&";
1555
+ }
829
1556
  };
830
1557
  var mysqlQuotedIds = /* @__PURE__ */ new Map();
831
1558
  var MysqlDialect = class extends BaseDialect {
@@ -843,18 +1570,45 @@ var MysqlDialect = class extends BaseDialect {
843
1570
  mysqlQuotedIds.set(name, quoted);
844
1571
  return quoted;
845
1572
  }
846
- renderConflict(onConflict, conflictCols, nextPlaceholder) {
1573
+ /**
1574
+ * MySQL rejects `LIMIT` inside an `IN` subquery with
1575
+ * `ER_NOT_SUPPORTED_YET: This version of MySQL doesn't yet support
1576
+ * 'LIMIT & IN/ALL/ANY/SOME subquery'`. Failing at compile time names the fix
1577
+ * instead of surfacing that error from the driver at runtime.
1578
+ */
1579
+ checkSubquery(node) {
1580
+ if (node.limit !== void 0 || node.offset !== void 0) {
1581
+ throw new Error(
1582
+ "MySQL does not support LIMIT/OFFSET inside an IN subquery. Select the ids first and pass them as a list, or wrap the subquery in a derived table."
1583
+ );
1584
+ }
1585
+ }
1586
+ renderConflict(onConflict, conflictCols, nextValue, names) {
1587
+ if (onConflict.targetWhere || onConflict.updateWhere) {
1588
+ throw new Error(
1589
+ "MySQL's ON DUPLICATE KEY UPDATE has no conflict-target predicate \u2014 a partial unique index is PostgreSQL/SQLite only."
1590
+ );
1591
+ }
847
1592
  if (onConflict.update === "nothing") {
848
- const col = this.quoteId(onConflict.target[0] ?? "id");
849
- return ` ON DUPLICATE KEY UPDATE ${col} = ${col}`;
1593
+ const col2 = this.columnId(onConflict.target[0] ?? "id", names);
1594
+ return ` ON DUPLICATE KEY UPDATE ${col2} = ${col2}`;
850
1595
  }
851
- const assignments = conflictCols.map((c) => `${this.quoteId(c)} = ${nextPlaceholder()}`).join(", ");
1596
+ const assignments = conflictCols.map((c) => `${this.columnId(c, names)} = ${nextValue()}`).join(", ");
852
1597
  return ` ON DUPLICATE KEY UPDATE ${assignments}`;
853
1598
  }
1599
+ /**
1600
+ * MySQL has no `RETURNING`, so it cannot be compiled into a statement.
1601
+ *
1602
+ * `session.execute()` still honors `.returning()` on a **single-row INSERT** by
1603
+ * running the insert and reading the row back by key on the same connection —
1604
+ * that is execution, not compilation, so it never reaches here. Compiling a
1605
+ * node with `returning` directly is an error, rather than SQL that silently
1606
+ * returns nothing.
1607
+ */
854
1608
  compileReturning(returning) {
855
1609
  if (returning === null) return "";
856
1610
  throw new Error(
857
- "RETURNING is not supported on MySQL \u2014 insert, then SELECT by key (e.g. LAST_INSERT_ID())."
1611
+ "RETURNING cannot be compiled for MySQL. session.execute() reads a single-row INSERT back by key (LAST_INSERT_ID()); UPDATE/DELETE have no equivalent \u2014 run a SELECT yourself."
858
1612
  );
859
1613
  }
860
1614
  };
@@ -873,6 +1627,11 @@ function getDialect(name) {
873
1627
  function selectionsFor(alias, model) {
874
1628
  return Object.keys(columnsOf(model)).map((column2) => ({ alias, column: column2 }));
875
1629
  }
1630
+ function withAliasNames(current, alias, model) {
1631
+ const names = columnNamesOf(model);
1632
+ if (!names) return current;
1633
+ return { ...current ?? {}, [alias]: names };
1634
+ }
876
1635
  var JoinBuilder = class _JoinBuilder {
877
1636
  constructor(node, sources) {
878
1637
  this.node = node;
@@ -885,7 +1644,8 @@ var JoinBuilder = class _JoinBuilder {
885
1644
  {
886
1645
  ...this.node,
887
1646
  joins: [...this.node.joins, clause],
888
- selections: [...this.node.selections, ...selectionsFor(clause.alias, model)]
1647
+ selections: [...this.node.selections, ...selectionsFor(clause.alias, model)],
1648
+ names: withAliasNames(this.node.names, clause.alias, model)
889
1649
  },
890
1650
  { ...this.sources, [clause.alias]: model }
891
1651
  );
@@ -946,7 +1706,8 @@ function join(model, alias) {
946
1706
  where: void 0,
947
1707
  orderBy: [],
948
1708
  limit: void 0,
949
- offset: void 0
1709
+ offset: void 0,
1710
+ names: withAliasNames(void 0, alias, model)
950
1711
  },
951
1712
  { [alias]: model }
952
1713
  );
@@ -960,8 +1721,8 @@ var RecordNotFound = class extends Error {
960
1721
  }
961
1722
  };
962
1723
  function primaryKeyOf(model) {
963
- for (const [name, col] of Object.entries(columnsOf(model))) {
964
- if (col.flags.primaryKey) return name;
1724
+ for (const [name, col2] of Object.entries(columnsOf(model))) {
1725
+ if (col2.flags.primaryKey) return name;
965
1726
  }
966
1727
  throw new Error(`${model.tablename} has no primary key`);
967
1728
  }
@@ -1049,8 +1810,8 @@ var BaseRepository = class {
1049
1810
 
1050
1811
  // src/active-record.ts
1051
1812
  function primaryKeyOf2(model) {
1052
- for (const [name, col] of Object.entries(columnsOf(model))) {
1053
- if (col.flags.primaryKey) return name;
1813
+ for (const [name, col2] of Object.entries(columnsOf(model))) {
1814
+ if (col2.flags.primaryKey) return name;
1054
1815
  }
1055
1816
  throw new Error(`${model.tablename} has no primary key`);
1056
1817
  }
@@ -1259,11 +2020,12 @@ function splitJoinRow(node, sources, raw) {
1259
2020
  const out = {};
1260
2021
  for (const [alias, model] of Object.entries(sources)) {
1261
2022
  const sub = {};
2023
+ const names = columnNamesOf(model);
1262
2024
  let allNull = true;
1263
2025
  for (const colName of Object.keys(columnsOf(model))) {
1264
2026
  const value = raw[`${alias}.${colName}`];
1265
2027
  if (value !== null && value !== void 0) allNull = false;
1266
- sub[colName] = value;
2028
+ sub[names?.[colName] ?? colName] = value;
1267
2029
  }
1268
2030
  out[alias] = leftAliases.has(alias) && allNull ? null : coerceRow(model, sub);
1269
2031
  }
@@ -1385,6 +2147,25 @@ var AsyncResult = class {
1385
2147
  }
1386
2148
  };
1387
2149
  var savepointCounter = 0;
2150
+ function needsInsertReadBack(dialect, node) {
2151
+ return dialect.name === "mysql" && node.kind === "insert" && node.returning !== null;
2152
+ }
2153
+ function singlePrimaryKey(model) {
2154
+ const keys = Object.entries(columnsOf(model)).filter(([, column2]) => column2.flags.primaryKey).map(([name]) => name);
2155
+ if (keys.length !== 1) {
2156
+ throw new Error(
2157
+ `${model.tablename} needs exactly one primary key to read an insert back on a dialect without RETURNING; found ${keys.length}.`
2158
+ );
2159
+ }
2160
+ return keys[0];
2161
+ }
2162
+ function assertRawParams(params) {
2163
+ if (!Array.isArray(params)) {
2164
+ throw new TypeError(
2165
+ "session.raw(sql, params) takes an array of bound parameters \u2014 never interpolate values into the SQL string."
2166
+ );
2167
+ }
2168
+ }
1388
2169
  var SyncSession = class {
1389
2170
  constructor(driver, dialect, logger) {
1390
2171
  this.driver = driver;
@@ -1403,6 +2184,44 @@ var SyncSession = class {
1403
2184
  throw new QueryExecutionError(error, sql2, params);
1404
2185
  }
1405
2186
  }
2187
+ /**
2188
+ * Run a raw, parameterized SQL statement (synchronous) — the runtime counterpart of the
2189
+ * migrations' `Op.execute`.
2190
+ *
2191
+ * A query builder never covers all of SQL, and without an escape hatch a single
2192
+ * unsupported query forces a whole second database stack alongside this one. Use
2193
+ * it for what the builder cannot yet express, and keep everything else typed.
2194
+ *
2195
+ * The statement goes through the same path as a compiled one: it is logged via
2196
+ * `onQuery`, wrapped in {@link QueryExecutionError} on failure, and runs on the
2197
+ * reserved connection inside `transaction()`.
2198
+ *
2199
+ * @param sql The statement text. Placeholders only (`$1` / `?` per dialect) —
2200
+ * never interpolate a value into this string.
2201
+ * @param params The bound parameters, in placeholder order.
2202
+ * @param options Pass `as` to coerce the returned rows with a model's column
2203
+ * types (and its column-name mapping).
2204
+ * @returns The result view over the returned rows.
2205
+ * @throws Error When `params` is not an array — the guard against calling this
2206
+ * with an interpolated string and no parameters by mistake.
2207
+ *
2208
+ * @example
2209
+ * ```ts
2210
+ * const claimed = await session.raw<OutboundRow>(
2211
+ * `UPDATE outbound_messages SET status = 'sending'
2212
+ * WHERE id = ANY($1) RETURNING *`,
2213
+ * [ids],
2214
+ * { as: Outbound },
2215
+ * ).all();
2216
+ * ```
2217
+ */
2218
+ raw(sql2, params = [], options) {
2219
+ assertRawParams(params);
2220
+ const result = this.exec(sql2, params);
2221
+ const model = options?.as;
2222
+ const rows = model ? result.rows.map((row) => coerceRow(model, row)) : result.rows;
2223
+ return new SyncResult(rows, result.changes);
2224
+ }
1406
2225
  /** Compile, run, and coerce a builder into a result. */
1407
2226
  execute(builder) {
1408
2227
  const node = builder.node;
@@ -1412,10 +2231,10 @@ var SyncSession = class {
1412
2231
  return new SyncResult(rows, result.changes);
1413
2232
  }
1414
2233
  /** Run `fn` inside a transaction: commit on success, rollback on throw. */
1415
- transaction(fn) {
2234
+ transaction(fn2) {
1416
2235
  this.exec("BEGIN", []);
1417
2236
  try {
1418
- const out = fn(this);
2237
+ const out = fn2(this);
1419
2238
  this.exec("COMMIT", []);
1420
2239
  return out;
1421
2240
  } catch (error) {
@@ -1424,12 +2243,12 @@ var SyncSession = class {
1424
2243
  }
1425
2244
  }
1426
2245
  /** Run `fn` inside a SAVEPOINT (nested transaction). */
1427
- beginNested(fn) {
2246
+ beginNested(fn2) {
1428
2247
  savepointCounter += 1;
1429
2248
  const name = `qsp_${savepointCounter}`;
1430
2249
  this.exec(`SAVEPOINT ${name}`, []);
1431
2250
  try {
1432
- const out = fn(this);
2251
+ const out = fn2(this);
1433
2252
  this.exec(`RELEASE ${name}`, []);
1434
2253
  return out;
1435
2254
  } catch (error) {
@@ -1485,8 +2304,53 @@ var AsyncSession = class _AsyncSession {
1485
2304
  throw new QueryExecutionError(error, sql2, params);
1486
2305
  }
1487
2306
  }
2307
+ /**
2308
+ * Run a raw, parameterized SQL statement — the runtime counterpart of the
2309
+ * migrations' `Op.execute`.
2310
+ *
2311
+ * A query builder never covers all of SQL, and without an escape hatch a single
2312
+ * unsupported query forces a whole second database stack alongside this one. Use
2313
+ * it for what the builder cannot yet express, and keep everything else typed.
2314
+ *
2315
+ * The statement goes through the same path as a compiled one: it is logged via
2316
+ * `onQuery`, wrapped in {@link QueryExecutionError} on failure, and runs on the
2317
+ * reserved connection inside `transaction()`.
2318
+ *
2319
+ * @param sql The statement text. Placeholders only (`$1` / `?` per dialect) —
2320
+ * never interpolate a value into this string.
2321
+ * @param params The bound parameters, in placeholder order.
2322
+ * @param options Pass `as` to coerce the returned rows with a model's column
2323
+ * types (and its column-name mapping).
2324
+ * @returns The result view over the returned rows.
2325
+ * @throws Error When `params` is not an array — the guard against calling this
2326
+ * with an interpolated string and no parameters by mistake.
2327
+ *
2328
+ * @example
2329
+ * ```ts
2330
+ * const claimed = await session.raw<OutboundRow>(
2331
+ * `UPDATE outbound_messages SET status = 'sending'
2332
+ * WHERE id = ANY($1) RETURNING *`,
2333
+ * [ids],
2334
+ * { as: Outbound },
2335
+ * ).all();
2336
+ * ```
2337
+ */
2338
+ raw(sql2, params = [], options) {
2339
+ assertRawParams(params);
2340
+ const model = options?.as;
2341
+ const inner = this.exec(sql2, params).then((result) => {
2342
+ const rows = model ? result.rows.map((row) => coerceRow(model, row)) : result.rows;
2343
+ return new SyncResult(rows, result.changes);
2344
+ });
2345
+ return new AsyncResult(inner);
2346
+ }
1488
2347
  execute(builder) {
1489
2348
  const node = builder.node;
2349
+ if (needsInsertReadBack(this.dialect, node)) {
2350
+ return new AsyncResult(
2351
+ this.insertAndReadBack(builder, node)
2352
+ );
2353
+ }
1490
2354
  const { sql: sql2, params } = this.dialect.compile(node);
1491
2355
  const inner = this.exec(sql2, params).then((result) => {
1492
2356
  const rows = mapRows(builder, result.rows);
@@ -1494,6 +2358,53 @@ var AsyncSession = class _AsyncSession {
1494
2358
  });
1495
2359
  return new AsyncResult(inner);
1496
2360
  }
2361
+ /**
2362
+ * Honor `.returning()` on a dialect without `RETURNING`, by inserting and then
2363
+ * reading the row back by key.
2364
+ *
2365
+ * Both statements must run on **one** connection, because `LAST_INSERT_ID()` is
2366
+ * per-connection: outside a transaction the pooled driver is reserved for the
2367
+ * pair; inside one, the session already holds a pinned connection (a reserved
2368
+ * driver exposes no `reserve`), so it runs there directly.
2369
+ *
2370
+ * @param builder The insert builder, for its source model.
2371
+ * @param node The insert AST, whose `returning` drives the read-back.
2372
+ * @returns The result view over the read-back row.
2373
+ * @throws Error When the insert writes more than one row — `LAST_INSERT_ID()`
2374
+ * identifies only the first, and the rest are consecutive only under some
2375
+ * auto-increment lock modes.
2376
+ */
2377
+ async insertAndReadBack(builder, node) {
2378
+ if (node.values.length !== 1) {
2379
+ throw new Error(
2380
+ `${this.dialect.name} has no RETURNING, and reading back a multi-row insert is not reliable \u2014 insert one row at a time, or drop .returning().`
2381
+ );
2382
+ }
2383
+ const model = builder.source;
2384
+ const pk = singlePrimaryKey(model);
2385
+ const supplied = node.values[0][pk];
2386
+ const readBack = select(model).where(
2387
+ supplied === void 0 || supplied === null ? col(pk).eq(fn.call("LAST_INSERT_ID")) : { [pk]: supplied }
2388
+ );
2389
+ const insertSql = this.dialect.compile({ ...node, returning: null });
2390
+ const selectSql = this.dialect.compile(
2391
+ node.returning === "*" || node.returning === null ? readBack.node : { ...readBack.node, columns: node.returning }
2392
+ );
2393
+ const run = async (driver) => {
2394
+ const scoped = new _AsyncSession(driver, this.dialect, this.logger);
2395
+ const written = await scoped.exec(insertSql.sql, insertSql.params);
2396
+ const read = await scoped.exec(selectSql.sql, selectSql.params);
2397
+ const rows = read.rows.map((row) => coerceRow(model, row));
2398
+ return new SyncResult(rows, written.changes);
2399
+ };
2400
+ if (!this.driver.reserve) return run(this.driver);
2401
+ const reserved = await this.driver.reserve();
2402
+ try {
2403
+ return await run(reserved);
2404
+ } finally {
2405
+ await reserved.release();
2406
+ }
2407
+ }
1497
2408
  /** Lazily iterate result rows. Uses driver streaming when available. */
1498
2409
  async *stream(builder) {
1499
2410
  const node = builder.node;
@@ -1514,13 +2425,13 @@ var AsyncSession = class _AsyncSession {
1514
2425
  yield coerceOne(builder, raw);
1515
2426
  }
1516
2427
  }
1517
- async transaction(fn) {
2428
+ async transaction(fn2) {
1518
2429
  if (this.driver.reserve) {
1519
2430
  const reserved = await this.driver.reserve();
1520
2431
  const scoped = new _AsyncSession(reserved, this.dialect, this.logger);
1521
2432
  try {
1522
2433
  await scoped.exec("BEGIN", []);
1523
- const out = await fn(scoped);
2434
+ const out = await fn2(scoped);
1524
2435
  await scoped.exec("COMMIT", []);
1525
2436
  return out;
1526
2437
  } catch (error) {
@@ -1532,7 +2443,7 @@ var AsyncSession = class _AsyncSession {
1532
2443
  }
1533
2444
  await this.exec("BEGIN", []);
1534
2445
  try {
1535
- const out = await fn(this);
2446
+ const out = await fn2(this);
1536
2447
  await this.exec("COMMIT", []);
1537
2448
  return out;
1538
2449
  } catch (error) {
@@ -1559,8 +2470,8 @@ var SyncEngine = class {
1559
2470
  session() {
1560
2471
  return new SyncSession(this.driver, getDialect("sqlite"), this.logger);
1561
2472
  }
1562
- transaction(fn) {
1563
- return this.session().transaction(fn);
2473
+ transaction(fn2) {
2474
+ return this.session().transaction(fn2);
1564
2475
  }
1565
2476
  close() {
1566
2477
  this.driver.close();
@@ -1582,8 +2493,8 @@ var AsyncEngine = class {
1582
2493
  session() {
1583
2494
  return new AsyncSession(this.driver, getDialect(this.dialect), this.logger);
1584
2495
  }
1585
- transaction(fn) {
1586
- return this.session().transaction(fn);
2496
+ transaction(fn2) {
2497
+ return this.session().transaction(fn2);
1587
2498
  }
1588
2499
  async close() {
1589
2500
  await this.driver.close();
@@ -1593,6 +2504,16 @@ var AsyncEngine = class {
1593
2504
  await this.close();
1594
2505
  }
1595
2506
  };
2507
+ function toAsyncDriver(driver) {
2508
+ return {
2509
+ async execute(sql2, params) {
2510
+ return await driver.execute(sql2, params);
2511
+ },
2512
+ async close() {
2513
+ await driver.close();
2514
+ }
2515
+ };
2516
+ }
1596
2517
  function asAsync(driver) {
1597
2518
  const syncIterate = driver.iterate?.bind(driver);
1598
2519
  return {
@@ -1754,24 +2675,58 @@ var DEFAULT_FLAGS = {
1754
2675
  hasDefault: false,
1755
2676
  unique: false
1756
2677
  };
2678
+ var EXPRESSION = /* @__PURE__ */ Symbol.for("tempest-db-js.expression");
2679
+ function expression(token, params = []) {
2680
+ return { [EXPRESSION]: true, kind: "expression", expression: token, params };
2681
+ }
2682
+ function isSqlExpression(value) {
2683
+ return typeof value === "object" && value !== null && value[EXPRESSION] === true;
2684
+ }
1757
2685
  var sql = {
1758
2686
  /** Current timestamp at insert (`CURRENT_TIMESTAMP` / `now()`). */
1759
- now: () => ({ kind: "expression", expression: "now" }),
2687
+ now: () => expression("now"),
1760
2688
  /** Current date. */
1761
- currentDate: () => ({ kind: "expression", expression: "current_date" }),
2689
+ currentDate: () => expression("current_date"),
1762
2690
  /** Current time. */
1763
- currentTime: () => ({ kind: "expression", expression: "current_time" }),
2691
+ currentTime: () => expression("current_time"),
1764
2692
  /** A freshly generated UUID v4 (`gen_random_uuid()` / portable fallback). */
1765
- uuidv4: () => ({ kind: "expression", expression: "uuidv4" }),
1766
- /** Escape hatch: a verbatim SQL expression rendered as-is. */
1767
- raw: (expression) => ({
1768
- kind: "expression",
1769
- expression: { raw: expression }
1770
- })
2693
+ uuidv4: () => expression("uuidv4"),
2694
+ /**
2695
+ * Escape hatch: a verbatim SQL expression rendered as-is, with no parameters.
2696
+ *
2697
+ * The fragment is interpolated into the statement untouched, so it must never
2698
+ * carry user input — use {@link sql.expr} when a value has to be bound.
2699
+ *
2700
+ * @param fragment The SQL text (e.g. `"attempts + 1"`).
2701
+ * @returns The expression, usable as a default and as a write value.
2702
+ */
2703
+ raw: (fragment) => expression({ raw: fragment }),
2704
+ /**
2705
+ * A parameterized SQL expression, written as a tagged template. Static text is
2706
+ * SQL; every `${...}` interpolation becomes a bound parameter, so the fragment
2707
+ * is injection-safe by construction.
2708
+ *
2709
+ * Cannot be used as a column default — a `DEFAULT` clause has nowhere to bind
2710
+ * parameters; use {@link sql.raw} there.
2711
+ *
2712
+ * @param parts The static SQL segments supplied by the template tag.
2713
+ * @param values The interpolated values, bound in order.
2714
+ * @returns The expression, usable as a write value.
2715
+ *
2716
+ * @example
2717
+ * ```ts
2718
+ * update(Account).set({ balance: sql.expr`balance - ${amount}` }).where({ id });
2719
+ * // UPDATE "accounts" SET "balance" = balance - $1 WHERE "id" = $2
2720
+ * ```
2721
+ */
2722
+ expr: (parts, ...values) => expression({ parts: Array.from(parts) }, values)
1771
2723
  };
1772
2724
  function isDefaultValue(value) {
1773
2725
  return typeof value === "object" && value !== null && "kind" in value && (value.kind === "literal" || value.kind === "expression");
1774
2726
  }
2727
+ function bindsParameters(value) {
2728
+ return value.kind === "expression" && typeof value.expression === "object" && "parts" in value.expression;
2729
+ }
1775
2730
  function parseReference(ref, options) {
1776
2731
  const dot = ref.lastIndexOf(".");
1777
2732
  if (dot <= 0 || dot === ref.length - 1) {
@@ -1785,48 +2740,71 @@ function parseReference(ref, options) {
1785
2740
  };
1786
2741
  }
1787
2742
  var Column = class _Column {
1788
- constructor(type, flags, defaultValue = null, onUpdateValue = null, reference = null) {
2743
+ constructor(type, flags, defaultValue = null, onUpdateValue = null, reference = null, dbName = null) {
1789
2744
  this.type = type;
1790
2745
  this.flags = flags;
1791
2746
  this.defaultValue = defaultValue;
1792
2747
  this.onUpdateValue = onUpdateValue;
1793
2748
  this.reference = reference;
2749
+ this.dbName = dbName;
1794
2750
  }
1795
2751
  type;
1796
2752
  flags;
1797
2753
  defaultValue;
1798
2754
  onUpdateValue;
1799
2755
  reference;
1800
- primaryKey() {
2756
+ dbName;
2757
+ /** Clone this column with one facet replaced, carrying every other over. */
2758
+ derive(patch) {
1801
2759
  return new _Column(
1802
2760
  this.type,
1803
- { ...this.flags, primaryKey: true, hasDefault: true },
1804
- this.defaultValue,
1805
- this.onUpdateValue,
1806
- this.reference
2761
+ patch.flags ?? this.flags,
2762
+ patch.defaultValue !== void 0 ? patch.defaultValue : this.defaultValue,
2763
+ patch.onUpdateValue !== void 0 ? patch.onUpdateValue : this.onUpdateValue,
2764
+ patch.reference !== void 0 ? patch.reference : this.reference,
2765
+ patch.dbName !== void 0 ? patch.dbName : this.dbName
1807
2766
  );
1808
2767
  }
2768
+ primaryKey() {
2769
+ return this.derive({ flags: { ...this.flags, primaryKey: true, hasDefault: true } });
2770
+ }
1809
2771
  notNull() {
1810
- return new _Column(
1811
- this.type,
1812
- { ...this.flags, notNull: true },
1813
- this.defaultValue,
1814
- this.onUpdateValue,
1815
- this.reference
1816
- );
2772
+ return this.derive({ flags: { ...this.flags, notNull: true } });
1817
2773
  }
1818
2774
  /**
1819
2775
  * Add a `UNIQUE` constraint to the column (mirrors SQLAlchemy's
1820
2776
  * `mapped_column(unique=True)`). DDL-only — does not change the inferred type.
1821
2777
  */
1822
2778
  unique() {
1823
- return new _Column(
1824
- this.type,
1825
- { ...this.flags, unique: true },
1826
- this.defaultValue,
1827
- this.onUpdateValue,
1828
- this.reference
1829
- );
2779
+ return this.derive({ flags: { ...this.flags, unique: true } });
2780
+ }
2781
+ /**
2782
+ * Map this property to a differently-named database column, à la SQLAlchemy's
2783
+ * `mapped_column("consumer_name")` (Django's `db_column`, Prisma's `@map`).
2784
+ *
2785
+ * The override applies everywhere the name reaches SQL — select, insert,
2786
+ * update, delete, where, order by, group by, returning, conflict targets, the
2787
+ * migration IR and the drift check — while the TypeScript row keeps the
2788
+ * property name. Use it to keep a `snake_case` schema behind a `camelCase`
2789
+ * model; {@link Model.naming} does the same for a whole table at once.
2790
+ *
2791
+ * @param dbName The real column name in the database.
2792
+ * @returns A new column bound to that name.
2793
+ * @throws Error When `dbName` is empty.
2794
+ *
2795
+ * @example
2796
+ * ```ts
2797
+ * class ApiKey extends Model {
2798
+ * static tablename = "api_keys";
2799
+ * consumerName = column.text().name("consumer_name").notNull();
2800
+ * }
2801
+ * ```
2802
+ */
2803
+ name(dbName) {
2804
+ if (dbName.length === 0) {
2805
+ throw new Error("column.name() requires a non-empty database column name.");
2806
+ }
2807
+ return this.derive({ dbName });
1830
2808
  }
1831
2809
  /**
1832
2810
  * Declare a foreign-key reference to another table's column, à la SQLAlchemy's
@@ -1839,35 +2817,45 @@ var Column = class _Column {
1839
2817
  * @throws Error When `ref` is not a valid `"table.column"` string.
1840
2818
  */
1841
2819
  references(ref, options) {
1842
- return new _Column(
1843
- this.type,
1844
- this.flags,
1845
- this.defaultValue,
1846
- this.onUpdateValue,
1847
- parseReference(ref, options)
1848
- );
2820
+ return this.derive({ reference: parseReference(ref, options) });
1849
2821
  }
1850
2822
  /**
1851
2823
  * Set the insert-time default: a constant value of type `T`, or a portable
1852
2824
  * server-side expression from {@link sql} (e.g. `sql.now()`, `sql.uuidv4()`).
2825
+ *
2826
+ * @param value The literal default, or a {@link sql} expression.
2827
+ * @returns A new column carrying the default.
2828
+ * @throws Error When given a `sql.expr` fragment — a `DEFAULT` clause has
2829
+ * nowhere to bind parameters; use `sql.raw()` for a verbatim expression.
1853
2830
  */
1854
2831
  default(value) {
1855
2832
  const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
1856
- return new _Column(
1857
- this.type,
1858
- { ...this.flags, hasDefault: true },
1859
- resolved,
1860
- this.onUpdateValue,
1861
- this.reference
1862
- );
2833
+ if (bindsParameters(resolved)) {
2834
+ throw new Error(
2835
+ "sql.expr`...` binds parameters and cannot be a column default \u2014 use sql.raw() for a verbatim DEFAULT expression."
2836
+ );
2837
+ }
2838
+ return this.derive({
2839
+ flags: { ...this.flags, hasDefault: true },
2840
+ defaultValue: resolved
2841
+ });
1863
2842
  }
1864
2843
  /**
1865
2844
  * Re-apply a value whenever the row is updated (e.g. an `updated_at` column
1866
2845
  * with `sql.now()`). Mirrors SQLAlchemy's `onupdate`.
2846
+ *
2847
+ * @param value The literal value, or a {@link sql} expression.
2848
+ * @returns A new column carrying the on-update value.
2849
+ * @throws Error When given a `sql.expr` fragment (see {@link Column.default}).
1867
2850
  */
1868
2851
  onUpdate(value) {
1869
2852
  const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
1870
- return new _Column(this.type, this.flags, this.defaultValue, resolved, this.reference);
2853
+ if (bindsParameters(resolved)) {
2854
+ throw new Error(
2855
+ "sql.expr`...` binds parameters and cannot be an onUpdate default \u2014 use sql.raw() for a verbatim expression."
2856
+ );
2857
+ }
2858
+ return this.derive({ onUpdateValue: resolved });
1871
2859
  }
1872
2860
  };
1873
2861
  function makeColumn(kind, meta = {}) {
@@ -1919,7 +2907,27 @@ var column = {
1919
2907
  /** `UUID` → `string`. */
1920
2908
  uuid: () => makeColumn("uuid"),
1921
2909
  /** `ENUM(...values)` → a string-literal union of the given values. */
1922
- enum: (...values) => makeColumn("enum", { values })
2910
+ enum: (...values) => makeColumn("enum", { values }),
2911
+ /**
2912
+ * A PostgreSQL array column (`text[]`, `integer[]`) → `T[]`.
2913
+ *
2914
+ * PostgreSQL only: SQLite and MySQL have no native array type, and rendering
2915
+ * one as JSON there would give the same model different semantics per dialect
2916
+ * (`@>` and `&&` work on one and not the other), so the DDL renderer throws
2917
+ * for those dialects instead of falling back silently.
2918
+ *
2919
+ * @param element The element column (its type, not its flags, is what is used).
2920
+ * @returns A column whose inferred type is an array of the element's type.
2921
+ *
2922
+ * @example
2923
+ * ```ts
2924
+ * class ApiKey extends Model {
2925
+ * static tablename = "api_keys";
2926
+ * scopes = column.array(column.text()).notNull().default(["send"]);
2927
+ * }
2928
+ * ```
2929
+ */
2930
+ array: (element) => makeColumn("array", { element: element.type })
1923
2931
  };
1924
2932
  function unique(...columns) {
1925
2933
  if (columns.length === 0) {
@@ -1943,6 +2951,9 @@ function foreignKey(columns, refTable, refColumns, options) {
1943
2951
  onUpdate: options?.onUpdate
1944
2952
  };
1945
2953
  }
2954
+ function toSnakeCase(name) {
2955
+ return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
2956
+ }
1946
2957
  var Model = class {
1947
2958
  static tablename;
1948
2959
  /**
@@ -1951,6 +2962,12 @@ var Model = class {
1951
2962
  * `__table_args__`.
1952
2963
  */
1953
2964
  static tableArgs;
2965
+ /**
2966
+ * How to derive column names from property names (default `"preserve"`). Set
2967
+ * `"snake_case"` to keep a `snake_case` schema behind a `camelCase` model
2968
+ * without annotating every column; {@link Column.name} overrides it per column.
2969
+ */
2970
+ static naming;
1954
2971
  };
1955
2972
  var columnsCache = /* @__PURE__ */ new WeakMap();
1956
2973
  function columnsOf(model) {
@@ -1966,6 +2983,47 @@ function columnsOf(model) {
1966
2983
  columnsCache.set(model, out);
1967
2984
  return out;
1968
2985
  }
2986
+ var nameMapCache = /* @__PURE__ */ new WeakMap();
2987
+ var propMapCache = /* @__PURE__ */ new WeakMap();
2988
+ function columnNamesOf(model) {
2989
+ const cached = nameMapCache.get(model);
2990
+ if (cached !== void 0) return cached;
2991
+ const strategy = model.naming ?? "preserve";
2992
+ const map = {};
2993
+ const seen = /* @__PURE__ */ new Map();
2994
+ let renamed = false;
2995
+ for (const [prop, col2] of Object.entries(columnsOf(model))) {
2996
+ const dbName = col2.dbName ?? (strategy === "snake_case" ? toSnakeCase(prop) : prop);
2997
+ const collision = seen.get(dbName);
2998
+ if (collision !== void 0) {
2999
+ throw new Error(
3000
+ `${model.tablename}: properties "${collision}" and "${prop}" both map to column "${dbName}".`
3001
+ );
3002
+ }
3003
+ seen.set(dbName, prop);
3004
+ map[prop] = dbName;
3005
+ if (dbName !== prop) renamed = true;
3006
+ }
3007
+ const result = renamed ? map : null;
3008
+ nameMapCache.set(model, result);
3009
+ return result;
3010
+ }
3011
+ function columnPropsOf(model) {
3012
+ const cached = propMapCache.get(model);
3013
+ if (cached !== void 0) return cached;
3014
+ const forward = columnNamesOf(model);
3015
+ let result = null;
3016
+ if (forward) {
3017
+ const inverse = {};
3018
+ for (const [prop, dbName] of Object.entries(forward)) inverse[dbName] = prop;
3019
+ result = inverse;
3020
+ }
3021
+ propMapCache.set(model, result);
3022
+ return result;
3023
+ }
3024
+ function dbColumn(names, prop) {
3025
+ return names?.[prop] ?? prop;
3026
+ }
1969
3027
 
1970
3028
  exports.ActiveRecord = ActiveRecord;
1971
3029
  exports.Agg = Agg;
@@ -1976,6 +3034,7 @@ exports.BaseDialect = BaseDialect;
1976
3034
  exports.BaseRepository = BaseRepository;
1977
3035
  exports.Column = Column;
1978
3036
  exports.DeleteBuilder = DeleteBuilder;
3037
+ exports.Expression = Expression;
1979
3038
  exports.InsertBuilder = InsertBuilder;
1980
3039
  exports.InvalidDatabaseUrl = InvalidDatabaseUrl;
1981
3040
  exports.JoinBuilder = JoinBuilder;
@@ -1984,6 +3043,7 @@ exports.MysqlDialect = MysqlDialect;
1984
3043
  exports.NoResultError = NoResultError;
1985
3044
  exports.NodeSqliteDriver = NodeSqliteDriver;
1986
3045
  exports.OPERATORS = OPERATORS;
3046
+ exports.Params = Params;
1987
3047
  exports.PostgresDialect = PostgresDialect;
1988
3048
  exports.QueryExecutionError = QueryExecutionError;
1989
3049
  exports.RecordNotFound = RecordNotFound;
@@ -1998,19 +3058,27 @@ exports.activeRecord = activeRecord;
1998
3058
  exports.and = and;
1999
3059
  exports.avg = avg;
2000
3060
  exports.belongsTo = belongsTo;
3061
+ exports.col = col;
2001
3062
  exports.column = column;
3063
+ exports.columnNamesOf = columnNamesOf;
3064
+ exports.columnPropsOf = columnPropsOf;
2002
3065
  exports.columnsOf = columnsOf;
2003
3066
  exports.count = count;
2004
3067
  exports.createEngine = createEngine;
2005
3068
  exports.createSyncEngine = createSyncEngine;
3069
+ exports.dbColumn = dbColumn;
2006
3070
  exports.del = del;
2007
3071
  exports.detectDialect = detectDialect;
3072
+ exports.fn = fn;
2008
3073
  exports.foreignKey = foreignKey;
2009
3074
  exports.fromDict = fromDict;
2010
3075
  exports.getDialect = getDialect;
2011
3076
  exports.hasMany = hasMany;
2012
3077
  exports.insert = insert;
2013
3078
  exports.isCondition = isCondition;
3079
+ exports.isExpression = isExpression;
3080
+ exports.isSqlExpression = isSqlExpression;
3081
+ exports.isSubquery = isSubquery;
2014
3082
  exports.join = join;
2015
3083
  exports.loadRelations = loadRelations;
2016
3084
  exports.max = max;
@@ -2023,10 +3091,13 @@ exports.select = select;
2023
3091
  exports.sql = sql;
2024
3092
  exports.stringify = stringify;
2025
3093
  exports.sum = sum;
3094
+ exports.toAsyncDriver = toAsyncDriver;
2026
3095
  exports.toCondNode = toCondNode;
2027
3096
  exports.toDict = toDict;
2028
3097
  exports.toJSON = toJSON;
3098
+ exports.toSnakeCase = toSnakeCase;
2029
3099
  exports.unique = unique;
2030
3100
  exports.update = update;
3101
+ exports.val = val;
2031
3102
  //# sourceMappingURL=index.cjs.map
2032
3103
  //# sourceMappingURL=index.cjs.map