@bakery-framework/orm 2.0.0-alpha.8 → 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.
@@ -13,11 +13,11 @@ export class MySQLAdapter extends SQLAdapter {
13
13
  ? connectionTarget.toString().replace(/^(mysqli?s?:\/\/)/, 'mysql://')
14
14
  : undefined
15
15
  super('mysql', undefined, target)
16
- // `isOpenConnection`, not `instanceof SQL` — see the helper for why the
16
+ // `isOpenConnection`, not `instanceof SQL`. See the helper for why the
17
17
  // latter throws rather than answering.
18
18
  //
19
19
  // Pool options apply only when this opens its own connection. A handle
20
- // handed in is already someone else's pool — notably a transaction's, where
20
+ // handed in is already someone else's pool: notably a transaction's, where
21
21
  // re-sizing anything would be meaningless.
22
22
  this.sql = isOpenConnection(connectionTarget)
23
23
  ? (connectionTarget as SQL)
@@ -40,7 +40,7 @@ export class MySQLAdapter extends SQLAdapter {
40
40
  // `affectedRows` first, and the order is the whole fix.
41
41
  //
42
42
  // Bun's MySQL driver sets `count` to **0** for every write and puts the
43
- // real number in `affectedRows` — the reverse of SQLite, where `count`
43
+ // real number in `affectedRows`: the reverse of SQLite, where `count`
44
44
  // is authoritative and `affectedRows` is null. Reading `count` first
45
45
  // through a `??` chain therefore never fell through: zero is not
46
46
  // nullish, so it was taken and returned. Every insert, update and
@@ -251,7 +251,7 @@ export class MySQLAdapter extends SQLAdapter {
251
251
  /**
252
252
  * MySQL binds a view's tables at query time, not at `CREATE VIEW`, so a table
253
253
  * can be dropped and rebuilt underneath a view that names it. SQLite and
254
- * Postgres both refuse — see the base declaration for the two messages.
254
+ * Postgres both refuse. See the base declaration for the two messages.
255
255
  */
256
256
  override get viewsBlockTableRebuild(): boolean {
257
257
  return false
@@ -270,7 +270,9 @@ export class MySQLAdapter extends SQLAdapter {
270
270
  return new MySQLAdapter(sql as SQL)
271
271
  }
272
272
 
273
- async getSchema(): Promise<SQLAdapter.TableDetails[]> {
273
+ async getSchema(
274
+ options?: SQLAdapter.SchemaOptions,
275
+ ): Promise<SQLAdapter.TableDetails[]> {
274
276
  const res = (await this.query(
275
277
  'SELECT table_name AS name' +
276
278
  ' FROM information_schema.tables' +
@@ -280,7 +282,11 @@ export class MySQLAdapter extends SQLAdapter {
280
282
  const tablesWithDetails: SQLAdapter.TableDetails[] = []
281
283
  for (const t of res) {
282
284
  const [countRes, cols, idxs] = (await Promise.all([
283
- this.query(`SELECT COUNT(*) as count FROM ${this.quote(t.name)}`).get(),
285
+ options?.rowCounts
286
+ ? this.query(
287
+ `SELECT COUNT(*) as count FROM ${this.quote(t.name)}`,
288
+ ).get()
289
+ : null,
284
290
  this.query(
285
291
  'SELECT column_name AS name, data_type AS type,' +
286
292
  ' is_nullable AS is_nullable, column_key AS column_key' +
@@ -293,13 +299,13 @@ export class MySQLAdapter extends SQLAdapter {
293
299
  ' FROM information_schema.statistics' +
294
300
  ' WHERE table_name = ? AND table_schema = DATABASE()',
295
301
  ).all(t.name),
296
- ])) as [SQLAdapter.CountRow, any[], any[]]
302
+ ])) as [SQLAdapter.CountRow | null, any[], any[]]
297
303
  const uniqueIdxs = Array.from(
298
304
  new Map(idxs.map(i => [i.name, i.non_unique === 0])).entries(),
299
305
  ).map(([name, unique]) => ({ name, unique }))
300
306
  tablesWithDetails.push({
301
307
  name: t.name,
302
- rowCount: countRes?.count || 0,
308
+ rowCount: options?.rowCounts ? countRes?.count || 0 : null,
303
309
  columns: cols.map(c => ({
304
310
  name: c.name,
305
311
  type: c.type,
@@ -326,10 +332,15 @@ export class MySQLAdapter extends SQLAdapter {
326
332
  new Set(cols.map(c => c.name)),
327
333
  )
328
334
  const tName = this.quote(tableName)
329
- const countRes = (await this.query(
330
- `SELECT COUNT(*) as count FROM ${tName}${whereSql}`,
331
- ).get(...whereParams)) as SQLAdapter.CountRow
332
- const totalRows = countRes?.count || 0
335
+ // See `TableDataOptions.knownTotal`: the count is almost the whole cost
336
+ // of a page, and a caller that has already counted can say so.
337
+ const reuse = SQLAdapter.usableTotal(options.knownTotal)
338
+ const countRes = reuse
339
+ ? null
340
+ : ((await this.query(
341
+ `SELECT COUNT(*) as count FROM ${tName}${whereSql}`,
342
+ ).get(...whereParams)) as SQLAdapter.CountRow)
343
+ const totalRows = reuse ? options.knownTotal! : countRes?.count || 0
333
344
  const rows = await this.query(
334
345
  `SELECT * FROM ${tName}${whereSql}${orderSql} LIMIT ? OFFSET ?`,
335
346
  ).all(
@@ -352,7 +363,7 @@ export class MySQLAdapter extends SQLAdapter {
352
363
  *
353
364
  * This deliberately does *not* go through `getSchema()`. That lists every
354
365
  * table in the database and then issues `COUNT(*)` plus two
355
- * information_schema queries per table — so deleting one row from a
366
+ * information_schema queries per table, so deleting one row from a
356
367
  * twenty-table schema ran sixty-one statements, twenty of them full row
357
368
  * counts, to learn one column name. The pk is one lookup against the same
358
369
  * `column_key = 'PRI'` that getSchema itself derived the flag from, in the
@@ -400,8 +411,8 @@ export class MySQLAdapter extends SQLAdapter {
400
411
  * Every information_schema column is aliased to its own lowercase name, and
401
412
  * the aliases are load-bearing despite looking redundant.
402
413
  *
403
- * MySQL 8 returns information_schema field names **uppercase** — a bare
404
- * `SELECT table_name` yields a row keyed `TABLE_NAME` — while an alias is
414
+ * MySQL 8 returns information_schema field names **uppercase** (a bare
415
+ * `SELECT table_name` yields a row keyed `TABLE_NAME`), while an alias is
405
416
  * returned exactly as written. Without them `t.table_name` is `undefined`
406
417
  * and this method throws `undefined is not an object` inside `Case.camel`,
407
418
  * which takes `db:sync` against MySQL down entirely. Postgres was never
@@ -505,6 +516,7 @@ export class MySQLAdapter extends SQLAdapter {
505
516
  type: info.non_unique === 0 ? 'unique' : 'index',
506
517
  table: Case.camel(info.table),
507
518
  cols: info.cols.map(Case.camel),
519
+ rawCols: info.cols,
508
520
  },
509
521
  ]),
510
522
  )
@@ -512,7 +524,7 @@ export class MySQLAdapter extends SQLAdapter {
512
524
  // Match pattern only. MySQL re-renders an expression default before storing
513
525
  // it in information_schema (`unix_timestamp()`, case and parens not
514
526
  // guaranteed), and isDateNowDefault() strips parens and uppercases before
515
- // comparing — so the bare prefix is the right entry here. It is *not*
527
+ // comparing, so the bare prefix is the right entry here. It is *not*
516
528
  // emittable SQL; see below.
517
529
  override readonly dateNowDefaults: string[] = ['UNIX_TIMESTAMP']
518
530
 
@@ -539,7 +551,7 @@ export class MySQLAdapter extends SQLAdapter {
539
551
  ),
540
552
  }
541
553
  // `column_type` ('varchar(64)'), not `data_type` ('varchar'), so the guard
542
- // sees the same string a human would read — and so TEXT, whose
554
+ // sees the same string a human would read, and so TEXT, whose
543
555
  // character_maximum_length MySQL reports as 65535, is excluded.
544
556
  const length = this.sizedTextLength(
545
557
  String(col.column_type || col.data_type || ''),
@@ -6,7 +6,7 @@ import type { SQLAdapter } from './base'
6
6
  *
7
7
  * Deliberately **not** an emitter and **not** a listener array. This sits in
8
8
  * the hot path of every statement the ORM runs, so the cost of having the
9
- * feature at all has to be a single null read when nobody is watching — see
9
+ * feature at all has to be a single null read when nobody is watching. See
10
10
  * `observe()` below, which returns the driver's own value untouched in that
11
11
  * case. An emitter would allocate an event object and walk a list per query
12
12
  * whether or not anything subscribed.
@@ -22,12 +22,12 @@ import type { SQLAdapter } from './base'
22
22
  export type QueryMethod = 'all' | 'run' | 'get' | 'values' | 'iterate'
23
23
 
24
24
  export interface QueryEvent {
25
- /** The statement as handed to the driver, before dialect normalisation. */
25
+ /** The statement as handed to the driver, before dialect normalization. */
26
26
  sql: string
27
27
  /** Wall-clock duration in milliseconds, fractional. */
28
28
  ms: number
29
29
  /**
30
- * Rows the statement produced, or `null` when it cannot be known — a failed
30
+ * Rows the statement produced, or `null` when it cannot be known: a failed
31
31
  * statement, or a result shape the adapter does not describe as an array.
32
32
  * For `run` this is the *affected* row count, not a result set.
33
33
  */
@@ -47,7 +47,7 @@ export interface QueryEvent {
47
47
  * observer receives is one `logger.info` away from a log file, an analytics
48
48
  * table or a dashboard panel. Defaulting this on would turn "add a slow
49
49
  * query panel" into a credential leak that nobody reviewed. Callers that
50
- * genuinely need bindings — a local query profiler, say — must ask, and are
50
+ * genuinely need bindings (a local query profiler, say) must ask, and are
51
51
  * then responsible for what they do with them.
52
52
  */
53
53
  params?: readonly unknown[]
@@ -73,7 +73,7 @@ let includeParams = false
73
73
  * call installed, so a test that forgets to restore cannot silently unhook a
74
74
  * later one.
75
75
  *
76
- * ```ts no-check — illustrative: the app decides where slow queries go
76
+ * ```ts no-check: illustrative: the app decides where slow queries go
77
77
  * import { setQueryObserver } from '@bakery-framework/orm'
78
78
  *
79
79
  * setQueryObserver(event => {
@@ -150,7 +150,7 @@ async function timed<R>(
150
150
  rows: null,
151
151
  driver,
152
152
  method,
153
- // Normalised so `error` is a reliable "did this throw" signal: a driver
153
+ // Normalized so `error` is a reliable "did this throw" signal: a driver
154
154
  // that rejects with `undefined` would otherwise be indistinguishable
155
155
  // from success.
156
156
  error: error ?? new Error('query failed with no error value'),
@@ -165,8 +165,8 @@ async function timed<R>(
165
165
  * Wrap one executor entry point so it reports to the observer.
166
166
  *
167
167
  * The unobserved path is the `if` below and nothing else: no timer read, no
168
- * event object, and the underlying call's own return value — which for `all`
169
- * and `run` may legitimately be synchronous — passes straight through.
168
+ * event object, and the underlying call's own return value (which for `all`
169
+ * and `run` may legitimately be synchronous) passes straight through.
170
170
  */
171
171
  export function observe<R>(
172
172
  driver: SQLAdapter.Driver,
@@ -182,7 +182,7 @@ export function observe<R>(
182
182
  ): (sqlText: string, params?: unknown[]) => Promise<R> | R
183
183
  // Two overloads because the executor's five entry points are not one shape.
184
184
  // `get` and `values` are declared to return a promise unconditionally, while
185
- // `all` and `run` may legitimately be synchronous — and the implementation
185
+ // `all` and `run` may legitimately be synchronous, and the implementation
186
186
  // signature, which has to admit both, cannot narrow to the former on its own.
187
187
  // Without the first overload `createExecutor` fails to satisfy `Executor`.
188
188
  export function observe<R>(
@@ -201,7 +201,7 @@ export function observe<R>(
201
201
  * `iterate` is a stream, so its duration means something different from the
202
202
  * other four, and the choice here is deliberate:
203
203
  *
204
- * - `ms` spans from the call to `iterate()` until iteration **ends** — the
204
+ * - `ms` spans from the call to `iterate()` until iteration **ends**: the
205
205
  * generator is exhausted, throws, or the consumer breaks out of the loop.
206
206
  * That includes whatever the consumer did between yields, so an `iterate`
207
207
  * event is not comparable with an `all` event and must not be averaged into
@@ -254,7 +254,7 @@ export function observeIterate(
254
254
  // The source is created here rather than inside `drain`, and `started` with
255
255
  // it: an async generator body does not run until the first `next()`, so
256
256
  // both would otherwise be deferred to whenever the consumer got round to
257
- // pulling — and the statement would be issued later than it is today.
257
+ // pulling, and the statement would be issued later than it is today.
258
258
  const started = performance.now()
259
259
  return drain(driver, iterate(sqlText, params), sqlText, params, started)
260
260
  }
@@ -22,11 +22,11 @@ export class PGAdapter extends SQLAdapter {
22
22
  ? connectionTarget.toString()
23
23
  : undefined
24
24
  super('postgres', undefined, target)
25
- // `isOpenConnection`, not `instanceof SQL` — see the helper for why the
25
+ // `isOpenConnection`, not `instanceof SQL`. See the helper for why the
26
26
  // latter throws rather than answering.
27
27
  //
28
28
  // Pool options apply only when this opens its own connection. A handle
29
- // handed in is already someone else's pool — notably a transaction's, where
29
+ // handed in is already someone else's pool: notably a transaction's, where
30
30
  // re-sizing anything would be meaningless.
31
31
  this.sql = isOpenConnection(connectionTarget)
32
32
  ? (connectionTarget as SQL)
@@ -57,16 +57,23 @@ export class PGAdapter extends SQLAdapter {
57
57
 
58
58
  private static handleSpecial(
59
59
  char: string,
60
- nextChar: string | undefined,
60
+ // Underscored because it is deliberately unread: the branch that would
61
+ // have used it is the backslash branch, and the comment below is why
62
+ // there is not one. Kept in the signature so the shape matches
63
+ // `handleSpecial` in the other two adapters.
64
+ _nextChar: string | undefined,
61
65
  state: PGSQLParserState,
62
66
  ): string | null {
63
- if (char === '\\') {
64
- if ((state.inSingleQuote || state.inDoubleQuote) && nextChar) {
65
- state.skipNext = true
66
- return `\\${nextChar}`
67
- }
68
- return char
69
- }
67
+ // No backslash branch, deliberately. Postgres with
68
+ // `standard_conforming_strings`: the server default since 9.1, and what
69
+ // every connection here runs under: treats a backslash inside a `'…'`
70
+ // literal as an ordinary character. This scanner used to apply MySQL's
71
+ // rule instead: `\` consumed the next character, so the `'` closing a
72
+ // `'\'` literal was eaten, the scanner stayed "inside" a literal the
73
+ // server had already closed, and every `?` after it was left unrewritten,
74
+ // which is how `ESCAPE '\'` produced a syntax error several tokens
75
+ // downstream. The server's parse is the only one that matters; the scanner
76
+ // now agrees with it by having no opinion about backslashes at all.
70
77
  if (char === '`') {
71
78
  return !state.inSingleQuote && !state.inDoubleQuote ? '"' : char
72
79
  }
@@ -89,7 +96,7 @@ export class PGAdapter extends SQLAdapter {
89
96
  skipNext: false,
90
97
  paramsLength: params.length,
91
98
  }
92
- // `skipNext` is consumed in exactly one place — the `i++` below — and
99
+ // `skipNext` is consumed in exactly one place (the `i++` below), and
93
100
  // cleared there.
94
101
  //
95
102
  // It used to be consumed twice: a top-of-loop
@@ -100,9 +107,8 @@ export class PGAdapter extends SQLAdapter {
100
107
  // default containing an apostrophe silently lost the rest of its value, and
101
108
  // the same applied to a backslash escape. Values bind as parameters, so
102
109
  // this
103
- // only ever reached literals the framework itself emits — DDL defaults —
104
- // which is why it survived: `ddl.test.ts` asserts the SQL *before*
105
- // normalisation, and no live server ran until now.
110
+ // only ever reached literals the framework itself emits (DDL defaults), // which is why it survived: `ddl.test.ts` asserts the SQL *before*
111
+ // normalization, and no live server ran until now.
106
112
  for (let i = 0; i < sql.length; i++) {
107
113
  const char = sql[i]
108
114
  const nextChar = sql[i + 1]
@@ -142,15 +148,15 @@ export class PGAdapter extends SQLAdapter {
142
148
  PGAdapter.normalizePostgresSQL(sql, params),
143
149
  params,
144
150
  )) as any
145
- // `count` is authoritative on Postgres for every command — it is the
146
- // row count from the command tag — so it is read first and `rows.length`
151
+ // `count` is authoritative on Postgres for every command (it is the
152
+ // row count from the command tag), so it is read first and `rows.length`
147
153
  // is only a fallback.
148
154
  //
149
155
  // It used to be the other way round, behind an `Array.isArray` check
150
156
  // that was always true: Bun returns an *array* for a write too, just an
151
157
  // empty one, so the `count` branch was unreachable and `changes` was
152
158
  // `rows.length`. `UPDATE` and `DELETE` return no rows, so both reported
153
- // 0. `INSERT` was right only by accident — the `RETURNING *` appended
159
+ // 0. `INSERT` was right only by accident: the `RETURNING *` appended
154
160
  // above happens to make `rows.length` the number inserted.
155
161
  const changes = Number(
156
162
  rows?.count ?? (Array.isArray(rows) ? rows.length : 0),
@@ -246,7 +252,9 @@ export class PGAdapter extends SQLAdapter {
246
252
  return new PGAdapter(sql as SQL)
247
253
  }
248
254
 
249
- async getSchema(): Promise<SQLAdapter.TableDetails[]> {
255
+ async getSchema(
256
+ options?: SQLAdapter.SchemaOptions,
257
+ ): Promise<SQLAdapter.TableDetails[]> {
250
258
  const res = (await this.query(
251
259
  'SELECT table_name AS name, table_type AS type' +
252
260
  ' FROM information_schema.tables' +
@@ -261,7 +269,9 @@ export class PGAdapter extends SQLAdapter {
261
269
  res.map(async t => {
262
270
  const qName = this.quote(t.name)
263
271
  const [countRes, cols, pkCols, idxs] = (await Promise.all([
264
- this.query(`SELECT COUNT(*)::int as count FROM ${qName}`).get(),
272
+ options?.rowCounts
273
+ ? this.query(`SELECT COUNT(*)::int as count FROM ${qName}`).get()
274
+ : null,
265
275
  this.query(
266
276
  'SELECT column_name AS name, data_type AS type,' +
267
277
  ' is_nullable AS is_nullable' +
@@ -271,8 +281,8 @@ export class PGAdapter extends SQLAdapter {
271
281
  ' ORDER BY ordinal_position',
272
282
  ).all(t.name),
273
283
  // `::regclass` casts a **string**, so the table name binds as a
274
- // parameter. It used to interpolate `qName` — a double-quoted
275
- // *identifier* — which Postgres reads as a column reference, so this
284
+ // parameter. It used to interpolate `qName` (a double-quoted
285
+ // *identifier*), which Postgres reads as a column reference, so this
276
286
  // threw `column "<table>" does not exist` for every table and took the
277
287
  // whole of `getSchema()` down with it on this dialect.
278
288
  //
@@ -292,10 +302,10 @@ export class PGAdapter extends SQLAdapter {
292
302
  " WHERE schemaname NOT IN ('pg_catalog', 'information_schema')" +
293
303
  ' AND tablename = ?',
294
304
  ).all(t.name),
295
- ])) as [SQLAdapter.CountRow, any[], any[], any[]]
305
+ ])) as [SQLAdapter.CountRow | null, any[], any[], any[]]
296
306
  return {
297
307
  name: t.name,
298
- rowCount: countRes?.count || 0,
308
+ rowCount: options?.rowCounts ? countRes?.count || 0 : null,
299
309
  columns: cols.map(c => ({
300
310
  name: c.name,
301
311
  type: c.type,
@@ -326,10 +336,15 @@ export class PGAdapter extends SQLAdapter {
326
336
  new Set(cols.map(c => c.name)),
327
337
  )
328
338
  const tName = this.quote(tableName)
329
- const countRes = (await this.query(
330
- `SELECT COUNT(*) as count FROM ${tName}${whereSql}`,
331
- ).get(...whereParams)) as SQLAdapter.CountRow
332
- const totalRows = countRes?.count || 0
339
+ // See `TableDataOptions.knownTotal`: the count is almost the whole cost
340
+ // of a page, and a caller that has already counted can say so.
341
+ const reuse = SQLAdapter.usableTotal(options.knownTotal)
342
+ const countRes = reuse
343
+ ? null
344
+ : ((await this.query(
345
+ `SELECT COUNT(*) as count FROM ${tName}${whereSql}`,
346
+ ).get(...whereParams)) as SQLAdapter.CountRow)
347
+ const totalRows = reuse ? options.knownTotal! : countRes?.count || 0
333
348
  const rows = (await this.query(
334
349
  `SELECT ctid::text AS rowid, * FROM ${tName}` +
335
350
  `${whereSql}${orderSql} LIMIT ? OFFSET ?`,
@@ -438,7 +453,7 @@ export class PGAdapter extends SQLAdapter {
438
453
  }
439
454
 
440
455
  // is_identity/identity_generation are selected because an identity column
441
- // — what colDef() emits — carries a NULL column_default; only the legacy
456
+ // (what colDef() emits) carries a NULL column_default; only the legacy
442
457
  // serial style leaves a nextval(...) marker there.
443
458
  const cols = (await this.query(
444
459
  'SELECT column_name, data_type, is_nullable, column_default,' +
@@ -473,24 +488,24 @@ export class PGAdapter extends SQLAdapter {
473
488
  )
474
489
  continue
475
490
  const m = r.indexdef.match(/\(([^)]+)\)/)
491
+ const raw: string[] = m
492
+ ? m[1].split(',').map((c: string) => c.trim().replace(/"/g, ''))
493
+ : []
476
494
  dbIndexes[Case.camel(r.indexname)] = {
477
495
  type: /UNIQUE/i.test(r.indexdef) ? 'unique' : 'index',
478
496
  table: Case.camel(r.tablename),
479
- cols: m
480
- ? m[1]
481
- .split(',')
482
- .map((c: string) => Case.camel(c.trim().replace(/"/g, '')))
483
- : [],
497
+ cols: raw.map(Case.camel),
498
+ rawCols: raw,
484
499
  }
485
500
  }
486
501
  return dbIndexes
487
502
  }
488
- // Two patterns because Postgres does not store DDL text — it stores a parsed
503
+ // Two patterns because Postgres does not store DDL text: it stores a parsed
489
504
  // expression and re-renders it, and how it renders depends on the version.
490
505
  // PG 14+ reports `(EXTRACT(epoch FROM now()))::integer`; PG <= 13 parses
491
506
  // EXTRACT into `date_part('epoch'::text, now())`, which the EXTRACT pattern
492
507
  // does not match. Missing it means the column diffs dirty on every single
493
- // sync — the same perpetual-rebuild failure the SQLite quote bug caused.
508
+ // sync: the same perpetual-rebuild failure the SQLite quote bug caused.
494
509
  override readonly dateNowDefaults: string[] = [
495
510
  'EXTRACT(EPOCH FROM',
496
511
  'DATE_PART',
@@ -506,7 +521,7 @@ export class PGAdapter extends SQLAdapter {
506
521
  // `gen_random_uuid()` is built in from Postgres 13; before that it lived in
507
522
  // the pgcrypto extension. Postgres reports it back as
508
523
  // `gen_random_uuid()`, so unlike the epoch expression the emitted form and
509
- // the match pattern coincide — stated rather than assumed, because the two
524
+ // the match pattern coincide: stated rather than assumed, because the two
510
525
  // being equal here is a coincidence of this expression, not a rule.
511
526
  override readonly uuidDefaults: string[] = ['GEN_RANDOM_UUID']
512
527
  override readonly uuidExpression: string = 'gen_random_uuid()'
@@ -521,7 +536,7 @@ export class PGAdapter extends SQLAdapter {
521
536
  ),
522
537
  }
523
538
  // Postgres calls it 'character varying' and reports null for TEXT, so the
524
- // guard has less to do here than on MySQL — but it is the same guard.
539
+ // guard has less to do here than on MySQL, but it is the same guard.
525
540
  const length = this.sizedTextLength(
526
541
  String(col.data_type || col.udt_name || ''),
527
542
  col.character_maximum_length,
@@ -542,11 +557,11 @@ export class PGAdapter extends SQLAdapter {
542
557
  // Postgres does not store the DDL text of a default; it re-renders the
543
558
  // parsed expression and appends the column's type. `''` comes back as
544
559
  // `''::character varying` and `'x'` as `'x'::text`, neither of which
545
- // equals what the schema says — so the column diffs on every sync.
560
+ // equals what the schema says, so the column diffs on every sync.
546
561
  //
547
562
  // The cast is only stripped when it ends the string, which leaves
548
563
  // `nextval('seq'::regclass)` and the `%dateNow%` expressions alone: both
549
- // end in `)`, and both are recognised elsewhere.
564
+ // end in `)`, and both are recognized elsewhere.
550
565
  if (typeof def === 'string') {
551
566
  const bare = def.replace(/::[\w ]+$/, '').trim()
552
567
  const quoted = /^'([\s\S]*)'$/.exec(bare)
@@ -6,11 +6,11 @@ import type { SQLAdapter } from './base'
6
6
  * add its own.
7
7
  *
8
8
  * A union type cannot be extended from outside the package, and widening it to
9
- * `string` would give up every place the compiler currently catches a typo — so
9
+ * `string` would give up every place the compiler currently catches a typo, so
10
10
  * this is an interface and a new name arrives by declaration merging, the same
11
11
  * mechanism `DBSchema` already uses for an app's tables:
12
12
  *
13
- * ```ts no-check — a third-party package's own declaration file
13
+ * ```ts no-check: a third-party package's own declaration file
14
14
  * declare module '@bakery-framework/orm/adapters' {
15
15
  * interface DriverRegistry {
16
16
  * mssql: true
@@ -18,7 +18,7 @@ import type { SQLAdapter } from './base'
18
18
  * }
19
19
  * ```
20
20
  *
21
- * `@bakery-framework/orm/adapters` — the public subpath — and not this file, which is
21
+ * `@bakery-framework/orm/adapters` (the public subpath), and not this file, which is
22
22
  * private and does not resolve from outside the package. The merge reaches
23
23
  * through the barrel's `export *`; aiming it at an unresolvable specifier
24
24
  * instead declares a second, unrelated interface and leaves every driver name
@@ -47,14 +47,14 @@ export interface AdapterSpec {
47
47
  /**
48
48
  * URL schemes this adapter answers to, without `://`.
49
49
  *
50
- * Checked first and exactly — `mysql://…` reaches the adapter that listed
50
+ * Checked first and exactly: `mysql://…` reaches the adapter that listed
51
51
  * `mysql`, and nothing else is consulted. List every spelling you accept;
52
52
  * the built-in MySQL adapter lists four.
53
53
  */
54
54
  protocols?: readonly string[]
55
55
 
56
56
  /**
57
- * Last resort, for a target no scheme matched — a bare file path, say.
57
+ * Last resort, for a target no scheme matched: a bare file path, say.
58
58
  *
59
59
  * Consulted in reverse registration order, so an adapter registered later can
60
60
  * claim a target one of the built-ins would otherwise have taken. Return
@@ -66,7 +66,7 @@ export interface AdapterSpec {
66
66
  * Open a connection. `target` is the raw `DB_URL`, or `undefined` when none
67
67
  * was set and this adapter is the default.
68
68
  *
69
- * Async so the implementation can `await import()` its own driver module —
69
+ * Async so the implementation can `await import()` its own driver module,
70
70
  * which is how the three built-ins stay lazy: registering all of them costs
71
71
  * three object literals, and only the one that wins is ever loaded.
72
72
  */
@@ -77,7 +77,7 @@ export interface AdapterSpec {
77
77
  }
78
78
 
79
79
  /**
80
- * What to fall back to when the target names no adapter — split in two because
80
+ * What to fall back to when the target names no adapter: split in two because
81
81
  * the two cases have never had the same answer. See {@link resolveDriver}.
82
82
  */
83
83
  export interface DriverFallback {
@@ -96,8 +96,8 @@ export const DEFAULT_FALLBACK: DriverFallback = {
96
96
  * A stack per driver name, not one entry per name.
97
97
  *
98
98
  * Registering over an existing driver is temporary far more often than it is
99
- * permanent — a test swapping in a fake, an app overriding a built-in for one
100
- * environment — so "undo" has to give back exactly what was displaced. One
99
+ * permanent (a test swapping in a fake, an app overriding a built-in for one
100
+ * environment), so "undo" has to give back exactly what was displaced. One
101
101
  * entry per name cannot: release two registrations out of order and the second
102
102
  * disposer restores the first's spec, quietly leaving a stub adapter installed
103
103
  * for the rest of the process. A stack makes the order irrelevant, which is the
@@ -149,14 +149,14 @@ export function listAdapters(): AdapterSpec[] {
149
149
  * 1. **No target at all** → `fallback` (`sqlite`), so a fresh app with no
150
150
  * `DB_URL` gets a file database rather than an error.
151
151
  * 2. **A URL scheme** that some adapter declared → that adapter, exactly.
152
- * 3. **`matches()`**, in reverse registration order — later registrations get
152
+ * 3. **`matches()`**, in reverse registration order: later registrations get
153
153
  * first refusal, which is what makes overriding a built-in possible.
154
154
  * 4. **`fallback`** otherwise, which for a target that looked like a host is
155
- * `postgres`. Long-standing behaviour, kept deliberately: a bare
155
+ * `postgres`. Long-standing behavior, kept deliberately: a bare
156
156
  * `db.internal:5432/app` has been read as Postgres since before the
157
157
  * registry existed.
158
158
  *
159
- * Throws only if the resolved driver has no adapter registered — which means
159
+ * Throws only if the resolved driver has no adapter registered, which means
160
160
  * something registered a `protocols` entry and then unregistered itself, not
161
161
  * anything a user can reach by typing a URL.
162
162
  */
@@ -175,7 +175,7 @@ export function resolveAdapter(
175
175
  return spec
176
176
  }
177
177
 
178
- /** Step 1–4 of {@link resolveAdapter}, without the lookup. */
178
+ /** Step 1 to 4 of {@link resolveAdapter}, without the lookup. */
179
179
  export function resolveDriver(
180
180
  target: string,
181
181
  fallback: DriverFallback = DEFAULT_FALLBACK,