tempest-db-js 0.5.0 → 0.7.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
@@ -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`. */
@@ -398,7 +641,8 @@ declare class InsertBuilder<Full, Ins, Ret = number> {
398
641
  * @param rows One row, or an array of rows.
399
642
  * @returns A builder carrying the rows.
400
643
  * @throws ValidationError When a value is not a column value the dialect can
401
- * bind (see the `sql` helpers for writing an expression instead).
644
+ * bind (see the `sql` helpers for writing an expression instead), or when the
645
+ * rows of a multi-row insert disagree about a column that has a default.
402
646
  */
403
647
  values(rows: WriteValues<Ins> | readonly WriteValues<Ins>[]): InsertBuilder<Full, Ins, Ret>;
404
648
  /**
@@ -805,6 +1049,14 @@ declare abstract class BaseDialect {
805
1049
  protected abstract placeholder(index: number): string;
806
1050
  /** Render a case-insensitive LIKE for the active dialect. */
807
1051
  protected abstract ilike(column: string, param: string): string;
1052
+ /**
1053
+ * Validate a subquery operand before it is rendered, for dialects that restrict
1054
+ * what an `IN (SELECT ...)` may contain. The default accepts everything.
1055
+ *
1056
+ * @param _node The subquery's AST.
1057
+ * @throws Error When the dialect cannot execute this subquery.
1058
+ */
1059
+ protected checkSubquery(_node: SelectNode): void;
808
1060
  /**
809
1061
  * The SQL operator for an array containment/overlap test.
810
1062
  *
@@ -871,6 +1123,19 @@ declare abstract class BaseDialect {
871
1123
  * @returns The SQL text, leading space included.
872
1124
  */
873
1125
  protected renderLock(lock: LockClause): string;
1126
+ /**
1127
+ * Compile a SELECT.
1128
+ *
1129
+ * Two alias rules differ between clauses and are handled here: PostgreSQL does
1130
+ * NOT accept a `SELECT` alias in `HAVING`, so an aggregate key is re-emitted as
1131
+ * its expression (`COUNT(*) > $1`), a form every dialect accepts; `ORDER BY`,
1132
+ * by contrast, accepts the output alias everywhere, so it is emitted as
1133
+ * written.
1134
+ *
1135
+ * @param node The select AST.
1136
+ * @param params The parameter collector.
1137
+ * @returns The SQL text.
1138
+ */
874
1139
  private compileSelect;
875
1140
  /**
876
1141
  * Compile an INSERT.
@@ -926,7 +1191,50 @@ declare abstract class BaseDialect {
926
1191
  * so select/update/delete/join all share this one compiler.
927
1192
  */
928
1193
  private compileCondition;
1194
+ /**
1195
+ * Render one side of a comparison.
1196
+ *
1197
+ * A column reference goes through `idFor`, so an explicit `.name()` mapping and
1198
+ * join qualification apply here exactly as they do in the object form of
1199
+ * `where` — `col()` is not a way around them. Only a `value` node binds.
1200
+ *
1201
+ * @param node The expression AST.
1202
+ * @param params The parameter collector.
1203
+ * @param idFor The identifier resolver for the enclosing statement.
1204
+ * @returns The SQL text of the expression.
1205
+ */
1206
+ private renderExpr;
1207
+ /**
1208
+ * Compile a comparison whose right-hand side is another expression rather than
1209
+ * a bound value (`total > paid`, `lower(a) = lower(b)`).
1210
+ *
1211
+ * The list and null operators are excluded: `IN`, `BETWEEN` and `IS NULL` take
1212
+ * a value operand, and accepting an expression there would silently compile to
1213
+ * something else.
1214
+ *
1215
+ * @param left The rendered left-hand side.
1216
+ * @param op The operator name.
1217
+ * @param right The rendered right-hand side.
1218
+ * @returns The SQL text of the predicate.
1219
+ * @throws Error When the operator needs a value operand.
1220
+ */
1221
+ private compileExprOperator;
929
1222
  private compileOperator;
1223
+ /**
1224
+ * Compile `IN` / `NOT IN`, whose operand is either a value list or a
1225
+ * single-column subquery.
1226
+ *
1227
+ * The subquery is rendered at the position it appears in the outer statement
1228
+ * and shares the same parameter collector, so its own placeholders land in the
1229
+ * right order — and it keeps its own `names` map, since the inner model may use
1230
+ * a different naming convention than the outer one.
1231
+ *
1232
+ * @param id The quoted column identifier being tested.
1233
+ * @param operand A list of values, or a {@link Subquery}.
1234
+ * @param params The parameter collector for the statement being compiled.
1235
+ * @param negate True for `NOT IN`.
1236
+ * @returns The SQL text of the predicate.
1237
+ */
930
1238
  private compileIn;
931
1239
  }
932
1240
  /** SQLite dialect: `?` placeholders; `ILIKE` falls back to `LIKE` (ASCII-insensitive). */
@@ -958,7 +1266,23 @@ declare class MysqlDialect extends BaseDialect {
958
1266
  protected placeholder(): string;
959
1267
  protected ilike(column: string, param: string): string;
960
1268
  protected quoteId(name: string): string;
1269
+ /**
1270
+ * MySQL rejects `LIMIT` inside an `IN` subquery with
1271
+ * `ER_NOT_SUPPORTED_YET: This version of MySQL doesn't yet support
1272
+ * 'LIMIT & IN/ALL/ANY/SOME subquery'`. Failing at compile time names the fix
1273
+ * instead of surfacing that error from the driver at runtime.
1274
+ */
1275
+ protected checkSubquery(node: SelectNode): void;
961
1276
  protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextValue: () => string, names: NameMap | undefined): string;
1277
+ /**
1278
+ * MySQL has no `RETURNING`, so it cannot be compiled into a statement.
1279
+ *
1280
+ * `session.execute()` still honors `.returning()` on a **single-row INSERT** by
1281
+ * running the insert and reading the row back by key on the same connection —
1282
+ * that is execution, not compilation, so it never reaches here. Compiling a
1283
+ * node with `returning` directly is an error, rather than SQL that silently
1284
+ * returns nothing.
1285
+ */
962
1286
  protected compileReturning(returning: readonly string[] | "*" | null): string;
963
1287
  }
964
1288
  /** Get a dialect instance by name. */
@@ -1008,15 +1332,21 @@ declare class NodeSqliteDriver implements SyncDriver {
1008
1332
  */
1009
1333
  private readonly statements;
1010
1334
  constructor(database: any);
1011
- /** Open a `node:sqlite` database at the given path (or `:memory:`). */
1012
- static open(path: string): NodeSqliteDriver;
1335
+ /**
1336
+ * Open a `node:sqlite` database at the given path (or `:memory:`).
1337
+ *
1338
+ * @param path The database file, or `":memory:"`.
1339
+ * @param options Passed straight to `DatabaseSync` (`readOnly`, `timeout`, …).
1340
+ * @returns A driver over the open handle.
1341
+ */
1342
+ static open(path: string, options?: Readonly<Record<string, unknown>>): NodeSqliteDriver;
1013
1343
  /** Return the cached prepared statement for `sql`, preparing it on first use. */
1014
1344
  private prepare;
1015
1345
  execute(sql: string, params: readonly unknown[]): DriverResult;
1016
1346
  iterate(sql: string, params: readonly unknown[]): IterableIterator<Record<string, unknown>>;
1017
1347
  close(): void;
1018
1348
  }
1019
- type AnySelect = SelectBuilder<any, any>;
1349
+ type AnySelect = SelectBuilder<any, any, any>;
1020
1350
  type AnyInsert = InsertBuilder<any, any, any>;
1021
1351
  type GuardedUpdate = UpdateBuilder<any, true, any>;
1022
1352
  type GuardedDelete = DeleteBuilder<any, true, any>;
@@ -1194,6 +1524,23 @@ declare class AsyncSession {
1194
1524
  readonly as?: ModelClass;
1195
1525
  }): AsyncResult<Row>;
1196
1526
  execute<B extends Executable>(builder: B): AsyncResult<RowOf<B>>;
1527
+ /**
1528
+ * Honor `.returning()` on a dialect without `RETURNING`, by inserting and then
1529
+ * reading the row back by key.
1530
+ *
1531
+ * Both statements must run on **one** connection, because `LAST_INSERT_ID()` is
1532
+ * per-connection: outside a transaction the pooled driver is reserved for the
1533
+ * pair; inside one, the session already holds a pinned connection (a reserved
1534
+ * driver exposes no `reserve`), so it runs there directly.
1535
+ *
1536
+ * @param builder The insert builder, for its source model.
1537
+ * @param node The insert AST, whose `returning` drives the read-back.
1538
+ * @returns The result view over the read-back row.
1539
+ * @throws Error When the insert writes more than one row — `LAST_INSERT_ID()`
1540
+ * identifies only the first, and the rest are consecutive only under some
1541
+ * auto-increment lock modes.
1542
+ */
1543
+ private insertAndReadBack;
1197
1544
  /** Lazily iterate result rows. Uses driver streaming when available. */
1198
1545
  stream<B extends Executable>(builder: B): AsyncIterableIterator<RowOf<B>>;
1199
1546
  transaction<T>(fn: (tx: AsyncSession) => Promise<T>): Promise<T>;
@@ -1210,6 +1557,12 @@ interface PoolOptions {
1210
1557
  /** Give up acquiring a connection after this long (ms). */
1211
1558
  readonly connectTimeoutMs?: number;
1212
1559
  }
1560
+ /**
1561
+ * A server-side notice (a PostgreSQL `NOTICE`). The shape is the driver's own —
1562
+ * passed through untouched rather than normalized, since what is useful in it
1563
+ * differs per database.
1564
+ */
1565
+ type NoticeLogger = (notice: Record<string, unknown>) => void;
1213
1566
  /** Options shared by both engine flavors. */
1214
1567
  interface EngineOptions {
1215
1568
  /** Override the driver detected from the URL (e.g. `"better-sqlite3"`). */
@@ -1221,6 +1574,34 @@ interface EngineOptions {
1221
1574
  * query logging/tracing. Thrown errors are swallowed so it never breaks a query.
1222
1575
  */
1223
1576
  readonly onQuery?: QueryLogger;
1577
+ /**
1578
+ * Called for every server-side notice (`CREATE TABLE IF NOT EXISTS` on an
1579
+ * existing table, `DROP ... IF EXISTS` on a missing one, and so on).
1580
+ *
1581
+ * **Without this, notices are silenced.** postgres.js defaults to printing them
1582
+ * with `console.log`, which drops a nine-line object into the host service's
1583
+ * stdout in the middle of its structured log — on every boot, since a migration
1584
+ * runner is usually the first thing to run. Writing to the host's stdout is the
1585
+ * application's decision, not a library's, so the default is to say nothing and
1586
+ * let you route them:
1587
+ *
1588
+ * ```ts
1589
+ * createEngine(url, { onNotice: (n) => logger.debug({ pg: n }, "postgres notice") });
1590
+ * ```
1591
+ *
1592
+ * Thrown errors are swallowed, like `onQuery`.
1593
+ */
1594
+ readonly onNotice?: NoticeLogger;
1595
+ /**
1596
+ * Options passed straight to the underlying driver, applied **last** so they
1597
+ * win over everything this layer derives (`pool`, `onNotice`).
1598
+ *
1599
+ * The escape hatch for what the typed surface does not model and is not going
1600
+ * to — postgres.js `connection`/`types`/`transform`/`ssl`, mysql2's own
1601
+ * settings, `node:sqlite`'s `readOnly` — so a gap need not become a feature
1602
+ * request.
1603
+ */
1604
+ readonly driverOptions?: Readonly<Record<string, unknown>>;
1224
1605
  }
1225
1606
  /** A synchronous engine (SQLite only). */
1226
1607
  declare class SyncEngine {
@@ -1246,6 +1627,18 @@ declare class AsyncEngine {
1246
1627
  /** `await using engine = createEngine(...)` closes the pool when the scope exits. */
1247
1628
  [Symbol.asyncDispose](): Promise<void>;
1248
1629
  }
1630
+ /**
1631
+ * Adapt a sync **or** async driver to the async interface.
1632
+ *
1633
+ * `await` normalizes both: a sync driver returns a plain value, an async one a
1634
+ * promise, and awaiting either yields the result. That is what lets the migration
1635
+ * CLI take one code path instead of branching on a difference it cannot detect
1636
+ * from the object's shape.
1637
+ *
1638
+ * @param driver Either driver flavor.
1639
+ * @returns An async driver delegating to it.
1640
+ */
1641
+ declare function toAsyncDriver(driver: SyncDriver | AsyncDriver): AsyncDriver;
1249
1642
  /**
1250
1643
  * Create a **synchronous** engine from a database URL. SQLite only — PostgreSQL
1251
1644
  * has no sane synchronous driver in Node, so a Postgres URL throws, pointing at
@@ -1966,9 +2359,22 @@ type ColValue<Col> = Col extends Column<infer T, infer F> ? F extends {
1966
2359
  type HasDefault<Col> = Col extends Column<unknown, infer F> ? F extends {
1967
2360
  hasDefault: true;
1968
2361
  } ? true : false : false;
1969
- /** Keys of the model whose columns are optional on insert. */
2362
+ /** True when a column accepts NULL — neither `notNull` nor a primary key. */
2363
+ type IsNullable<Col> = Col extends Column<unknown, infer F> ? F extends {
2364
+ notNull: true;
2365
+ } | {
2366
+ primaryKey: true;
2367
+ } ? false : true : false;
2368
+ /**
2369
+ * Keys of the model whose columns may be omitted on insert.
2370
+ *
2371
+ * A column is optional when it has a default **or** when it accepts NULL: SQL
2372
+ * applies `NULL` to an omitted column that declares no other default, so
2373
+ * requiring the caller to write `note: null` adds noise that reads like a
2374
+ * deliberate decision to blank the column. Passing `null` explicitly still works.
2375
+ */
1970
2376
  type OptionalInsertKeys<I> = {
1971
- [K in ColumnKeys<I>]: HasDefault<I[K]> extends true ? K : never;
2377
+ [K in ColumnKeys<I>]: HasDefault<I[K]> extends true ? K : IsNullable<I[K]> extends true ? K : never;
1972
2378
  }[ColumnKeys<I>];
1973
2379
  /**
1974
2380
  * Infer the SELECT row shape from a model class: every column field becomes its
@@ -1979,7 +2385,9 @@ type InferModel<C extends ModelClass> = {
1979
2385
  [K in ColumnKeys<InstanceType<C>>]: ColValue<InstanceType<C>[K]>;
1980
2386
  };
1981
2387
  /**
1982
- * Infer the INSERT shape: columns with a default (or PK) are optional; the rest
2388
+ * Infer the INSERT shape: a column is optional when it has a default (or is a PK)
2389
+ * **or** when it is nullable — matching SQL, where an omitted column with no
2390
+ * `DEFAULT` clause is written as `NULL`. Only `notNull` columns without a default
1983
2391
  * are required. Nullability is preserved on both sides.
1984
2392
  */
1985
2393
  type InferInsert<C extends ModelClass> = Simplify<{
@@ -1988,4 +2396,4 @@ type InferInsert<C extends ModelClass> = Simplify<{
1988
2396
  [K in Exclude<ColumnKeys<InstanceType<C>>, OptionalInsertKeys<InstanceType<C>>>]: ColValue<InstanceType<C>[K]>;
1989
2397
  }>;
1990
2398
 
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 };
2399
+ 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, type NoticeLogger, 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 };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, Params, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, 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 } from './chunk-5QQMVTS5.js';
1
+ export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, Expression, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, Params, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, 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 } from './chunk-4AWUP7BM.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map