@bakery-framework/orm 2.0.0-alpha.9 → 2.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/orm/query.ts CHANGED
@@ -2,18 +2,7 @@ import { Case } from '@bakery-framework/core/utils'
2
2
  import { throws } from '@bakery-framework/core/utils/common'
3
3
  import { getActiveDb, txStorage } from '../connection'
4
4
  import type { AppDBSchema as DBSchema } from '../schema-registry'
5
- import {
6
- evalOperands,
7
- isSafeIdentifier,
8
- qId,
9
- qRaw,
10
- SQL_FUNCTIONS,
11
- col as schemaCol,
12
- ColumnRef as schemaColumnRef,
13
- OperatorRef as schemaOperatorRef,
14
- SQLFunctionRef as schemaSQLFunctionRef,
15
- WindowRef as schemaWindowRef,
16
- } from '../schema-util'
5
+ import { ColumnRef as schemaColumnRef, OperatorRef as schemaOperatorRef, SQLFunctionRef as schemaSQLFunctionRef, SQL_FUNCTIONS, WindowRef as schemaWindowRef, col as schemaCol, evalOperands, isSafeIdentifier, nullComparison, qId, qRaw } from '../schema-util'
17
6
  import { Mutation } from './mutation'
18
7
 
19
8
  export namespace DB {
@@ -22,7 +11,7 @@ export namespace DB {
22
11
  /**
23
12
  * Bound a query that only needs its first row. `.get()` otherwise runs the
24
13
  * full query and materializes every row into JS objects before discarding all
25
- * but one — `first()` on an unfiltered table scanned the whole table.
14
+ * but one: `first()` on an unfiltered table scanned the whole table.
26
15
  */
27
16
  function singleRow(sql: string): string {
28
17
  return /\bLIMIT\s+\d+/i.test(sql) ? sql : `${sql} LIMIT 1`
@@ -135,7 +124,7 @@ export namespace DB {
135
124
  * `S`. That read as "columns of the schema in scope" and is not what it
136
125
  * means: the right-hand side of a join names a table you are *adding*, so
137
126
  * it cannot already be in scope. The parameter was ignored in the body,
138
- * so every call site got this answer anyway — it only misled the reader.
127
+ * so every call site got this answer anyway: it only misled the reader.
139
128
  */
140
129
  export type AllTableColumns = {
141
130
  [T in keyof DBSchema]: `${T & string}.${Extract<keyof DBSchema[T] & string, string>}`
@@ -174,12 +163,12 @@ export namespace DB {
174
163
  schemaSQLFunctionRef<C>
175
164
 
176
165
  /**
177
- * `COUNT`, `SUM` and `AVG` also come in a `.distinct` form —
166
+ * `COUNT`, `SUM` and `AVG` also come in a `.distinct` form:
178
167
  * `DB.count.distinct('users.city')` emits `COUNT(DISTINCT "users"."city")`.
179
168
  *
180
169
  * Only these three. `DISTINCT` is legal inside `MIN`/`MAX` on every dialect
181
170
  * and cannot change their result, so offering it there would imply an effect
182
- * that does not exist — the same reason the builder has no per-column
171
+ * that does not exist: the same reason the builder has no per-column
183
172
  * `distinct('col')`.
184
173
  */
185
174
  const aggregate = <N extends string>(fnName: N) =>
@@ -228,7 +217,7 @@ export namespace DB {
228
217
  /**
229
218
  * `'users.score'` or `'users.score DESC'`. The direction is optional and
230
219
  * per column, which is why it is spelled inside the string rather than as
231
- * a separate field — a window frequently orders by two columns in opposite
220
+ * a separate field: a window frequently orders by two columns in opposite
232
221
  * directions, and one shared flag could not express that.
233
222
  */
234
223
  orderBy?: string | string[]
@@ -245,7 +234,7 @@ export namespace DB {
245
234
 
246
235
  const order = list(spec.orderBy).map(entry => {
247
236
  // Split the direction off the tail rather than taking a separate
248
- // argument — `safeColumn` would reject 'score DESC' as an identifier, so
237
+ // argument: `safeColumn` would reject 'score DESC' as an identifier, so
249
238
  // the two halves have to be validated apart from each other anyway.
250
239
  const m = /^(.*?)\s+(ASC|DESC)$/i.exec(String(entry).trim())
251
240
  if (!m) return `${safeColumn(String(entry).trim())} ASC`
@@ -259,7 +248,7 @@ export namespace DB {
259
248
  /**
260
249
  * An aggregate over a window: `SUM("total") OVER (PARTITION BY "user_id")`.
261
250
  *
262
- * ```ts no-check — illustrative
251
+ * ```ts no-check, illustrative
263
252
  * DB.from('orders').select({
264
253
  * runningTotal: DB.over(DB.sum('orders.total'), {
265
254
  * partitionBy: 'orders.userId',
@@ -269,7 +258,7 @@ export namespace DB {
269
258
  * ```
270
259
  *
271
260
  * Takes an existing function ref rather than a column, so every aggregate the
272
- * builder already has — including `DB.count.distinct(…)` — composes with a
261
+ * builder already has, including `DB.count.distinct(…)`, composes with a
273
262
  * window without a second set of wrappers.
274
263
  */
275
264
  export function over(
@@ -280,7 +269,7 @@ export namespace DB {
280
269
  }
281
270
 
282
271
  /**
283
- * The ranking functions, which take no column — the window *is* the argument.
272
+ * The ranking functions, which take no column: the window *is* the argument.
284
273
  *
285
274
  * Only these three are wrapped by name. The rest of `WINDOW_FUNCTIONS`
286
275
  * (`LAG`, `NTILE`, `FIRST_VALUE`, …) take arguments whose meaning differs per
@@ -299,12 +288,12 @@ export namespace DB {
299
288
  /**
300
289
  * Any window function by name, with its own arguments.
301
290
  *
302
- * ```ts no-check — illustrative
291
+ * ```ts no-check, illustrative
303
292
  * DB.window('LAG', ['orders.total', 1], { orderBy: 'orders.createdAt' })
304
293
  * ```
305
294
  *
306
295
  * The name is checked against `WINDOW_FUNCTIONS` (plus the aggregates) at
307
- * parse time — it is interpolated, not bound. Arguments go through
296
+ * parse time. It is interpolated, not bound. Arguments go through
308
297
  * `evalOperands`, so a bare string binds as a parameter and `DB.col('x')`
309
298
  * references a column, exactly as everywhere else in the builder.
310
299
  */
@@ -384,10 +373,10 @@ export namespace DB {
384
373
  export type SelectValue<S extends TableSchemas, J extends string> =
385
374
  | ColumnString<S, J>
386
375
  | SQLFunctionRef<ColumnString<S, J> | '*'>
387
- // Unparameterised: a window's columns are validated at construction by
376
+ // Unparameterized: a window's columns are validated at construction by
388
377
  // `safeColumn`, not by the select's column union. Threading `S`/`J` through
389
378
  // would mean typing the spec against the same table set, which reads well
390
- // until a window orders by a *select alias* — legal SQL, and not a column
379
+ // until a window orders by a *select alias*: legal SQL, and not a column
391
380
  // of any table in scope.
392
381
  | WindowRef
393
382
  | QBRaw
@@ -416,7 +405,7 @@ export namespace DB {
416
405
  /**
417
406
  * Memo for `Case.camel` over result-row keys. Every row of every result set
418
407
  * is re-cased key by key, so a 1000-row × 8-column query ran `Case.camel`
419
- * 8000 times over the same eight strings — and `Case.camel` is two regex
408
+ * 8000 times over the same eight strings, and `Case.camel` is two regex
420
409
  * passes, one with a replacer callback.
421
410
  *
422
411
  * Capped `Map`, not `LRUCache`, for the reason given on `snakeCache` in
@@ -461,13 +450,13 @@ export namespace DB {
461
450
  abstract parse(): { sql: string; params: any[] }
462
451
 
463
452
  /**
464
- * `UNION` — every distinct row from this query and the next.
453
+ * `UNION`: every distinct row from this query and the next.
465
454
  *
466
455
  * Returns a {@link QBSet}, not `this`. That is the whole shape of the
467
456
  * feature: a compound select is not a `SELECT` with an extra clause, it is
468
457
  * a different kind of statement whose operands happen to be selects. So
469
- * `.where()` and `.select()` are gone from the result — they would have to
470
- * mean "on which branch?" — and what remains is what SQL allows after the
458
+ * `.where()` and `.select()` are gone from the result: they would have to
459
+ * mean "on which branch?", and what remains is what SQL allows after the
471
460
  * last operand: `orderBy`, `limit`, `offset`.
472
461
  */
473
462
  union<Q>(next: QBExecutable<Q>): QBSet<P> {
@@ -477,7 +466,7 @@ export namespace DB {
477
466
  ])
478
467
  }
479
468
 
480
- /** `UNION ALL` — as `union`, keeping duplicates. Cheaper: no dedupe pass. */
469
+ /** `UNION ALL`: as `union`, keeping duplicates. Cheaper: no dedupe pass. */
481
470
  unionAll<Q>(next: QBExecutable<Q>): QBSet<P> {
482
471
  return new QBSet<P>([
483
472
  { op: null, query: this },
@@ -485,7 +474,7 @@ export namespace DB {
485
474
  ])
486
475
  }
487
476
 
488
- /** `INTERSECT` — rows present in both. */
477
+ /** `INTERSECT`: rows present in both. */
489
478
  intersect<Q>(next: QBExecutable<Q>, all = false): QBSet<P> {
490
479
  return new QBSet<P>([
491
480
  { op: null, query: this },
@@ -493,7 +482,7 @@ export namespace DB {
493
482
  ])
494
483
  }
495
484
 
496
- /** `EXCEPT` — rows in this query that are not in the next. */
485
+ /** `EXCEPT`: rows in this query that are not in the next. */
497
486
  except<Q>(next: QBExecutable<Q>, all = false): QBSet<P> {
498
487
  return new QBSet<P>([
499
488
  { op: null, query: this },
@@ -501,6 +490,27 @@ export namespace DB {
501
490
  ])
502
491
  }
503
492
 
493
+ /**
494
+ * Walk the result a chunk at a time, holding only the chunk.
495
+ *
496
+ * **The name promises a cursor and the implementation is paging**, because
497
+ * Bun cannot stream: the statement becomes a derived table and is
498
+ * re-executed once per 500 rows. Two consequences, and the second is the
499
+ * one that surprises people.
500
+ *
501
+ * Chunk boundaries are only stable under a total order, so a row inserted
502
+ * or deleted mid-walk can be seen twice or missed. `seek()` is keyset
503
+ * pagination and has neither problem.
504
+ *
505
+ * And it is slow in proportion to how badly the ordering indexes.
506
+ * Measured on 100,000 rows: 3x `all()` on SQLite ordered by a primary key,
507
+ * **130x** ordered by a column with no index, 35.7 seconds against
508
+ * 275 ms. Postgres pays a round trip per chunk instead and lands at 19x
509
+ * and 101x. The table is on `pagedIterate` in `adapters/base.ts`.
510
+ *
511
+ * Worth it when the result does not fit in memory. Not worth it merely
512
+ * because the result is large.
513
+ */
504
514
  async *iterable(): AsyncIterable<P> {
505
515
  const { sql, params } = this.parse()
506
516
  for await (const row of getActiveDb()
@@ -626,7 +636,7 @@ export namespace DB {
626
636
  >
627
637
 
628
638
  /**
629
- * `FULL OUTER JOIN`. Typed like the others, but **MySQL has none** — the
639
+ * `FULL OUTER JOIN`. Typed like the others, but **MySQL has none**: the
630
640
  * runtime refuses there, because a capability the compiler cannot see
631
641
  * cannot be expressed in this signature.
632
642
  */
@@ -663,19 +673,19 @@ export namespace DB {
663
673
  >
664
674
 
665
675
  /**
666
- * Ordering and paging are legal straight off the table — `DB.table('t')
667
- * .limit(10)` needs no where or select — and always worked at runtime.
676
+ * Ordering and paging are legal straight off the table: `DB.table('t')
677
+ * .limit(10)` needs no where or select, and always worked at runtime.
668
678
  * They were simply missing from this stage of the interface chain.
669
679
  */
670
680
  orderBy(
671
681
  colStr: keyof P | ColumnString<S, J>,
672
682
  direction?: 'ASC' | 'DESC',
673
683
  ): IQBOrderBy<S, J, P>
674
- /** `SELECT DISTINCT` — see the runtime method for the semantics. */
684
+ /** `SELECT DISTINCT`. See the runtime method for the semantics. */
675
685
  distinct(): this
676
686
  limit(count: number, offset?: number): IQBLimit<S, J, P>
677
687
  paginate(page: number, pageSize: number): IQBLimit<S, J, P>
678
- /** Cursor pagination — see the implementation for why it is not paginate(). */
688
+ /** Cursor pagination. See the implementation for why it is not paginate(). */
679
689
  seek(
680
690
  column: ColumnString<S, J>,
681
691
  cursor: unknown,
@@ -711,11 +721,11 @@ export namespace DB {
711
721
  colStr: keyof P | ColumnString<S, J>,
712
722
  direction?: 'ASC' | 'DESC',
713
723
  ): IQBOrderBy<S, J, P>
714
- /** `SELECT DISTINCT` — see the runtime method for the semantics. */
724
+ /** `SELECT DISTINCT`. See the runtime method for the semantics. */
715
725
  distinct(): this
716
726
  limit(count: number, offset?: number): IQBLimit<S, J, P>
717
727
  paginate(page: number, pageSize: number): IQBLimit<S, J, P>
718
- /** Cursor pagination — see the implementation for why it is not paginate(). */
728
+ /** Cursor pagination. See the implementation for why it is not paginate(). */
719
729
  seek(
720
730
  column: ColumnString<S, J>,
721
731
  cursor: unknown,
@@ -742,7 +752,7 @@ export namespace DB {
742
752
  valueOrRef?: WhereValue<ColumnString<S, J>>,
743
753
  ): IQBHaving<S, J, P>
744
754
  /**
745
- * `SELECT DISTINCT` — see the runtime method for the semantics.
755
+ * `SELECT DISTINCT`. See the runtime method for the semantics.
746
756
  *
747
757
  * Declared here too, unlike the sibling stages, because this one has no
748
758
  * `limit`: `distinct()` was added everywhere `limit` already appeared, and
@@ -766,11 +776,11 @@ export namespace DB {
766
776
  colStr: keyof P | ColumnString<S, J>,
767
777
  direction?: 'ASC' | 'DESC',
768
778
  ): IQBOrderBy<S, J, P>
769
- /** `SELECT DISTINCT` — see the runtime method for the semantics. */
779
+ /** `SELECT DISTINCT`. See the runtime method for the semantics. */
770
780
  distinct(): this
771
781
  limit(count: number, offset?: number): IQBLimit<S, J, P>
772
782
  paginate(page: number, pageSize: number): IQBLimit<S, J, P>
773
- /** Cursor pagination — see the implementation for why it is not paginate(). */
783
+ /** Cursor pagination. See the implementation for why it is not paginate(). */
774
784
  seek(
775
785
  column: ColumnString<S, J>,
776
786
  cursor: unknown,
@@ -782,8 +792,8 @@ export namespace DB {
782
792
  export interface IQBSelect<S extends TableSchemas, J extends string, P = any>
783
793
  extends QBObject<P> {
784
794
  /**
785
- * Grouping after selecting, which always worked at runtime — clauses are
786
- * assembled at `parse()`, so call order is irrelevant — and was simply
795
+ * Grouping after selecting, which always worked at runtime (clauses are
796
+ * assembled at `parse()`, so call order is irrelevant), and was simply
787
797
  * missing from this stage of the chain. The same gap `IQBTable` had for
788
798
  * `orderBy`/`limit`, found by writing the query that motivates it:
789
799
  * `.select({ n: DB.count.distinct(c) }).groupBy(x).having(…)`.
@@ -806,8 +816,8 @@ export namespace DB {
806
816
  ): IQBHaving<S, J, P>
807
817
 
808
818
  /**
809
- * Filtering after projection is ordinary builder usage — the clauses are
810
- * assembled, not emitted in call order — and worked at runtime already.
819
+ * Filtering after projection is ordinary builder usage (the clauses are
820
+ * assembled, not emitted in call order), and worked at runtime already.
811
821
  * The projection `P` is preserved, so the row type survives the call.
812
822
  */
813
823
  where(
@@ -827,11 +837,11 @@ export namespace DB {
827
837
  colStr: keyof P | ColumnString<S, J>,
828
838
  direction?: 'ASC' | 'DESC',
829
839
  ): IQBOrderBy<S, J, P>
830
- /** `SELECT DISTINCT` — see the runtime method for the semantics. */
840
+ /** `SELECT DISTINCT`. See the runtime method for the semantics. */
831
841
  distinct(): this
832
842
  limit(count: number, offset?: number): IQBLimit<S, J, P>
833
843
  paginate(page: number, pageSize: number): IQBLimit<S, J, P>
834
- /** Cursor pagination — see the implementation for why it is not paginate(). */
844
+ /** Cursor pagination. See the implementation for why it is not paginate(). */
835
845
  seek(
836
846
  column: ColumnString<S, J>,
837
847
  cursor: unknown,
@@ -846,11 +856,11 @@ export namespace DB {
846
856
  colStr: keyof P | ColumnString<S, J>,
847
857
  direction?: 'ASC' | 'DESC',
848
858
  ): IQBOrderBy<S, J, P>
849
- /** `SELECT DISTINCT` — see the runtime method for the semantics. */
859
+ /** `SELECT DISTINCT`. See the runtime method for the semantics. */
850
860
  distinct(): this
851
861
  limit(count: number, offset?: number): IQBLimit<S, J, P>
852
862
  paginate(page: number, pageSize: number): IQBLimit<S, J, P>
853
- /** Cursor pagination — see the implementation for why it is not paginate(). */
863
+ /** Cursor pagination. See the implementation for why it is not paginate(). */
854
864
  seek(
855
865
  column: ColumnString<S, J>,
856
866
  cursor: unknown,
@@ -861,11 +871,11 @@ export namespace DB {
861
871
 
862
872
  export interface IQBLimit<S extends TableSchemas, J extends string, P = any>
863
873
  extends QBObject<P> {
864
- /** `SELECT DISTINCT` — see the runtime method for the semantics. */
874
+ /** `SELECT DISTINCT`. See the runtime method for the semantics. */
865
875
  distinct(): this
866
876
  limit(count: number, offset?: number): IQBLimit<S, J, P>
867
877
  paginate(page: number, pageSize: number): IQBLimit<S, J, P>
868
- /** Cursor pagination — see the implementation for why it is not paginate(). */
878
+ /** Cursor pagination. See the implementation for why it is not paginate(). */
869
879
  seek(
870
880
  column: ColumnString<S, J>,
871
881
  cursor: unknown,
@@ -885,23 +895,23 @@ export namespace DB {
885
895
  /**
886
896
  * A compound select: two or more queries joined by `UNION` and friends.
887
897
  *
888
- * The emission rules below are not style choices — each one is the only form
898
+ * The emission rules below are not style choices: each one is the only form
889
899
  * all three dialects accept, measured against live servers rather than read
890
900
  * off a standard:
891
901
  *
892
- * - **Operands are bare, never parenthesised.** MySQL and Postgres take
893
- * `(SELECT …) UNION (SELECT …)`; **SQLite rejects it outright** — a
894
- * parenthesised select is not a legal operand of a compound there.
902
+ * - **Operands are bare, never parenthesized.** MySQL and Postgres take
903
+ * `(SELECT …) UNION (SELECT …)`; **SQLite rejects it outright**: a
904
+ * parenthesized select is not a legal operand of a compound there.
895
905
  * - **A branch carrying its own `ORDER BY`/`LIMIT` is wrapped as a derived
896
906
  * table** instead. `SELECT id FROM a LIMIT 2 UNION …` is a syntax error on
897
- * all three, the parenthesised fix works on two of them, and
907
+ * all three, the parenthesized fix works on two of them, and
898
908
  * `SELECT * FROM (SELECT id FROM a LIMIT 2) AS b0` works on all three. The
899
909
  * alias is required by MySQL and harmless elsewhere.
900
910
  * - **`ORDER BY` and `LIMIT` on the set go at the very end**, unwrapped,
901
911
  * where every dialect reads them as applying to the whole compound.
902
912
  * - **`INTERSECT ALL` / `EXCEPT ALL` are gated.** MySQL 8.0.31+ and Postgres
903
- * have them; SQLite does not, and its message — `near "ALL": syntax error`
904
- * — does not say which construct it means.
913
+ * have them; SQLite does not, and its message (`near "ALL": syntax error`
914
+ * ) does not say which construct it means.
905
915
  */
906
916
  export class QBSet<P = any> extends QBExecutable<P> {
907
917
  private _orderBy: string[] = []
@@ -980,7 +990,7 @@ export namespace DB {
980
990
  ) {
981
991
  throws(
982
992
  `${op} is not supported by this database. ` +
983
- `Use ${op.replace(' ALL', '')} instead — it removes duplicates.`,
993
+ `Use ${op.replace(' ALL', '')} instead: it removes duplicates.`,
984
994
  )
985
995
  }
986
996
  parts.push(op)
@@ -1053,8 +1063,8 @@ export namespace DB {
1053
1063
  }
1054
1064
 
1055
1065
  /**
1056
- * Emit one WHERE/HAVING clause. Captures nothing from the builder — it takes
1057
- * `params` as an argument — so it lives here rather than being re-created as
1066
+ * Emit one WHERE/HAVING clause. Captures nothing from the builder (it takes
1067
+ * `params` as an argument), so it lives here rather than being re-created as
1058
1068
  * a closure on every `parse()`, which is once per executed query.
1059
1069
  */
1060
1070
  function formatClause(
@@ -1072,16 +1082,9 @@ export namespace DB {
1072
1082
  if (op === 'IS NULL' || op === 'IS NOT NULL') {
1073
1083
  return `${left} ${op}`
1074
1084
  }
1075
- // A bound NULL can never satisfy `=` or `<>` — three-valued logic makes
1076
- // both comparisons UNKNOWN for every row, so `where(col, null)` silently
1077
- // matched nothing while cheerfully reporting success. The caller who
1078
- // passes an explicit null means the SQL spelling of it. Only literal
1079
- // values: a column reference on the right is a different comparison and
1080
- // is left alone.
1081
- if (rightArg === null && !isRightColumn) {
1082
- if (op === '=') return `${left} IS NULL`
1083
- if (op === '!=' || op === '<>') return `${left} IS NOT NULL`
1084
- }
1085
+ // One rule, three callers. See `nullComparison`.
1086
+ const nullOp = nullComparison(op, rightArg, isRightColumn)
1087
+ if (nullOp) return `${left} ${nullOp}`
1085
1088
  if (op === 'BETWEEN' && Array.isArray(rightArg)) {
1086
1089
  const min = evalOperands(rightArg[0], params, false)
1087
1090
  const max = evalOperands(rightArg[1], params, false)
@@ -1215,14 +1218,14 @@ export namespace DB {
1215
1218
 
1216
1219
  /**
1217
1220
  * Both columns, the joined table and the alias go through the convention-8
1218
- * guards here, at the call site — the same thing `orderBy` and `groupBy`
1221
+ * guards here, at the call site: the same thing `orderBy` and `groupBy`
1219
1222
  * do, and for the same reason: the `ColumnString` union is compile-time
1220
1223
  * only, so a value taken off a request reaches this method as a plain
1221
1224
  * string.
1222
1225
  *
1223
1226
  * It previously concatenated the raw arguments into an ON clause and left
1224
1227
  * `parse()` to run a `word.word` regex over the result, quoting what
1225
- * matched and passing everything else through untouched — so
1228
+ * matched and passing everything else through untouched, so
1226
1229
  * `join("users.id = 1 OR 1=1 UNION SELECT password FROM secrets --", …)`
1227
1230
  * was emitted verbatim.
1228
1231
  */
@@ -1249,8 +1252,8 @@ export namespace DB {
1249
1252
  }
1250
1253
 
1251
1254
  // Always qualified, never the raw argument. An undotted right column
1252
- // means "that table's `id`" — which is what the aliased form already
1253
- // emitted — but the unaliased path passed `strRight` straight through, so
1255
+ // means "that table's `id`" (which is what the aliased form already
1256
+ // emitted), but the unaliased path passed `strRight` straight through, so
1254
1257
  // `join('teachers.campusId', 'campuses')` produced
1255
1258
  // `ON "teachers"."campus_id" = campuses`: a bare table name in a value
1256
1259
  // position, invalid on every dialect and only discovered at execution.
@@ -1268,8 +1271,8 @@ export namespace DB {
1268
1271
  throws(`Invalid join type: ${type}`)
1269
1272
  }
1270
1273
  // Refused at the call site rather than at the server, because MySQL's
1271
- // message for it — "You have an error in your SQL syntax" pointing at the
1272
- // whole statement — says nothing about which construct is unsupported.
1274
+ // message for it ("You have an error in your SQL syntax" pointing at the
1275
+ // whole statement) says nothing about which construct is unsupported.
1273
1276
  if (joinType === 'FULL' && !getActiveDb()?.supportsFullOuterJoin) {
1274
1277
  throws(
1275
1278
  'FULL OUTER JOIN is not supported by this database (MySQL has no ' +
@@ -1300,7 +1303,7 @@ export namespace DB {
1300
1303
  }
1301
1304
 
1302
1305
  /**
1303
- * `FULL OUTER JOIN` — every row from both sides, matched where possible.
1306
+ * `FULL OUTER JOIN`: every row from both sides, matched where possible.
1304
1307
  *
1305
1308
  * SQLite (3.39+) and Postgres have it; **MySQL does not**, at any version,
1306
1309
  * so this throws there rather than emitting SQL the server will reject.
@@ -1390,7 +1393,7 @@ export namespace DB {
1390
1393
  }
1391
1394
 
1392
1395
  /**
1393
- * `SELECT DISTINCT`. Applies to the whole select list, not one column —
1396
+ * `SELECT DISTINCT`. Applies to the whole select list, not one column:
1394
1397
  * SQL has no per-column distinct, and offering one would imply otherwise.
1395
1398
  *
1396
1399
  * Idempotent, so `.distinct().distinct()` is one keyword rather than two.
@@ -1416,7 +1419,7 @@ export namespace DB {
1416
1419
  }
1417
1420
 
1418
1421
  /**
1419
- * Cursor (keyset) pagination — the next `pageSize` rows *after* `cursor`.
1422
+ * Cursor (keyset) pagination: the next `pageSize` rows *after* `cursor`.
1420
1423
  *
1421
1424
  * const first = await DB.from('posts').seek('id', null, 20).all()
1422
1425
  * const next = await DB.from('posts')
@@ -1424,15 +1427,15 @@ export namespace DB {
1424
1427
  *
1425
1428
  * `paginate()` is offset-based, and an offset is not free: `LIMIT 20 OFFSET
1426
1429
  * 200000` makes the server walk and discard 200,000 rows, so page 10,000
1427
- * costs far more than page 1. This walks nothing — it seeks straight into
1428
- * the index — so every page costs the same.
1430
+ * costs far more than page 1. This walks nothing (it seeks straight into
1431
+ * the index), so every page costs the same.
1429
1432
  *
1430
1433
  * It also does not skip or repeat rows when the table is written to
1431
1434
  * mid-scan, which offset paging does by construction: delete one row on
1432
1435
  * page 1 and every later page shifts by one.
1433
1436
  *
1434
- * The trade is that pages are only reachable in order — there is no "jump
1435
- * to page 500" — and the column must be **unique and ordered**, which in
1437
+ * The trade is that pages are only reachable in order (there is no "jump
1438
+ * to page 500"), and the column must be **unique and ordered**, which in
1436
1439
  * practice means a primary key or something monotonic. A non-unique cursor
1437
1440
  * column silently drops the rows that tie on the boundary value, which is
1438
1441
  * why this takes one column rather than pretending to sort by several.
@@ -1455,14 +1458,14 @@ export namespace DB {
1455
1458
  if (cursor !== null && cursor !== undefined) {
1456
1459
  // `gt`/`lt`, not a `'id >'` string: the operator belongs in an operand
1457
1460
  // ref, and folding it into the column name puts `id >` through
1458
- // `safeColumn`, which rejects it — correctly, since that is the guard
1461
+ // `safeColumn`, which rejects it. Correctly, since that is the guard
1459
1462
  // stopping an operator from being smuggled into an identifier.
1460
1463
  this._where.push({
1461
1464
  connector: this._where.length === 0 ? 'WHERE' : 'AND',
1462
1465
  ...parseWhereArgs(column, dir === 'ASC' ? gt(cursor) : lt(cursor)),
1463
1466
  })
1464
1467
  }
1465
- // Ordering is not optional here the way it is for `paginate` — a cursor
1468
+ // Ordering is not optional here the way it is for `paginate`: a cursor
1466
1469
  // is meaningless without the order it is a position in. Prepended so an
1467
1470
  // explicit `.orderBy()` still breaks ties after it.
1468
1471
  this._orderBy.unshift(`${safeColumn(column)} ${dir}`)
@@ -1472,7 +1475,7 @@ export namespace DB {
1472
1475
  // The numbered sections below are the statement grammar. Parameter push order
1473
1476
  // is load-bearing (Postgres renumbers `?` to `$n` left to right), so the
1474
1477
  // sequence is the correctness condition, not incidental.
1475
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: SQL assembler — one section per clause, in emission order
1478
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: SQL assembler, one section per clause, in emission order
1476
1479
  parse(): { sql: string; params: any[] } {
1477
1480
  const params: any[] = []
1478
1481
 
@@ -1497,7 +1500,7 @@ export namespace DB {
1497
1500
  if (Object.keys(this._select).length > 0) {
1498
1501
  for (const [alias, colRef] of Object.entries(this._select)) {
1499
1502
  if (colRef instanceof WindowRef || colRef instanceof SQLFunctionRef) {
1500
- // `evalOperands` is the single writer for a function call — the
1503
+ // `evalOperands` is the single writer for a function call: the
1501
1504
  // same one WHERE and HAVING go through, allow-list included.
1502
1505
  //
1503
1506
  // This used to re-implement it, and the copies had drifted: the
package/src/pool.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Nothing exposed these before: every deployment ran on Bun's defaults, which
5
5
  * is fine until an app with more workers than the server has connection slots
6
6
  * meets `FATAL: sorry, too many clients already`. `--threads N` makes that a
7
- * realistic shape rather than a theoretical one — each worker opens its own
7
+ * realistic shape rather than a theoretical one: each worker opens its own
8
8
  * pool, so the number that matters is `max x threads`.
9
9
  *
10
10
  * **SQLite ignores all of it**, and that is not an omission: a SQLite adapter
@@ -39,14 +39,14 @@ const ENV_KEYS: Record<keyof PoolOptions, string> = {
39
39
  /**
40
40
  * Read pool options from the environment, dropping anything unusable.
41
41
  *
42
- * Unset stays unset — an option Bakery does not pass is one Bun defaults,
42
+ * Unset stays unset: an option Bakery does not pass is one Bun defaults,
43
43
  * which is different from passing Bun a zero. A non-numeric or negative value
44
44
  * is dropped for the same reason: `DB_POOL_MAX=lots` must not become
45
45
  * `max: NaN`, which Bun would take and then behave unpredictably around.
46
46
  *
47
47
  * **Only known keys are ever forwarded**, and that matters more than it looks:
48
- * Bun accepts an unrecognised option silently — verified, `new SQL(url, {
49
- * totallyNotAnOption: 1 })` constructs and queries fine — so a typo'd key
48
+ * Bun accepts an unrecognized option silently (verified, `new SQL(url, {
49
+ * totallyNotAnOption: 1 })` constructs and queries fine), so a typo'd key
50
50
  * would configure nothing and report nothing. Passing a fixed set means the
51
51
  * typo lands in an env var name, where it is at least visible in one place.
52
52
  */
@@ -70,7 +70,7 @@ export function poolOptionsFromEnv(
70
70
  /**
71
71
  * Merge pool options into the object handed to `new SQL()`.
72
72
  *
73
- * The timeouts are **seconds here and milliseconds inside Bun** — it multiplies
73
+ * The timeouts are **seconds here and milliseconds inside Bun**: it multiplies
74
74
  * by 1000 on the way in, verified by reading `sql.options` back: `idleTimeout:
75
75
  * 5` is stored as `5000`. Seconds is what Bun's own documented unit is, so this
76
76
  * passes them straight through rather than converting and doubling the factor.
@@ -2,7 +2,7 @@
2
2
  * Type-level extension point connecting the framework to the app's schema.
3
3
  *
4
4
  * The dependency must point one way: the app depends on the framework, never
5
- * the reverse. Yet the ORM's types are derived from the app's `schema.ts` —
5
+ * the reverse. Yet the ORM's types are derived from the app's `schema.ts`:
6
6
  * previously via a direct `import ... from '~/schema'`, which meant the
7
7
  * framework's own types imported an app-owned, gitignored file, and a fresh
8
8
  * clone could not typecheck its database layer.
@@ -24,7 +24,7 @@
24
24
  * ```
25
25
  *
26
26
  * With no registration, every table and column falls back to permissive
27
- * `any`-shaped records — the ORM stays fully usable, just untyped. The
27
+ * `any`-shaped records. The ORM stays fully usable, just untyped. The
28
28
  * runtime needs no registration at all: schema *values* are loaded by path
29
29
  * (see `sync/load.ts`), from `schema` in `server.config.ts` when the app sets
30
30
  * one and otherwise from `<cwd>/orm/index.ts` or `<cwd>/schema.ts`, whichever
@@ -45,7 +45,7 @@ import type { MapOf } from '@bakery-framework/core/types'
45
45
  // fix. That rule is off repo-wide now, but `noEmptyInterface` reaches the same
46
46
  // construct and offers the same fix, so the suppression stays. The compiler is
47
47
  // the real guard: change this line and `apps/starter` fails to typecheck.
48
- // biome-ignore lint/suspicious/noEmptyInterface: must stay mergeable — above
48
+ // biome-ignore lint/suspicious/noEmptyInterface: must stay mergeable, above
49
49
  export interface SchemaRegistry {}
50
50
 
51
51
  type Registered = SchemaRegistry extends { schema: infer S } ? S : never