tempest-db-js 0.5.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.cts CHANGED
@@ -61,6 +61,8 @@ interface SelectNode {
61
61
  /** `GROUP BY` columns. */
62
62
  readonly groupBy: readonly string[];
63
63
  readonly where: CondNode | undefined;
64
+ /** `HAVING` condition, keyed by aggregate alias or grouped column. */
65
+ readonly having?: CondNode | undefined;
64
66
  readonly orderBy: readonly OrderTerm[];
65
67
  readonly limit: number | undefined;
66
68
  readonly offset: number | undefined;
@@ -69,16 +71,34 @@ interface SelectNode {
69
71
  /** Property → column map, or `undefined` when every name is the identity. */
70
72
  readonly names?: NameMap | undefined;
71
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>;
72
89
  /** Operators valid on every column type. */
73
90
  interface BaseOperators<T> {
74
91
  /** Equal to. */
75
92
  eq?: T;
76
93
  /** Not equal to. */
77
94
  ne?: T;
78
- /** One of the given values (`IN`). */
79
- in?: readonly T[];
80
- /** None of the given values (`NOT IN`). */
81
- 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>;
82
102
  /** `IS NULL` (true) / `IS NOT NULL` (false). */
83
103
  isNull?: boolean;
84
104
  }
@@ -174,20 +194,43 @@ type SimplifyProj<T> = {
174
194
  * @typeParam Full - the complete row type (constrains where/orderBy keys).
175
195
  * @typeParam Proj - the projected result type returned on execution.
176
196
  */
177
- declare class SelectBuilder<Full, Proj = Full> {
197
+ declare class SelectBuilder<Full, Proj = Full, Grouped extends boolean = false> {
178
198
  readonly node: SelectNode;
179
199
  /** The source model, used to coerce rows on execution. */
180
200
  readonly source: ModelClass;
181
201
  /** Phantom: the result element type, read only by the type system. */
182
202
  readonly __row: Proj;
203
+ /** Phantom: `true` once `aggregate()` has made `having()` meaningful. */
204
+ readonly __grouped: Grouped;
183
205
  constructor(node: SelectNode,
184
206
  /** The source model, used to coerce rows on execution. */
185
207
  source: ModelClass);
186
208
  private with;
187
209
  /** Add a WHERE filter: the object form (keys typed) or an `and`/`or`/`not`. */
188
- 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>;
189
232
  /** Emit `SELECT DISTINCT` — drop duplicate rows. */
190
- distinct(): SelectBuilder<Full, Proj>;
233
+ distinct(): SelectBuilder<Full, Proj, Grouped>;
191
234
  /**
192
235
  * Group by columns and compute aggregates. The result row is the grouped
193
236
  * columns (typed from the model) plus one field per aggregate alias.
@@ -206,13 +249,50 @@ declare class SelectBuilder<Full, Proj = Full> {
206
249
  */
207
250
  aggregate<K extends keyof Full & string, S extends Record<string, Agg<unknown>>>(groupBy: readonly K[], spec: S): SelectBuilder<Full, SimplifyProj<Pick<Full, K> & {
208
251
  [A in keyof S]: AggResult<S[A]>;
209
- }>>;
210
- /** Order by a column of `Full`. */
211
- 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>;
212
263
  /** Limit the number of rows. */
213
- limit(n: number): SelectBuilder<Full, Proj>;
264
+ limit(n: number): SelectBuilder<Full, Proj, Grouped>;
214
265
  /** Skip the first `n` rows. */
215
- 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]>;
216
296
  /**
217
297
  * Lock the selected rows for update (`SELECT ... FOR UPDATE`), à la
218
298
  * SQLAlchemy's `with_for_update()`.
@@ -241,7 +321,7 @@ declare class SelectBuilder<Full, Proj = Full> {
241
321
  * ).all();
242
322
  * ```
243
323
  */
244
- forUpdate(options?: LockOptions): SelectBuilder<Full, Proj>;
324
+ forUpdate(options?: LockOptions): SelectBuilder<Full, Proj, Grouped>;
245
325
  /**
246
326
  * Take a shared read lock on the selected rows (`SELECT ... FOR SHARE`), the
247
327
  * weaker counterpart of {@link SelectBuilder.forUpdate}.
@@ -250,7 +330,7 @@ declare class SelectBuilder<Full, Proj = Full> {
250
330
  * @returns A builder carrying the locking clause.
251
331
  * @throws Error When both `skipLocked` and `noWait` are set.
252
332
  */
253
- forShare(options?: LockOptions): SelectBuilder<Full, Proj>;
333
+ forShare(options?: LockOptions): SelectBuilder<Full, Proj, Grouped>;
254
334
  }
255
335
  /** Build a SELECT over every column of the model. */
256
336
  declare function select<C extends ModelClass>(model: C): SelectBuilder<InferModel<C>, InferModel<C>>;
@@ -271,6 +351,25 @@ interface CondFields {
271
351
  readonly kind: "fields";
272
352
  readonly fields: Record<string, unknown>;
273
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
+ };
274
373
  /** Logical condition nodes. */
275
374
  type CondNode = CondFields | {
276
375
  readonly kind: "and";
@@ -281,6 +380,11 @@ type CondNode = CondFields | {
281
380
  } | {
282
381
  readonly kind: "not";
283
382
  readonly part: CondNode;
383
+ } | {
384
+ readonly kind: "compare";
385
+ readonly left: ExprNode;
386
+ readonly op: Operator;
387
+ readonly right: ExprNode;
284
388
  };
285
389
  declare const CONDITION: unique symbol;
286
390
  /** A composed condition produced by `and`/`or`/`not`. */
@@ -300,6 +404,145 @@ declare function toCondNode(input: Condition | Record<string, unknown>): CondNod
300
404
  * for full key + operator checking inside combinators.
301
405
  */
302
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
+ };
303
546
  /** Combine conditions with `AND`. */
304
547
  declare function and<Row = Record<string, unknown>>(...inputs: WhereArg<NoInfer<Row>>[]): Condition;
305
548
  /** Combine conditions with `OR`. */
@@ -805,6 +1048,14 @@ declare abstract class BaseDialect {
805
1048
  protected abstract placeholder(index: number): string;
806
1049
  /** Render a case-insensitive LIKE for the active dialect. */
807
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;
808
1059
  /**
809
1060
  * The SQL operator for an array containment/overlap test.
810
1061
  *
@@ -871,6 +1122,19 @@ declare abstract class BaseDialect {
871
1122
  * @returns The SQL text, leading space included.
872
1123
  */
873
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
+ */
874
1138
  private compileSelect;
875
1139
  /**
876
1140
  * Compile an INSERT.
@@ -926,7 +1190,50 @@ declare abstract class BaseDialect {
926
1190
  * so select/update/delete/join all share this one compiler.
927
1191
  */
928
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;
929
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
+ */
930
1237
  private compileIn;
931
1238
  }
932
1239
  /** SQLite dialect: `?` placeholders; `ILIKE` falls back to `LIKE` (ASCII-insensitive). */
@@ -958,7 +1265,23 @@ declare class MysqlDialect extends BaseDialect {
958
1265
  protected placeholder(): string;
959
1266
  protected ilike(column: string, param: string): string;
960
1267
  protected quoteId(name: 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;
961
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
+ */
962
1285
  protected compileReturning(returning: readonly string[] | "*" | null): string;
963
1286
  }
964
1287
  /** Get a dialect instance by name. */
@@ -1016,7 +1339,7 @@ declare class NodeSqliteDriver implements SyncDriver {
1016
1339
  iterate(sql: string, params: readonly unknown[]): IterableIterator<Record<string, unknown>>;
1017
1340
  close(): void;
1018
1341
  }
1019
- type AnySelect = SelectBuilder<any, any>;
1342
+ type AnySelect = SelectBuilder<any, any, any>;
1020
1343
  type AnyInsert = InsertBuilder<any, any, any>;
1021
1344
  type GuardedUpdate = UpdateBuilder<any, true, any>;
1022
1345
  type GuardedDelete = DeleteBuilder<any, true, any>;
@@ -1194,6 +1517,23 @@ declare class AsyncSession {
1194
1517
  readonly as?: ModelClass;
1195
1518
  }): AsyncResult<Row>;
1196
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;
1197
1537
  /** Lazily iterate result rows. Uses driver streaming when available. */
1198
1538
  stream<B extends Executable>(builder: B): AsyncIterableIterator<RowOf<B>>;
1199
1539
  transaction<T>(fn: (tx: AsyncSession) => Promise<T>): Promise<T>;
@@ -1246,6 +1586,18 @@ declare class AsyncEngine {
1246
1586
  /** `await using engine = createEngine(...)` closes the pool when the scope exits. */
1247
1587
  [Symbol.asyncDispose](): Promise<void>;
1248
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;
1249
1601
  /**
1250
1602
  * Create a **synchronous** engine from a database URL. SQLite only — PostgreSQL
1251
1603
  * has no sane synchronous driver in Node, so a Postgres URL throws, pointing at
@@ -1988,4 +2340,4 @@ type InferInsert<C extends ModelClass> = Simplify<{
1988
2340
  [K in Exclude<ColumnKeys<InstanceType<C>>, OptionalInsertKeys<InstanceType<C>>>]: ColValue<InstanceType<C>[K]>;
1989
2341
  }>;
1990
2342
 
1991
- 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, 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 SyncDriver, SyncEngine, SyncResult, SyncSession, type TableConstraint, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, type WritePatch, type WriteValues, activeRecord, and, avg, belongsTo, column, columnNamesOf, columnPropsOf, columnsOf, count, createEngine, createSyncEngine, dbColumn, del, detectDialect, foreignKey, fromDict, getDialect, hasMany, insert, isCondition, isSqlExpression, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, toSnakeCase, 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 };