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.d.ts CHANGED
@@ -26,6 +26,28 @@ interface AggregateTerm {
26
26
  /** The result alias. */
27
27
  readonly alias: string;
28
28
  }
29
+ /**
30
+ * A row-level locking clause (`SELECT ... FOR UPDATE`).
31
+ *
32
+ * `wait` decides what happens when another transaction already holds the lock:
33
+ * `"block"` waits, `"skipLocked"` skips those rows (the job-queue claim), and
34
+ * `"noWait"` fails immediately.
35
+ */
36
+ interface LockClause {
37
+ readonly strength: "update" | "share";
38
+ readonly wait: "block" | "skipLocked" | "noWait";
39
+ /** Tables to lock (`FOR UPDATE OF t`); empty locks every table in the query. */
40
+ readonly of: readonly string[];
41
+ }
42
+ /** Options accepted by {@link SelectBuilder.forUpdate} / {@link SelectBuilder.forShare}. */
43
+ interface LockOptions {
44
+ /** Skip rows another transaction has locked instead of waiting for them. */
45
+ readonly skipLocked?: boolean;
46
+ /** Fail immediately instead of waiting for a locked row. */
47
+ readonly noWait?: boolean;
48
+ /** Restrict the lock to these tables (`FOR UPDATE OF ...`). */
49
+ readonly of?: readonly string[];
50
+ }
29
51
  /** Serializable AST for a SELECT. Dialects (Phase 4) compile this to SQL. */
30
52
  interface SelectNode {
31
53
  readonly kind: "select";
@@ -39,20 +61,44 @@ interface SelectNode {
39
61
  /** `GROUP BY` columns. */
40
62
  readonly groupBy: readonly string[];
41
63
  readonly where: CondNode | undefined;
64
+ /** `HAVING` condition, keyed by aggregate alias or grouped column. */
65
+ readonly having?: CondNode | undefined;
42
66
  readonly orderBy: readonly OrderTerm[];
43
67
  readonly limit: number | undefined;
44
68
  readonly offset: number | undefined;
69
+ /** Row-level locking clause, or `undefined` for none. */
70
+ readonly lock?: LockClause | undefined;
71
+ /** Property → column map, or `undefined` when every name is the identity. */
72
+ readonly names?: NameMap | undefined;
45
73
  }
74
+ /**
75
+ * A SELECT projecting exactly one column of type `T`, usable as the operand of
76
+ * `in` / `notIn`.
77
+ *
78
+ * Build it with {@link SelectBuilder.asSubquery}, which both narrows the
79
+ * projection to one column and gives the operand its element type.
80
+ */
81
+ interface Subquery<T> {
82
+ /** Phantom: the element type the subquery yields, read only by the type system. */
83
+ readonly __element?: T;
84
+ /** The AST the outer statement embeds. */
85
+ readonly node: SelectNode;
86
+ }
87
+ /** Runtime guard: is this `in`/`notIn` operand a subquery rather than a list? */
88
+ declare function isSubquery(value: unknown): value is Subquery<unknown>;
46
89
  /** Operators valid on every column type. */
47
90
  interface BaseOperators<T> {
48
91
  /** Equal to. */
49
92
  eq?: T;
50
93
  /** Not equal to. */
51
94
  ne?: T;
52
- /** One of the given values (`IN`). */
53
- in?: readonly T[];
54
- /** None of the given values (`NOT IN`). */
55
- notIn?: readonly T[];
95
+ /**
96
+ * One of the given values (`IN`) — a list, or a single-column
97
+ * {@link Subquery} built with `.asSubquery(column)`.
98
+ */
99
+ in?: readonly T[] | Subquery<T>;
100
+ /** None of the given values (`NOT IN`), as a list or a {@link Subquery}. */
101
+ notIn?: readonly T[] | Subquery<T>;
56
102
  /** `IS NULL` (true) / `IS NOT NULL` (false). */
57
103
  isNull?: boolean;
58
104
  }
@@ -71,19 +117,41 @@ interface OrderedOperators<T> extends BaseOperators<T> {
71
117
  }
72
118
  /** Extra operators for string-like types. */
73
119
  interface StringOperators<T> extends BaseOperators<T> {
74
- /** `LIKE` pattern (case-sensitive). */
120
+ /** `LIKE` pattern (case-sensitive). `%` and `_` are wildcards. */
75
121
  like?: string;
76
- /** `ILIKE` pattern (case-insensitive). */
122
+ /**
123
+ * `ILIKE` **pattern** (case-insensitive). This is pattern matching, not
124
+ * equality: `%` and `_` in the operand are wildcards, so `{ ilike: "%" }`
125
+ * matches every row. Never feed it unescaped user input — for a
126
+ * case-insensitive *equality* test use {@link StringOperators.ieq}, and to
127
+ * match a literal that may contain wildcards, wrap it in `escapeLike`.
128
+ */
77
129
  ilike?: string;
130
+ /**
131
+ * Case-insensitive equality — compiles to `lower(col) = lower($1)`, with no
132
+ * wildcards. The safe operator for a case-insensitive lookup (login, email),
133
+ * and the one that matches a `lower(col)` functional index.
134
+ */
135
+ ieq?: T;
136
+ }
137
+ /** Extra operators for array columns (PostgreSQL). */
138
+ interface ArrayOperators<T> extends BaseOperators<T> {
139
+ /** `@>` — the column contains every element of the operand. */
140
+ contains?: T;
141
+ /** `<@` — every element of the column is in the operand. */
142
+ containedBy?: T;
143
+ /** `&&` — the column and the operand share at least one element. */
144
+ overlaps?: T;
78
145
  }
79
146
  /**
80
147
  * The operator object allowed for a column of (non-null) type `T`:
81
- * - `string` → equality, `in`, `like`/`ilike`
148
+ * - `T[]` → equality, `in`, `contains`/`containedBy`/`overlaps` (PostgreSQL)
149
+ * - `string` → equality, `in`, `like`/`ilike`/`ieq`
82
150
  * - `number` / `bigint` / `Date` → equality, `in`, ordered comparisons, `between`
83
151
  * - `boolean` → equality, `isNull`
84
152
  * - anything else (json/blob) → equality and `in` only
85
153
  */
86
- type OperatorsFor<T> = [T] extends [string] ? StringOperators<T> : [T] extends [number] ? OrderedOperators<T> : [T] extends [bigint] ? OrderedOperators<T> : [T] extends [Date] ? OrderedOperators<T> : [T] extends [boolean] ? BaseOperators<T> : BaseOperators<T>;
154
+ type OperatorsFor<T> = [T] extends [readonly unknown[]] ? ArrayOperators<T> : [T] extends [string] ? StringOperators<T> : [T] extends [number] ? OrderedOperators<T> : [T] extends [bigint] ? OrderedOperators<T> : [T] extends [Date] ? OrderedOperators<T> : [T] extends [boolean] ? BaseOperators<T> : BaseOperators<T>;
87
155
  /**
88
156
  * `where` shape: each key must be a real column; each value accepts either a
89
157
  * bare value (shorthand for `eq`) or an operator object restricted to operators
@@ -94,7 +162,7 @@ type WhereInput<Row = Record<string, unknown>> = {
94
162
  [K in keyof Row]?: Row[K] | OperatorsFor<NonNullable<Row[K]>>;
95
163
  };
96
164
  /** The full set of operator keys, for the dialect compiler to recognize. */
97
- declare const OPERATORS: readonly ["eq", "ne", "gt", "gte", "lt", "lte", "like", "ilike", "in", "notIn", "between", "isNull"];
165
+ declare const OPERATORS: readonly ["eq", "ne", "gt", "gte", "lt", "lte", "like", "ilike", "ieq", "in", "notIn", "between", "isNull", "contains", "containedBy", "overlaps"];
98
166
  /** One supported operator name. */
99
167
  type Operator = (typeof OPERATORS)[number];
100
168
  /** An aggregate expression carrying its result type `T` as a phantom. */
@@ -126,20 +194,43 @@ type SimplifyProj<T> = {
126
194
  * @typeParam Full - the complete row type (constrains where/orderBy keys).
127
195
  * @typeParam Proj - the projected result type returned on execution.
128
196
  */
129
- declare class SelectBuilder<Full, Proj = Full> {
197
+ declare class SelectBuilder<Full, Proj = Full, Grouped extends boolean = false> {
130
198
  readonly node: SelectNode;
131
199
  /** The source model, used to coerce rows on execution. */
132
200
  readonly source: ModelClass;
133
201
  /** Phantom: the result element type, read only by the type system. */
134
202
  readonly __row: Proj;
203
+ /** Phantom: `true` once `aggregate()` has made `having()` meaningful. */
204
+ readonly __grouped: Grouped;
135
205
  constructor(node: SelectNode,
136
206
  /** The source model, used to coerce rows on execution. */
137
207
  source: ModelClass);
138
208
  private with;
139
209
  /** Add a WHERE filter: the object form (keys typed) or an `and`/`or`/`not`. */
140
- where(input: WhereInput<Full> | Condition): SelectBuilder<Full, Proj>;
210
+ where(input: WhereInput<Full> | Condition): SelectBuilder<Full, Proj, Grouped>;
211
+ /**
212
+ * Filter by the result of the aggregation (`HAVING`).
213
+ *
214
+ * Only available on a grouped builder — `.having()` before `.aggregate()` is a
215
+ * compile error, not invalid SQL at runtime. Keys are the aggregate aliases you
216
+ * named plus the grouped columns; `WHERE` still filters rows *before* grouping,
217
+ * which is a different question.
218
+ *
219
+ * @param input The condition, keyed by alias or grouped column.
220
+ * @returns A builder carrying the `HAVING` clause.
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * select(Outbound)
225
+ * .where({ status: "queued" })
226
+ * .aggregate(["consumer"], { n: count() })
227
+ * .having({ n: { gt: 100 } });
228
+ * // ... GROUP BY "consumer" HAVING COUNT(*) > $2
229
+ * ```
230
+ */
231
+ having(this: SelectBuilder<Full, Proj, true>, input: WhereInput<Proj> | Condition): SelectBuilder<Full, Proj, true>;
141
232
  /** Emit `SELECT DISTINCT` — drop duplicate rows. */
142
- distinct(): SelectBuilder<Full, Proj>;
233
+ distinct(): SelectBuilder<Full, Proj, Grouped>;
143
234
  /**
144
235
  * Group by columns and compute aggregates. The result row is the grouped
145
236
  * columns (typed from the model) plus one field per aggregate alias.
@@ -158,13 +249,88 @@ declare class SelectBuilder<Full, Proj = Full> {
158
249
  */
159
250
  aggregate<K extends keyof Full & string, S extends Record<string, Agg<unknown>>>(groupBy: readonly K[], spec: S): SelectBuilder<Full, SimplifyProj<Pick<Full, K> & {
160
251
  [A in keyof S]: AggResult<S[A]>;
161
- }>>;
162
- /** Order by a column of `Full`. */
163
- orderBy(column: keyof Full & string, direction?: SortDirection): SelectBuilder<Full, Proj>;
252
+ }>, true>;
253
+ /**
254
+ * Order by a column of the model, or — on a grouped query — by an aggregate
255
+ * alias. Unlike `HAVING`, every dialect accepts the output alias in `ORDER BY`,
256
+ * so the alias is emitted as written.
257
+ *
258
+ * @param column A model column, or a projected alias.
259
+ * @param direction `"asc"` (default) or `"desc"`.
260
+ * @returns A builder carrying the ordering term.
261
+ */
262
+ orderBy(column: (keyof Full & string) | (keyof Proj & string), direction?: SortDirection): SelectBuilder<Full, Proj, Grouped>;
164
263
  /** Limit the number of rows. */
165
- limit(n: number): SelectBuilder<Full, Proj>;
264
+ limit(n: number): SelectBuilder<Full, Proj, Grouped>;
166
265
  /** Skip the first `n` rows. */
167
- offset(n: number): SelectBuilder<Full, Proj>;
266
+ offset(n: number): SelectBuilder<Full, Proj, Grouped>;
267
+ /**
268
+ * Narrow this SELECT to a single column and mark it as a subquery, so it can be
269
+ * the operand of `in` / `notIn`.
270
+ *
271
+ * The whole query — `where`, `orderBy`, `limit`, and a locking clause — is
272
+ * embedded in the outer statement, which is what collapses the claim-a-batch
273
+ * pattern into one round trip instead of selecting ids and sending them back.
274
+ *
275
+ * @param column The single column to project (checked against the model).
276
+ * @returns A subquery carrying that column's type.
277
+ *
278
+ * @example
279
+ * ```ts
280
+ * update(Outbound)
281
+ * .set({ status: "sending", attempts: sql.raw("attempts + 1") })
282
+ * .where({
283
+ * id: {
284
+ * in: select(Outbound)
285
+ * .where({ status: "queued" })
286
+ * .orderBy("nextAttemptAt")
287
+ * .limit(10)
288
+ * .forUpdate({ skipLocked: true })
289
+ * .asSubquery("id"),
290
+ * },
291
+ * })
292
+ * .returning();
293
+ * ```
294
+ */
295
+ asSubquery<K extends keyof Full & string>(column: K): Subquery<Full[K]>;
296
+ /**
297
+ * Lock the selected rows for update (`SELECT ... FOR UPDATE`), à la
298
+ * SQLAlchemy's `with_for_update()`.
299
+ *
300
+ * `{ skipLocked: true }` is the job-queue claim: competing workers each take a
301
+ * disjoint batch instead of blocking on — or worse, double-processing — the
302
+ * same rows.
303
+ *
304
+ * PostgreSQL and MySQL 8.0+ only. SQLite has no row-level locking, and its
305
+ * dialect throws rather than emitting a `SELECT` that silently locks nothing —
306
+ * a lock that does not exist only fails under production concurrency.
307
+ *
308
+ * @param options `skipLocked` / `noWait` wait behavior, and `of` to restrict
309
+ * the lock to specific tables.
310
+ * @returns A builder carrying the locking clause.
311
+ * @throws Error When both `skipLocked` and `noWait` are set.
312
+ *
313
+ * @example
314
+ * ```ts
315
+ * const batch = await session.execute(
316
+ * select(Outbound)
317
+ * .where({ status: "queued" })
318
+ * .orderBy("nextAttemptAt")
319
+ * .limit(10)
320
+ * .forUpdate({ skipLocked: true }),
321
+ * ).all();
322
+ * ```
323
+ */
324
+ forUpdate(options?: LockOptions): SelectBuilder<Full, Proj, Grouped>;
325
+ /**
326
+ * Take a shared read lock on the selected rows (`SELECT ... FOR SHARE`), the
327
+ * weaker counterpart of {@link SelectBuilder.forUpdate}.
328
+ *
329
+ * @param options `skipLocked` / `noWait` wait behavior, and `of` tables.
330
+ * @returns A builder carrying the locking clause.
331
+ * @throws Error When both `skipLocked` and `noWait` are set.
332
+ */
333
+ forShare(options?: LockOptions): SelectBuilder<Full, Proj, Grouped>;
168
334
  }
169
335
  /** Build a SELECT over every column of the model. */
170
336
  declare function select<C extends ModelClass>(model: C): SelectBuilder<InferModel<C>, InferModel<C>>;
@@ -185,6 +351,25 @@ interface CondFields {
185
351
  readonly kind: "fields";
186
352
  readonly fields: Record<string, unknown>;
187
353
  }
354
+ /**
355
+ * One side of a comparison: a column reference, a bound value, or a SQL function
356
+ * applied to other expressions.
357
+ *
358
+ * A column reference carries the **property** name, not the database column —
359
+ * the dialect resolves it through the node's name map, so `col()` cannot become a
360
+ * back door around an explicit `.name()` mapping.
361
+ */
362
+ type ExprNode = {
363
+ readonly kind: "column";
364
+ readonly name: string;
365
+ } | {
366
+ readonly kind: "value";
367
+ readonly value: unknown;
368
+ } | {
369
+ readonly kind: "fn";
370
+ readonly name: string;
371
+ readonly args: readonly ExprNode[];
372
+ };
188
373
  /** Logical condition nodes. */
189
374
  type CondNode = CondFields | {
190
375
  readonly kind: "and";
@@ -195,6 +380,11 @@ type CondNode = CondFields | {
195
380
  } | {
196
381
  readonly kind: "not";
197
382
  readonly part: CondNode;
383
+ } | {
384
+ readonly kind: "compare";
385
+ readonly left: ExprNode;
386
+ readonly op: Operator;
387
+ readonly right: ExprNode;
198
388
  };
199
389
  declare const CONDITION: unique symbol;
200
390
  /** A composed condition produced by `and`/`or`/`not`. */
@@ -214,6 +404,145 @@ declare function toCondNode(input: Condition | Record<string, unknown>): CondNod
214
404
  * for full key + operator checking inside combinators.
215
405
  */
216
406
  type WhereArg<Row = Record<string, unknown>> = WhereInput<Row> | Condition;
407
+ /** Runtime guard: is this operand a built {@link Expression}? */
408
+ declare function isExpression(value: unknown): value is Expression;
409
+ /**
410
+ * One side of a comparison, built with {@link col}, {@link val} or {@link fn}.
411
+ *
412
+ * The comparison methods mirror the `where` operators, but both sides are
413
+ * expressions — which is what makes `col("total").gt(col("paid"))` and a
414
+ * functional-index lookup like `fn.lower("email").eq(fn.lower(val(probe)))`
415
+ * expressible at all. An operand that is not an `Expression` is bound as a
416
+ * parameter, so `.eq(probe)` stays safe by default.
417
+ */
418
+ declare class Expression {
419
+ /** The expression AST the dialect renders. */
420
+ readonly node: ExprNode;
421
+ constructor(
422
+ /** The expression AST the dialect renders. */
423
+ node: ExprNode);
424
+ /** Compare this expression against another expression or a bound value. */
425
+ private compare;
426
+ /** `=` (or `IS NULL` for a null value). */
427
+ eq(operand: unknown): Condition;
428
+ /** `<>` (or `IS NOT NULL` for a null value). */
429
+ ne(operand: unknown): Condition;
430
+ /** `>`. */
431
+ gt(operand: unknown): Condition;
432
+ /** `>=`. */
433
+ gte(operand: unknown): Condition;
434
+ /** `<`. */
435
+ lt(operand: unknown): Condition;
436
+ /** `<=`. */
437
+ lte(operand: unknown): Condition;
438
+ /** `LIKE` — `%` and `_` in the operand are wildcards. */
439
+ like(pattern: string | Expression): Condition;
440
+ /** `ILIKE` — case-insensitive **pattern** matching, wildcards included. */
441
+ ilike(pattern: string | Expression): Condition;
442
+ /** Case-insensitive equality (`lower(a) = lower(b)`), with no wildcards. */
443
+ ieq(operand: unknown): Condition;
444
+ /**
445
+ * `IN (...)` over a list of values.
446
+ *
447
+ * @param values The values to test against.
448
+ * @returns The condition.
449
+ * @throws Error When an entry is an {@link Expression} — a list operand is
450
+ * bound, so an expression there would be serialized as a parameter instead of
451
+ * rendered as SQL.
452
+ */
453
+ in(values: readonly unknown[]): Condition;
454
+ /**
455
+ * `NOT IN (...)` over a list of values.
456
+ *
457
+ * @param values The values to exclude.
458
+ * @returns The condition.
459
+ * @throws Error When an entry is an {@link Expression} (see {@link Expression.in}).
460
+ */
461
+ notIn(values: readonly unknown[]): Condition;
462
+ /**
463
+ * `BETWEEN lo AND hi` (inclusive).
464
+ *
465
+ * @param lo The lower bound.
466
+ * @param hi The upper bound.
467
+ * @returns The condition.
468
+ * @throws Error When a bound is an {@link Expression} (see {@link Expression.in}).
469
+ */
470
+ between(lo: unknown, hi: unknown): Condition;
471
+ /** `IS NULL` (true) / `IS NOT NULL` (false). */
472
+ isNull(value?: boolean): Condition;
473
+ }
474
+ /**
475
+ * A column reference, by **property** name.
476
+ *
477
+ * Pass the row type for key-safety: `col<UserRow>("total")` rejects a name that
478
+ * is not a column. The name is resolved through the model's column-name map at
479
+ * compile time, exactly like the object form of `where`.
480
+ *
481
+ * @param name The model property name.
482
+ * @returns An expression referencing that column.
483
+ *
484
+ * @example
485
+ * ```ts
486
+ * select(Order).where(col<OrderRow>("total").gt(col<OrderRow>("paid")));
487
+ * // SELECT * FROM "orders" WHERE "total" > "paid"
488
+ * ```
489
+ */
490
+ declare function col<Row = Record<string, unknown>>(name: keyof Row & string): Expression;
491
+ /**
492
+ * A bound value, for the places that take an expression and would otherwise read
493
+ * a bare string as a column name (the arguments of {@link fn}).
494
+ *
495
+ * @param value The value to bind as a parameter.
496
+ * @returns An expression that renders as a placeholder.
497
+ */
498
+ declare function val(value: unknown): Expression;
499
+ /**
500
+ * Build a call to a SQL function.
501
+ *
502
+ * A bare string argument is a **column name** — that is the useful default here
503
+ * (`fn.lower("username")`), and it is why a literal has to be wrapped in
504
+ * {@link val}. The function name is interpolated into the statement, so it is
505
+ * validated as a plain identifier and must never come from user input; the
506
+ * arguments are always rendered through the expression compiler.
507
+ *
508
+ * @param name The SQL function name.
509
+ * @param args Column names or expressions.
510
+ * @returns An expression for the call.
511
+ * @throws Error When `name` is not a plain SQL identifier.
512
+ */
513
+ declare function call(name: string, ...args: (Expression | string)[]): Expression;
514
+ /**
515
+ * SQL functions usable on either side of a comparison.
516
+ *
517
+ * The named entries are the ones every supported dialect implements. Anything
518
+ * else — `date_trunc`, `strftime`, a custom function — goes through
519
+ * {@link fn.call}, which does not pretend to be portable.
520
+ *
521
+ * @example
522
+ * ```ts
523
+ * select(AdminUser).where(fn.lower("username").eq(fn.lower(val(probe))));
524
+ * // WHERE lower("username") = lower($1)
525
+ * ```
526
+ */
527
+ declare const fn: {
528
+ /** `lower(x)`. */
529
+ readonly lower: (arg: Expression | string) => Expression;
530
+ /** `upper(x)`. */
531
+ readonly upper: (arg: Expression | string) => Expression;
532
+ /** `trim(x)`. */
533
+ readonly trim: (arg: Expression | string) => Expression;
534
+ /** `length(x)`. */
535
+ readonly length: (arg: Expression | string) => Expression;
536
+ /** `abs(x)`. */
537
+ readonly abs: (arg: Expression | string) => Expression;
538
+ /** `coalesce(a, b, ...)`. */
539
+ readonly coalesce: (...args: (Expression | string)[]) => Expression;
540
+ /**
541
+ * Any other SQL function, by name. Portability is the caller's problem —
542
+ * `date_trunc` is PostgreSQL, `strftime` is SQLite.
543
+ */
544
+ readonly call: typeof call;
545
+ };
217
546
  /** Combine conditions with `AND`. */
218
547
  declare function and<Row = Record<string, unknown>>(...inputs: WhereArg<NoInfer<Row>>[]): Condition;
219
548
  /** Combine conditions with `OR`. */
@@ -235,6 +564,19 @@ declare function not<Row = Record<string, unknown>>(input: WhereArg<NoInfer<Row>
235
564
 
236
565
  /** Columns to return from a mutation, or "*" for the whole row. */
237
566
  type Returning = readonly string[] | "*" | null;
567
+ /**
568
+ * A write shape over `Row`: every column accepts its own value **or** a
569
+ * {@link SqlExpression}, which the dialect renders inline instead of binding.
570
+ * Optionality is preserved from `Row`, so an insert shape keeps its defaults
571
+ * optional.
572
+ */
573
+ type WriteValues<Row> = {
574
+ [K in keyof Row]: Row[K] | SqlExpression;
575
+ };
576
+ /** A partial write shape — the `SET` clause of an UPDATE or a `DO UPDATE`. */
577
+ type WritePatch<Row> = {
578
+ [K in keyof Row]?: Row[K] | SqlExpression;
579
+ };
238
580
  /**
239
581
  * Conflict-resolution clause for an INSERT (`ON CONFLICT`). `target` is the
240
582
  * conflicting column(s) (a unique/PK constraint); `update` is `"nothing"` for
@@ -243,6 +585,28 @@ type Returning = readonly string[] | "*" | null;
243
585
  interface OnConflict {
244
586
  readonly target: readonly string[];
245
587
  readonly update: Record<string, unknown> | "nothing";
588
+ /**
589
+ * The predicate of a **partial** unique index. PostgreSQL only matches a
590
+ * partial index as a conflict target when `ON CONFLICT` repeats its predicate,
591
+ * so without this an insert against `... WHERE key IS NOT NULL` is rejected
592
+ * with "there is no unique or exclusion constraint matching the ON CONFLICT
593
+ * specification".
594
+ */
595
+ readonly targetWhere?: CondNode | undefined;
596
+ /** Extra condition restricting which conflicting rows `DO UPDATE` rewrites. */
597
+ readonly updateWhere?: CondNode | undefined;
598
+ }
599
+ /** Options for the `ON CONFLICT` clause of {@link InsertBuilder.onConflictDoNothing}. */
600
+ interface OnConflictOptions<Full> {
601
+ /** The predicate of the partial unique index used as the conflict target. */
602
+ readonly where?: WhereInput<Full> | Condition;
603
+ }
604
+ /** Options for {@link InsertBuilder.onConflictDoUpdate}. */
605
+ interface OnConflictUpdateOptions<Full> {
606
+ /** The predicate of the partial unique index used as the conflict target. */
607
+ readonly indexWhere?: WhereInput<Full> | Condition;
608
+ /** Extra condition deciding which conflicting rows are actually rewritten. */
609
+ readonly updateWhere?: WhereInput<Full> | Condition;
246
610
  }
247
611
  /** Serializable AST for an INSERT. */
248
612
  interface InsertNode {
@@ -252,6 +616,8 @@ interface InsertNode {
252
616
  readonly returning: Returning;
253
617
  /** Conflict handling (`ON CONFLICT ...`), or `undefined` for none. */
254
618
  readonly onConflict?: OnConflict;
619
+ /** Property → column map, or `undefined` when every name is the identity. */
620
+ readonly names?: NameMap | undefined;
255
621
  }
256
622
  /**
257
623
  * INSERT builder.
@@ -269,21 +635,46 @@ declare class InsertBuilder<Full, Ins, Ret = number> {
269
635
  /** The source model, used to coerce returned rows on execution. */
270
636
  source: ModelClass);
271
637
  private with;
272
- /** Provide one row or many rows to insert, typed by the insert shape. */
273
- values(rows: Ins | readonly Ins[]): InsertBuilder<Full, Ins, Ret>;
638
+ /**
639
+ * Provide one row or many rows to insert, typed by the insert shape.
640
+ *
641
+ * @param rows One row, or an array of rows.
642
+ * @returns A builder carrying the rows.
643
+ * @throws ValidationError When a value is not a column value the dialect can
644
+ * bind (see the `sql` helpers for writing an expression instead).
645
+ */
646
+ values(rows: WriteValues<Ins> | readonly WriteValues<Ins>[]): InsertBuilder<Full, Ins, Ret>;
274
647
  /**
275
648
  * On a unique/PK conflict on `target`, do nothing (skip the row).
276
649
  *
277
650
  * @param target The conflicting column(s) — a unique or primary key.
651
+ * @param options Pass `where` to name the predicate of a **partial** unique
652
+ * index, which PostgreSQL requires in order to match it as a conflict target.
653
+ * @returns A builder carrying the conflict clause.
654
+ *
655
+ * @example
656
+ * ```ts
657
+ * insert(Outbound)
658
+ * .values(data)
659
+ * .onConflictDoNothing(["consumer", "idempotencyKey"], {
660
+ * where: { idempotencyKey: { isNull: false } },
661
+ * })
662
+ * .returning();
663
+ * ```
278
664
  */
279
- onConflictDoNothing(target: readonly (keyof Full & string)[]): InsertBuilder<Full, Ins, Ret>;
665
+ onConflictDoNothing(target: readonly (keyof Full & string)[], options?: OnConflictOptions<Full>): InsertBuilder<Full, Ins, Ret>;
280
666
  /**
281
667
  * On a unique/PK conflict on `target`, overwrite the given columns (upsert).
282
668
  *
283
669
  * @param target The conflicting column(s) — a unique or primary key.
284
670
  * @param set The columns to update with new values.
671
+ * @param options `indexWhere` names the predicate of a partial unique index
672
+ * (the conflict target); `updateWhere` further restricts which conflicting
673
+ * rows are rewritten.
674
+ * @returns A builder carrying the conflict clause.
675
+ * @throws ValidationError When a `set` value cannot be bound.
285
676
  */
286
- onConflictDoUpdate(target: readonly (keyof Full & string)[], set: Partial<Full>): InsertBuilder<Full, Ins, Ret>;
677
+ onConflictDoUpdate(target: readonly (keyof Full & string)[], set: WritePatch<Full>, options?: OnConflictUpdateOptions<Full>): InsertBuilder<Full, Ins, Ret>;
287
678
  /** Return the full inserted row(s). */
288
679
  returning(): InsertBuilder<Full, Ins, Full>;
289
680
  /** Return only the given columns of the inserted row(s). */
@@ -300,6 +691,8 @@ interface UpdateNode {
300
691
  /** True once a where-clause or explicit opt-in makes the write safe. */
301
692
  readonly guarded: boolean;
302
693
  readonly returning: Returning;
694
+ /** Property → column map, or `undefined` when every name is the identity. */
695
+ readonly names?: NameMap | undefined;
303
696
  }
304
697
  /**
305
698
  * UPDATE builder.
@@ -318,8 +711,26 @@ declare class UpdateBuilder<Full, Guarded extends boolean, Ret = number> {
318
711
  /** The source model, used to coerce returned rows on execution. */
319
712
  source: ModelClass);
320
713
  private with;
321
- /** The columns to write. Partial — only the given columns change. */
322
- set(values: Partial<Full>): UpdateBuilder<Full, Guarded, Ret>;
714
+ /**
715
+ * The columns to write. Partial — only the given columns change.
716
+ *
717
+ * A value is bound as a parameter unless it is a {@link sql} expression, which
718
+ * is rendered inline instead — that is how a counter is written without a
719
+ * read-modify-write race.
720
+ *
721
+ * @param values The column → value map.
722
+ * @returns A builder carrying the assignments.
723
+ * @throws ValidationError When a value is not a column value the dialect can
724
+ * bind (a bare object, an array on a scalar column, a function).
725
+ *
726
+ * @example
727
+ * ```ts
728
+ * update(Outbound)
729
+ * .set({ attempts: sql.raw("attempts + 1"), updatedAt: sql.now() })
730
+ * .where({ id });
731
+ * ```
732
+ */
733
+ set(values: WritePatch<Full>): UpdateBuilder<Full, Guarded, Ret>;
323
734
  /** Restrict the rows to update. Marks the builder safe to execute. */
324
735
  where(input: WhereInput<Full> | Condition): UpdateBuilder<Full, true, Ret>;
325
736
  /** Explicit opt-in to update EVERY row. Use deliberately. */
@@ -338,6 +749,8 @@ interface DeleteNode {
338
749
  readonly where: CondNode | undefined;
339
750
  readonly guarded: boolean;
340
751
  readonly returning: Returning;
752
+ /** Property → column map, or `undefined` when every name is the identity. */
753
+ readonly names?: NameMap | undefined;
341
754
  }
342
755
  /**
343
756
  * DELETE builder. Starts unguarded — same safety rule as UPDATE.
@@ -532,6 +945,8 @@ interface JoinNode {
532
945
  }[];
533
946
  readonly limit: number | undefined;
534
947
  readonly offset: number | undefined;
948
+ /** Per-alias property → column maps, for the sources that rename columns. */
949
+ readonly names?: Readonly<Record<string, NameMap>> | undefined;
535
950
  }
536
951
  /** A map of source alias → its (possibly nullable) row type. */
537
952
  type Sources = Record<string, object | null>;
@@ -605,6 +1020,16 @@ interface CompiledQuery {
605
1020
  }
606
1021
  /** Any compilable AST node. */
607
1022
  type QueryNode = SelectNode | InsertNode | UpdateNode | DeleteNode | JoinNode;
1023
+ /**
1024
+ * Collects bound parameters and renders placeholders in dialect style. Exposed
1025
+ * because dialect subclasses receive it when overriding clause rendering.
1026
+ */
1027
+ declare class Params {
1028
+ private readonly placeholder;
1029
+ readonly values: unknown[];
1030
+ constructor(placeholder: (index: number) => string);
1031
+ bind(value: unknown): string;
1032
+ }
608
1033
  /**
609
1034
  * Base SQL compiler shared by every dialect. Subclasses customize only what
610
1035
  * actually differs between databases (placeholder syntax, `ILIKE` support).
@@ -623,6 +1048,25 @@ declare abstract class BaseDialect {
623
1048
  protected abstract placeholder(index: number): string;
624
1049
  /** Render a case-insensitive LIKE for the active dialect. */
625
1050
  protected abstract ilike(column: string, param: string): string;
1051
+ /**
1052
+ * Validate a subquery operand before it is rendered, for dialects that restrict
1053
+ * what an `IN (SELECT ...)` may contain. The default accepts everything.
1054
+ *
1055
+ * @param _node The subquery's AST.
1056
+ * @throws Error When the dialect cannot execute this subquery.
1057
+ */
1058
+ protected checkSubquery(_node: SelectNode): void;
1059
+ /**
1060
+ * The SQL operator for an array containment/overlap test.
1061
+ *
1062
+ * Only PostgreSQL has native arrays; the other dialects throw rather than
1063
+ * emitting an operator that means something else there.
1064
+ *
1065
+ * @param op The array operator name.
1066
+ * @returns The SQL operator text.
1067
+ * @throws Error On a dialect without native array support.
1068
+ */
1069
+ protected arrayOperator(op: "contains" | "containedBy" | "overlaps"): string;
626
1070
  /**
627
1071
  * Quote an identifier (column/table) for the active dialect.
628
1072
  *
@@ -634,10 +1078,83 @@ declare abstract class BaseDialect {
634
1078
  protected quoteId(name: string): string;
635
1079
  /** Compile any node to `{ sql, params }`. */
636
1080
  compile(node: QueryNode): CompiledQuery;
637
- /** Render a qualified `alias.column` ref as `"alias"."column"`. */
1081
+ /**
1082
+ * Render a qualified `alias.column` ref as `"alias"."column"`, translating the
1083
+ * property name to the real column name for that alias's model.
1084
+ *
1085
+ * @param ref The `alias.property` reference (a bare name is left unqualified).
1086
+ * @param names The node's per-alias name maps, if any source renames columns.
1087
+ * @returns The quoted, qualified identifier.
1088
+ */
638
1089
  private qualify;
1090
+ /**
1091
+ * Quote a column identifier, translating the model property name to the real
1092
+ * database column name first.
1093
+ *
1094
+ * `names` is `undefined` for a model that renames nothing — the overwhelmingly
1095
+ * common case — so this stays a single lookup plus the memoized quote.
1096
+ *
1097
+ * @param prop The model property name as written in the builder.
1098
+ * @param names The node's property → column map, if any.
1099
+ * @returns The quoted database identifier.
1100
+ */
1101
+ protected columnId(prop: string, names: NameMap | undefined): string;
1102
+ /**
1103
+ * Render a {@link SqlExpression} inline, binding the parameters it carries.
1104
+ *
1105
+ * This is what keeps `set({ attempts: sql.raw("attempts + 1") })` an expression
1106
+ * instead of a bound object: the fragment goes into the statement text, and
1107
+ * only a `sql.expr` template's interpolations become parameters.
1108
+ *
1109
+ * @param expr The branded expression.
1110
+ * @param params The parameter collector for the statement being compiled.
1111
+ * @returns The SQL text of the expression.
1112
+ */
1113
+ protected renderExpression(expr: SqlExpression, params: Params): string;
1114
+ /** Render one write value: a SQL expression inline, anything else as a parameter. */
1115
+ protected renderValue(value: unknown, params: Params): string;
1116
+ /**
1117
+ * Render a row-level locking clause (`FOR UPDATE ...`).
1118
+ *
1119
+ * Standard on PostgreSQL and MySQL 8.0+; SQLite overrides it to throw.
1120
+ *
1121
+ * @param lock The locking clause from the node.
1122
+ * @returns The SQL text, leading space included.
1123
+ */
1124
+ protected renderLock(lock: LockClause): string;
1125
+ /**
1126
+ * Compile a SELECT.
1127
+ *
1128
+ * Two alias rules differ between clauses and are handled here: PostgreSQL does
1129
+ * NOT accept a `SELECT` alias in `HAVING`, so an aggregate key is re-emitted as
1130
+ * its expression (`COUNT(*) > $1`), a form every dialect accepts; `ORDER BY`,
1131
+ * by contrast, accepts the output alias everywhere, so it is emitted as
1132
+ * written.
1133
+ *
1134
+ * @param node The select AST.
1135
+ * @param params The parameter collector.
1136
+ * @returns The SQL text.
1137
+ */
639
1138
  private compileSelect;
1139
+ /**
1140
+ * Compile an INSERT.
1141
+ *
1142
+ * Takes the cached fast path only when the statement text is a pure function of
1143
+ * its structure. A SQL expression among the values, or a conflict predicate,
1144
+ * makes the text depend on the values themselves — those compile uncached, in
1145
+ * SQL order, so placeholder positions stay correct.
1146
+ */
640
1147
  private compileInsert;
1148
+ /**
1149
+ * Compile an INSERT without the template cache, rendering clauses in statement
1150
+ * order so every parameter is bound at the position it appears.
1151
+ *
1152
+ * @param node The insert node.
1153
+ * @param columns The column keys shared by every row.
1154
+ * @param params The parameter collector.
1155
+ * @returns The SQL text.
1156
+ */
1157
+ private compileInsertDirect;
641
1158
  /**
642
1159
  * The INSERT SQL template for a given structure, cached across calls.
643
1160
  *
@@ -649,24 +1166,74 @@ declare abstract class BaseDialect {
649
1166
  private insertTemplate;
650
1167
  /**
651
1168
  * Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
652
- * `ON CONFLICT (...) DO NOTHING | DO UPDATE SET ...`; MySQL overrides this.
1169
+ * `ON CONFLICT (...) [WHERE predicate] DO NOTHING | DO UPDATE SET ... [WHERE ...]`;
1170
+ * MySQL overrides this.
1171
+ *
1172
+ * The index predicate is rendered before the `DO UPDATE` assignments because
1173
+ * that is where it sits in the statement, so its parameters bind first.
653
1174
  *
654
1175
  * @param onConflict The conflict clause from the node.
655
1176
  * @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
656
- * @param nextPlaceholder Yields the next positional placeholder (advances the count).
1177
+ * @param nextValue Yields the SQL for the next `DO UPDATE` assignment value.
1178
+ * @param names The node's property → column map, if any.
1179
+ * @param params The parameter collector, for the predicates.
1180
+ * @returns The SQL text, leading space included.
657
1181
  */
658
- protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextPlaceholder: () => string): string;
1182
+ protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextValue: () => string, names: NameMap | undefined, params: Params): string;
659
1183
  private compileUpdate;
660
1184
  private compileDelete;
661
1185
  private compileJoin;
662
- protected compileReturning(returning: readonly string[] | "*" | null): string;
1186
+ protected compileReturning(returning: readonly string[] | "*" | null, names?: NameMap | undefined): string;
663
1187
  /**
664
1188
  * Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
665
1189
  * key to a quoted identifier — `quoteId` for single-table, `qualify` for joins —
666
1190
  * so select/update/delete/join all share this one compiler.
667
1191
  */
668
1192
  private compileCondition;
1193
+ /**
1194
+ * Render one side of a comparison.
1195
+ *
1196
+ * A column reference goes through `idFor`, so an explicit `.name()` mapping and
1197
+ * join qualification apply here exactly as they do in the object form of
1198
+ * `where` — `col()` is not a way around them. Only a `value` node binds.
1199
+ *
1200
+ * @param node The expression AST.
1201
+ * @param params The parameter collector.
1202
+ * @param idFor The identifier resolver for the enclosing statement.
1203
+ * @returns The SQL text of the expression.
1204
+ */
1205
+ private renderExpr;
1206
+ /**
1207
+ * Compile a comparison whose right-hand side is another expression rather than
1208
+ * a bound value (`total > paid`, `lower(a) = lower(b)`).
1209
+ *
1210
+ * The list and null operators are excluded: `IN`, `BETWEEN` and `IS NULL` take
1211
+ * a value operand, and accepting an expression there would silently compile to
1212
+ * something else.
1213
+ *
1214
+ * @param left The rendered left-hand side.
1215
+ * @param op The operator name.
1216
+ * @param right The rendered right-hand side.
1217
+ * @returns The SQL text of the predicate.
1218
+ * @throws Error When the operator needs a value operand.
1219
+ */
1220
+ private compileExprOperator;
669
1221
  private compileOperator;
1222
+ /**
1223
+ * Compile `IN` / `NOT IN`, whose operand is either a value list or a
1224
+ * single-column subquery.
1225
+ *
1226
+ * The subquery is rendered at the position it appears in the outer statement
1227
+ * and shares the same parameter collector, so its own placeholders land in the
1228
+ * right order — and it keeps its own `names` map, since the inner model may use
1229
+ * a different naming convention than the outer one.
1230
+ *
1231
+ * @param id The quoted column identifier being tested.
1232
+ * @param operand A list of values, or a {@link Subquery}.
1233
+ * @param params The parameter collector for the statement being compiled.
1234
+ * @param negate True for `NOT IN`.
1235
+ * @returns The SQL text of the predicate.
1236
+ */
670
1237
  private compileIn;
671
1238
  }
672
1239
  /** SQLite dialect: `?` placeholders; `ILIKE` falls back to `LIKE` (ASCII-insensitive). */
@@ -674,12 +1241,19 @@ declare class SqliteDialect extends BaseDialect {
674
1241
  readonly name: "sqlite";
675
1242
  protected placeholder(): string;
676
1243
  protected ilike(column: string, param: string): string;
1244
+ /**
1245
+ * SQLite has no row-level locking, so a lock request is an error rather than a
1246
+ * silently unlocked `SELECT` — a lock that does not exist only shows up as
1247
+ * duplicated work under production concurrency.
1248
+ */
1249
+ protected renderLock(): string;
677
1250
  }
678
- /** PostgreSQL dialect: `$1` placeholders; native `ILIKE`. */
1251
+ /** PostgreSQL dialect: `$1` placeholders; native `ILIKE`; native array operators. */
679
1252
  declare class PostgresDialect extends BaseDialect {
680
1253
  readonly name: "postgresql";
681
1254
  protected placeholder(index: number): string;
682
1255
  protected ilike(column: string, param: string): string;
1256
+ protected arrayOperator(op: "contains" | "containedBy" | "overlaps"): string;
683
1257
  }
684
1258
  /**
685
1259
  * MySQL dialect: `?` placeholders, backtick identifiers, `ON DUPLICATE KEY
@@ -691,7 +1265,23 @@ declare class MysqlDialect extends BaseDialect {
691
1265
  protected placeholder(): string;
692
1266
  protected ilike(column: string, param: string): string;
693
1267
  protected quoteId(name: string): string;
694
- protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextPlaceholder: () => string): string;
1268
+ /**
1269
+ * MySQL rejects `LIMIT` inside an `IN` subquery with
1270
+ * `ER_NOT_SUPPORTED_YET: This version of MySQL doesn't yet support
1271
+ * 'LIMIT & IN/ALL/ANY/SOME subquery'`. Failing at compile time names the fix
1272
+ * instead of surfacing that error from the driver at runtime.
1273
+ */
1274
+ protected checkSubquery(node: SelectNode): void;
1275
+ protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextValue: () => string, names: NameMap | undefined): string;
1276
+ /**
1277
+ * MySQL has no `RETURNING`, so it cannot be compiled into a statement.
1278
+ *
1279
+ * `session.execute()` still honors `.returning()` on a **single-row INSERT** by
1280
+ * running the insert and reading the row back by key on the same connection —
1281
+ * that is execution, not compilation, so it never reaches here. Compiling a
1282
+ * node with `returning` directly is an error, rather than SQL that silently
1283
+ * returns nothing.
1284
+ */
695
1285
  protected compileReturning(returning: readonly string[] | "*" | null): string;
696
1286
  }
697
1287
  /** Get a dialect instance by name. */
@@ -749,7 +1339,7 @@ declare class NodeSqliteDriver implements SyncDriver {
749
1339
  iterate(sql: string, params: readonly unknown[]): IterableIterator<Record<string, unknown>>;
750
1340
  close(): void;
751
1341
  }
752
- type AnySelect = SelectBuilder<any, any>;
1342
+ type AnySelect = SelectBuilder<any, any, any>;
753
1343
  type AnyInsert = InsertBuilder<any, any, any>;
754
1344
  type GuardedUpdate = UpdateBuilder<any, true, any>;
755
1345
  type GuardedDelete = DeleteBuilder<any, true, any>;
@@ -832,6 +1422,40 @@ declare class SyncSession {
832
1422
  logger?: QueryLogger | undefined);
833
1423
  /** Log, run, and error-wrap one raw statement. */
834
1424
  private exec;
1425
+ /**
1426
+ * Run a raw, parameterized SQL statement (synchronous) — the runtime counterpart of the
1427
+ * migrations' `Op.execute`.
1428
+ *
1429
+ * A query builder never covers all of SQL, and without an escape hatch a single
1430
+ * unsupported query forces a whole second database stack alongside this one. Use
1431
+ * it for what the builder cannot yet express, and keep everything else typed.
1432
+ *
1433
+ * The statement goes through the same path as a compiled one: it is logged via
1434
+ * `onQuery`, wrapped in {@link QueryExecutionError} on failure, and runs on the
1435
+ * reserved connection inside `transaction()`.
1436
+ *
1437
+ * @param sql The statement text. Placeholders only (`$1` / `?` per dialect) —
1438
+ * never interpolate a value into this string.
1439
+ * @param params The bound parameters, in placeholder order.
1440
+ * @param options Pass `as` to coerce the returned rows with a model's column
1441
+ * types (and its column-name mapping).
1442
+ * @returns The result view over the returned rows.
1443
+ * @throws Error When `params` is not an array — the guard against calling this
1444
+ * with an interpolated string and no parameters by mistake.
1445
+ *
1446
+ * @example
1447
+ * ```ts
1448
+ * const claimed = await session.raw<OutboundRow>(
1449
+ * `UPDATE outbound_messages SET status = 'sending'
1450
+ * WHERE id = ANY($1) RETURNING *`,
1451
+ * [ids],
1452
+ * { as: Outbound },
1453
+ * ).all();
1454
+ * ```
1455
+ */
1456
+ raw<Row = Record<string, unknown>>(sql: string, params?: readonly unknown[], options?: {
1457
+ readonly as?: ModelClass;
1458
+ }): SyncResult<Row>;
835
1459
  /** Compile, run, and coerce a builder into a result. */
836
1460
  execute<B extends Executable>(builder: B): SyncResult<RowOf<B>>;
837
1461
  /** Run `fn` inside a transaction: commit on success, rollback on throw. */
@@ -858,7 +1482,58 @@ declare class AsyncSession {
858
1482
  logger?: QueryLogger | undefined);
859
1483
  /** Log, run, and error-wrap one raw statement. */
860
1484
  private exec;
1485
+ /**
1486
+ * Run a raw, parameterized SQL statement — the runtime counterpart of the
1487
+ * migrations' `Op.execute`.
1488
+ *
1489
+ * A query builder never covers all of SQL, and without an escape hatch a single
1490
+ * unsupported query forces a whole second database stack alongside this one. Use
1491
+ * it for what the builder cannot yet express, and keep everything else typed.
1492
+ *
1493
+ * The statement goes through the same path as a compiled one: it is logged via
1494
+ * `onQuery`, wrapped in {@link QueryExecutionError} on failure, and runs on the
1495
+ * reserved connection inside `transaction()`.
1496
+ *
1497
+ * @param sql The statement text. Placeholders only (`$1` / `?` per dialect) —
1498
+ * never interpolate a value into this string.
1499
+ * @param params The bound parameters, in placeholder order.
1500
+ * @param options Pass `as` to coerce the returned rows with a model's column
1501
+ * types (and its column-name mapping).
1502
+ * @returns The result view over the returned rows.
1503
+ * @throws Error When `params` is not an array — the guard against calling this
1504
+ * with an interpolated string and no parameters by mistake.
1505
+ *
1506
+ * @example
1507
+ * ```ts
1508
+ * const claimed = await session.raw<OutboundRow>(
1509
+ * `UPDATE outbound_messages SET status = 'sending'
1510
+ * WHERE id = ANY($1) RETURNING *`,
1511
+ * [ids],
1512
+ * { as: Outbound },
1513
+ * ).all();
1514
+ * ```
1515
+ */
1516
+ raw<Row = Record<string, unknown>>(sql: string, params?: readonly unknown[], options?: {
1517
+ readonly as?: ModelClass;
1518
+ }): AsyncResult<Row>;
861
1519
  execute<B extends Executable>(builder: B): AsyncResult<RowOf<B>>;
1520
+ /**
1521
+ * Honor `.returning()` on a dialect without `RETURNING`, by inserting and then
1522
+ * reading the row back by key.
1523
+ *
1524
+ * Both statements must run on **one** connection, because `LAST_INSERT_ID()` is
1525
+ * per-connection: outside a transaction the pooled driver is reserved for the
1526
+ * pair; inside one, the session already holds a pinned connection (a reserved
1527
+ * driver exposes no `reserve`), so it runs there directly.
1528
+ *
1529
+ * @param builder The insert builder, for its source model.
1530
+ * @param node The insert AST, whose `returning` drives the read-back.
1531
+ * @returns The result view over the read-back row.
1532
+ * @throws Error When the insert writes more than one row — `LAST_INSERT_ID()`
1533
+ * identifies only the first, and the rest are consecutive only under some
1534
+ * auto-increment lock modes.
1535
+ */
1536
+ private insertAndReadBack;
862
1537
  /** Lazily iterate result rows. Uses driver streaming when available. */
863
1538
  stream<B extends Executable>(builder: B): AsyncIterableIterator<RowOf<B>>;
864
1539
  transaction<T>(fn: (tx: AsyncSession) => Promise<T>): Promise<T>;
@@ -911,6 +1586,18 @@ declare class AsyncEngine {
911
1586
  /** `await using engine = createEngine(...)` closes the pool when the scope exits. */
912
1587
  [Symbol.asyncDispose](): Promise<void>;
913
1588
  }
1589
+ /**
1590
+ * Adapt a sync **or** async driver to the async interface.
1591
+ *
1592
+ * `await` normalizes both: a sync driver returns a plain value, an async one a
1593
+ * promise, and awaiting either yields the result. That is what lets the migration
1594
+ * CLI take one code path instead of branching on a difference it cannot detect
1595
+ * from the object's shape.
1596
+ *
1597
+ * @param driver Either driver flavor.
1598
+ * @returns An async driver delegating to it.
1599
+ */
1600
+ declare function toAsyncDriver(driver: SyncDriver | AsyncDriver): AsyncDriver;
914
1601
  /**
915
1602
  * Create a **synchronous** engine from a database URL. SQLite only — PostgreSQL
916
1603
  * has no sane synchronous driver in Node, so a Postgres URL throws, pointing at
@@ -1168,7 +1855,7 @@ interface ColumnFlags {
1168
1855
  * generic types (e.g. `String` → varchar, `Text` → text). Dialect renderers
1169
1856
  * (Phase 4/6) map each kind + meta to concrete SQL per database.
1170
1857
  */
1171
- type ColumnTypeKind = "smallint" | "integer" | "bigint" | "numeric" | "real" | "double" | "varchar" | "text" | "char" | "boolean" | "date" | "time" | "datetime" | "timestamp" | "blob" | "json" | "uuid" | "enum";
1858
+ type ColumnTypeKind = "smallint" | "integer" | "bigint" | "numeric" | "real" | "double" | "varchar" | "text" | "char" | "boolean" | "date" | "time" | "datetime" | "timestamp" | "blob" | "json" | "uuid" | "enum" | "array";
1172
1859
  /** Parameters that refine a column type and feed the migration IR / DDL. */
1173
1860
  interface ColumnTypeMeta {
1174
1861
  /** Max length for `varchar`/`char`. */
@@ -1183,6 +1870,8 @@ interface ColumnTypeMeta {
1183
1870
  readonly values?: readonly string[] | undefined;
1184
1871
  /** Render as `JSONB` (PostgreSQL) instead of `JSON`. */
1185
1872
  readonly jsonb?: boolean | undefined;
1873
+ /** The element type of an `array` column (`text[]`, `integer[]`). */
1874
+ readonly element?: ColumnType | undefined;
1186
1875
  }
1187
1876
  /** A structured, dialect-neutral column type descriptor. */
1188
1877
  interface ColumnType {
@@ -1193,10 +1882,14 @@ interface ColumnType {
1193
1882
  * A portable default expression. The token is dialect-neutral; the renderer
1194
1883
  * (Phase 4/6) maps it to the right SQL per database — e.g. `"now"` becomes
1195
1884
  * `CURRENT_TIMESTAMP` on SQLite and `now()` on PostgreSQL. Use `{ raw }` as an
1196
- * escape hatch for a verbatim SQL fragment.
1885
+ * escape hatch for a verbatim SQL fragment, or `{ parts }` for a parameterized
1886
+ * fragment built by the `sql.expr` tagged template (one bound parameter per gap
1887
+ * between consecutive parts).
1197
1888
  */
1198
1889
  type PortableExpression = "now" | "current_date" | "current_time" | "uuidv4" | {
1199
1890
  readonly raw: string;
1891
+ } | {
1892
+ readonly parts: readonly string[];
1200
1893
  };
1201
1894
  /**
1202
1895
  * A column default. Either a constant literal value or a server-side expression
@@ -1210,18 +1903,79 @@ type DefaultValue = {
1210
1903
  readonly kind: "expression";
1211
1904
  readonly expression: PortableExpression;
1212
1905
  };
1213
- /** Portable server-side default expressions, à la SQLAlchemy's `func`. */
1906
+ /**
1907
+ * Brand marking a value as a SQL expression rather than a bound parameter.
1908
+ *
1909
+ * A plain object reaching `set()`/`values()` is a mistake (it would be bound as a
1910
+ * parameter and silently written as JSON or null); an object carrying this symbol
1911
+ * is deliberate, and the dialect renders it inline instead of binding it. Mirrors
1912
+ * how `Condition` is branded, so the check is a symbol lookup, not duck typing.
1913
+ */
1914
+ declare const EXPRESSION: unique symbol;
1915
+ /**
1916
+ * A SQL expression, usable both as a column default (`.default(sql.now())`) and
1917
+ * as a write value (`.set({ attempts: sql.raw("attempts + 1") })`). The dialect
1918
+ * renders `expression` inline and binds `params` in the order of the fragment's
1919
+ * gaps.
1920
+ */
1921
+ interface SqlExpression {
1922
+ readonly [EXPRESSION]: true;
1923
+ readonly kind: "expression";
1924
+ readonly expression: PortableExpression;
1925
+ /** Parameters bound into the fragment's gaps, in order (empty for a token). */
1926
+ readonly params: readonly unknown[];
1927
+ }
1928
+ /**
1929
+ * Runtime guard: is this value a branded {@link SqlExpression}?
1930
+ *
1931
+ * @param value Any value handed to `set()`, `values()` or `.default()`.
1932
+ * @returns True when the value carries the expression brand.
1933
+ */
1934
+ declare function isSqlExpression(value: unknown): value is SqlExpression;
1935
+ /**
1936
+ * Portable server-side expressions, à la SQLAlchemy's `func`.
1937
+ *
1938
+ * Every entry doubles as a column default and as a write value, so
1939
+ * `.default(sql.now())` and `.set({ updatedAt: sql.now() })` both work.
1940
+ */
1214
1941
  declare const sql: {
1215
1942
  /** Current timestamp at insert (`CURRENT_TIMESTAMP` / `now()`). */
1216
- readonly now: () => DefaultValue;
1943
+ readonly now: () => SqlExpression;
1217
1944
  /** Current date. */
1218
- readonly currentDate: () => DefaultValue;
1945
+ readonly currentDate: () => SqlExpression;
1219
1946
  /** Current time. */
1220
- readonly currentTime: () => DefaultValue;
1947
+ readonly currentTime: () => SqlExpression;
1221
1948
  /** A freshly generated UUID v4 (`gen_random_uuid()` / portable fallback). */
1222
- readonly uuidv4: () => DefaultValue;
1223
- /** Escape hatch: a verbatim SQL expression rendered as-is. */
1224
- readonly raw: (expression: string) => DefaultValue;
1949
+ readonly uuidv4: () => SqlExpression;
1950
+ /**
1951
+ * Escape hatch: a verbatim SQL expression rendered as-is, with no parameters.
1952
+ *
1953
+ * The fragment is interpolated into the statement untouched, so it must never
1954
+ * carry user input — use {@link sql.expr} when a value has to be bound.
1955
+ *
1956
+ * @param fragment The SQL text (e.g. `"attempts + 1"`).
1957
+ * @returns The expression, usable as a default and as a write value.
1958
+ */
1959
+ readonly raw: (fragment: string) => SqlExpression;
1960
+ /**
1961
+ * A parameterized SQL expression, written as a tagged template. Static text is
1962
+ * SQL; every `${...}` interpolation becomes a bound parameter, so the fragment
1963
+ * is injection-safe by construction.
1964
+ *
1965
+ * Cannot be used as a column default — a `DEFAULT` clause has nowhere to bind
1966
+ * parameters; use {@link sql.raw} there.
1967
+ *
1968
+ * @param parts The static SQL segments supplied by the template tag.
1969
+ * @param values The interpolated values, bound in order.
1970
+ * @returns The expression, usable as a write value.
1971
+ *
1972
+ * @example
1973
+ * ```ts
1974
+ * update(Account).set({ balance: sql.expr`balance - ${amount}` }).where({ id });
1975
+ * // UPDATE "accounts" SET "balance" = balance - $1 WHERE "id" = $2
1976
+ * ```
1977
+ */
1978
+ readonly expr: (parts: TemplateStringsArray, ...values: unknown[]) => SqlExpression;
1225
1979
  };
1226
1980
  /**
1227
1981
  * A referential action for a foreign key's `ON DELETE` / `ON UPDATE` clause.
@@ -1258,6 +2012,8 @@ declare class Column<T, F extends ColumnFlags = ColumnFlags> {
1258
2012
  readonly onUpdateValue: DefaultValue | null;
1259
2013
  /** The foreign-key reference this column points to, or `null` for none. */
1260
2014
  readonly reference: ForeignKeyRef | null;
2015
+ /** An explicit database column name overriding the property name, or `null`. */
2016
+ readonly dbName: string | null;
1261
2017
  /** Phantom: never read at runtime, only inspected by the type system. */
1262
2018
  readonly [TYPE]: T;
1263
2019
  constructor(type: ColumnType, flags: F,
@@ -1266,7 +2022,11 @@ declare class Column<T, F extends ColumnFlags = ColumnFlags> {
1266
2022
  /** The value re-applied on update (e.g. `updated_at`), or `null`. */
1267
2023
  onUpdateValue?: DefaultValue | null,
1268
2024
  /** The foreign-key reference this column points to, or `null` for none. */
1269
- reference?: ForeignKeyRef | null);
2025
+ reference?: ForeignKeyRef | null,
2026
+ /** An explicit database column name overriding the property name, or `null`. */
2027
+ dbName?: string | null);
2028
+ /** Clone this column with one facet replaced, carrying every other over. */
2029
+ private derive;
1270
2030
  primaryKey(): Column<T, F & {
1271
2031
  primaryKey: true;
1272
2032
  hasDefault: true;
@@ -1281,6 +2041,29 @@ declare class Column<T, F extends ColumnFlags = ColumnFlags> {
1281
2041
  unique(): Column<T, F & {
1282
2042
  unique: true;
1283
2043
  }>;
2044
+ /**
2045
+ * Map this property to a differently-named database column, à la SQLAlchemy's
2046
+ * `mapped_column("consumer_name")` (Django's `db_column`, Prisma's `@map`).
2047
+ *
2048
+ * The override applies everywhere the name reaches SQL — select, insert,
2049
+ * update, delete, where, order by, group by, returning, conflict targets, the
2050
+ * migration IR and the drift check — while the TypeScript row keeps the
2051
+ * property name. Use it to keep a `snake_case` schema behind a `camelCase`
2052
+ * model; {@link Model.naming} does the same for a whole table at once.
2053
+ *
2054
+ * @param dbName The real column name in the database.
2055
+ * @returns A new column bound to that name.
2056
+ * @throws Error When `dbName` is empty.
2057
+ *
2058
+ * @example
2059
+ * ```ts
2060
+ * class ApiKey extends Model {
2061
+ * static tablename = "api_keys";
2062
+ * consumerName = column.text().name("consumer_name").notNull();
2063
+ * }
2064
+ * ```
2065
+ */
2066
+ name(dbName: string): Column<T, F>;
1284
2067
  /**
1285
2068
  * Declare a foreign-key reference to another table's column, à la SQLAlchemy's
1286
2069
  * `mapped_column(ForeignKey("table.column", ondelete=...))`. DDL-only — does
@@ -1295,6 +2078,11 @@ declare class Column<T, F extends ColumnFlags = ColumnFlags> {
1295
2078
  /**
1296
2079
  * Set the insert-time default: a constant value of type `T`, or a portable
1297
2080
  * server-side expression from {@link sql} (e.g. `sql.now()`, `sql.uuidv4()`).
2081
+ *
2082
+ * @param value The literal default, or a {@link sql} expression.
2083
+ * @returns A new column carrying the default.
2084
+ * @throws Error When given a `sql.expr` fragment — a `DEFAULT` clause has
2085
+ * nowhere to bind parameters; use `sql.raw()` for a verbatim expression.
1298
2086
  */
1299
2087
  default(value: T | DefaultValue): Column<T, F & {
1300
2088
  hasDefault: true;
@@ -1302,6 +2090,10 @@ declare class Column<T, F extends ColumnFlags = ColumnFlags> {
1302
2090
  /**
1303
2091
  * Re-apply a value whenever the row is updated (e.g. an `updated_at` column
1304
2092
  * with `sql.now()`). Mirrors SQLAlchemy's `onupdate`.
2093
+ *
2094
+ * @param value The literal value, or a {@link sql} expression.
2095
+ * @returns A new column carrying the on-update value.
2096
+ * @throws Error When given a `sql.expr` fragment (see {@link Column.default}).
1305
2097
  */
1306
2098
  onUpdate(value: T | DefaultValue): Column<T, F>;
1307
2099
  }
@@ -1370,6 +2162,26 @@ declare const column: {
1370
2162
  readonly uuid: () => Column<string, ColumnFlags>;
1371
2163
  /** `ENUM(...values)` → a string-literal union of the given values. */
1372
2164
  readonly enum: <const E extends string>(...values: E[]) => Column<E, ColumnFlags>;
2165
+ /**
2166
+ * A PostgreSQL array column (`text[]`, `integer[]`) → `T[]`.
2167
+ *
2168
+ * PostgreSQL only: SQLite and MySQL have no native array type, and rendering
2169
+ * one as JSON there would give the same model different semantics per dialect
2170
+ * (`@>` and `&&` work on one and not the other), so the DDL renderer throws
2171
+ * for those dialects instead of falling back silently.
2172
+ *
2173
+ * @param element The element column (its type, not its flags, is what is used).
2174
+ * @returns A column whose inferred type is an array of the element's type.
2175
+ *
2176
+ * @example
2177
+ * ```ts
2178
+ * class ApiKey extends Model {
2179
+ * static tablename = "api_keys";
2180
+ * scopes = column.array(column.text()).notNull().default(["send"]);
2181
+ * }
2182
+ * ```
2183
+ */
2184
+ readonly array: <T>(element: Column<T, ColumnFlags>) => Column<T[], ColumnFlags>;
1373
2185
  };
1374
2186
  /**
1375
2187
  * A table-level constraint declared via a model's `static tableArgs`. Mirrors
@@ -1414,6 +2226,16 @@ declare function foreignKey(columns: string[], refTable: string, refColumns: str
1414
2226
  onDelete?: FkAction;
1415
2227
  onUpdate?: FkAction;
1416
2228
  }): TableConstraint;
2229
+ /**
2230
+ * How property names map to database column names when a column declares no
2231
+ * explicit {@link Column.name}.
2232
+ *
2233
+ * - `"preserve"` (default) — the column name is the property name, verbatim.
2234
+ * - `"snake_case"` — `consumerName` becomes `consumer_name`.
2235
+ */
2236
+ type NamingStrategy = "preserve" | "snake_case";
2237
+ /** Convert a `camelCase` / `PascalCase` identifier to `snake_case`. */
2238
+ declare function toSnakeCase(name: string): string;
1417
2239
  /** Base class every model extends, SQLAlchemy-declarative style. */
1418
2240
  declare abstract class Model {
1419
2241
  static tablename: string;
@@ -1423,6 +2245,12 @@ declare abstract class Model {
1423
2245
  * `__table_args__`.
1424
2246
  */
1425
2247
  static tableArgs?: () => readonly TableConstraint[];
2248
+ /**
2249
+ * How to derive column names from property names (default `"preserve"`). Set
2250
+ * `"snake_case"` to keep a `snake_case` schema behind a `camelCase` model
2251
+ * without annotating every column; {@link Column.name} overrides it per column.
2252
+ */
2253
+ static naming?: NamingStrategy;
1426
2254
  }
1427
2255
  /**
1428
2256
  * Reflect a model class into its column map at runtime, keyed by column name.
@@ -1438,6 +2266,32 @@ declare abstract class Model {
1438
2266
  * @returns A record of column name → `Column` instance (do not mutate).
1439
2267
  */
1440
2268
  declare function columnsOf(model: ModelClass): Record<string, Column<unknown>>;
2269
+ /** A mapping between property names and database column names. */
2270
+ type NameMap = Readonly<Record<string, string>>;
2271
+ /**
2272
+ * The property → database-column map for a model, or `null` when every column
2273
+ * keeps its property name.
2274
+ *
2275
+ * `null` is the common case and the fast path: builders and the row coercer skip
2276
+ * translation entirely, so a model that renames nothing costs nothing. The map
2277
+ * is memoized per class, like {@link columnsOf}.
2278
+ *
2279
+ * @param model The model class.
2280
+ * @returns The name map, or `null` when no column is renamed.
2281
+ * @throws Error When two properties resolve to the same column name.
2282
+ */
2283
+ declare function columnNamesOf(model: ModelClass): NameMap | null;
2284
+ /**
2285
+ * The database-column → property map for a model, or `null` when every column
2286
+ * keeps its property name. The inverse of {@link columnNamesOf}, used to map
2287
+ * driver rows back into property space.
2288
+ *
2289
+ * @param model The model class.
2290
+ * @returns The inverse name map, or `null` when no column is renamed.
2291
+ */
2292
+ declare function columnPropsOf(model: ModelClass): NameMap | null;
2293
+ /** Resolve one property name to its database column name. */
2294
+ declare function dbColumn(names: NameMap | null | undefined, prop: string): string;
1441
2295
  /** Pull the static type out of a Column. */
1442
2296
  type ColType<C> = C extends Column<infer T, infer _F> ? T : never;
1443
2297
  /** Keys of the model instance whose values are Columns. */
@@ -1448,6 +2302,7 @@ type ColumnKeys<M> = {
1448
2302
  type ModelClass = (new () => Model) & {
1449
2303
  tablename: string;
1450
2304
  tableArgs?: () => readonly TableConstraint[];
2305
+ naming?: NamingStrategy;
1451
2306
  };
1452
2307
  /** Flatten an intersection into a single object literal for clean inference. */
1453
2308
  type Simplify<T> = {
@@ -1485,4 +2340,4 @@ type InferInsert<C extends ModelClass> = Simplify<{
1485
2340
  [K in Exclude<ColumnKeys<InstanceType<C>>, OptionalInsertKeys<InstanceType<C>>>]: ColValue<InstanceType<C>[K]>;
1486
2341
  }>;
1487
2342
 
1488
- export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type FkAction, type ForeignKeyOptions, type ForeignKeyRef, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, Model, type ModelClass, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, type OnConflict, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, SqliteDialect, type SyncDriver, SyncEngine, SyncResult, SyncSession, type TableConstraint, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, foreignKey, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, unique, update };
2343
+ export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type ExprNode, Expression, type FkAction, type ForeignKeyOptions, type ForeignKeyRef, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, type LockClause, type LockOptions, Model, type ModelClass, MysqlDialect, type NameMap, type NamingStrategy, NoResultError, NodeSqliteDriver, OPERATORS, type OnConflict, type OnConflictOptions, type OnConflictUpdateOptions, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, Params, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, type SqlExpression, SqliteDialect, type Subquery, type SyncDriver, SyncEngine, SyncResult, SyncSession, type TableConstraint, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, type WritePatch, type WriteValues, activeRecord, and, avg, belongsTo, col, column, columnNamesOf, columnPropsOf, columnsOf, count, createEngine, createSyncEngine, dbColumn, del, detectDialect, fn, foreignKey, fromDict, getDialect, hasMany, insert, isCondition, isExpression, isSqlExpression, isSubquery, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toAsyncDriver, toCondNode, toDict, toJSON, toSnakeCase, unique, update, val };