@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.
@@ -37,7 +37,7 @@ async function processTableRebuild(
37
37
  // Inline, or the rebuild silently drops every foreign key the table had.
38
38
  //
39
39
  // A constraint is part of the table definition, so recreating the table
40
- // without it removes it — and on SQLite there is no `ALTER` to put one back,
40
+ // without it removes it, and on SQLite there is no `ALTER` to put one back,
41
41
  // which is precisely why the planner turns a foreign-key change into a
42
42
  // rebuild. Without this, that plan could never *add* a key: the rebuild it
43
43
  // scheduled was the thing dropping them.
@@ -209,12 +209,12 @@ async function rebuildTablesPhase(
209
209
  * Drop declared views before any table is rebuilt, where the dialect needs it.
210
210
  *
211
211
  * A rebuild swaps the table out and back, and two of the three dialects refuse
212
- * to do that while a view still names the table — SQLite at the rename, Postgres
212
+ * to do that while a view still names the table: SQLite at the rename, Postgres
213
213
  * at the drop. `viewsBlockTableRebuild` carries which, and why; MySQL is the one
214
214
  * that does not care and skips this entirely.
215
215
  *
216
216
  * Views hold no data and `syncViewsAndTablesPhase` recreates every declared one
217
- * a moment later, so dropping them first costs nothing — it is the same "drop
217
+ * a moment later, so dropping them first costs nothing: it is the same "drop
218
218
  * and recreate" the engine already does when a view's body changes.
219
219
  *
220
220
  * Only when something is actually being rebuilt: a sync with no rebuilds should
@@ -295,7 +295,7 @@ async function addIndexesPhase(
295
295
  * Foreign keys on tables that already existed.
296
296
  *
297
297
  * Only reachable where the dialect can ALTER one in. SQLite cannot, so its
298
- * missing keys are handled by scheduling a table rebuild in the planner — the
298
+ * missing keys are handled by scheduling a table rebuild in the planner: the
299
299
  * rebuild recreates the table through `createTable`, which emits them inline.
300
300
  */
301
301
  async function foreignKeysPhase(
@@ -308,7 +308,7 @@ async function foreignKeysPhase(
308
308
  for (const fk of fksToDrop.values()) {
309
309
  // Called unconditionally, like every other phase. These two were `?.` for
310
310
  // as long as `syncMsgs` did not declare them, which read as "this message
311
- // is optional" and was really "this message does not exist" — the optional
311
+ // is optional" and was really "this message does not exist": the optional
312
312
  // call is what let the gap survive: it type-checked, and the proxy turned
313
313
  // the miss into an error line at run time instead of a build failure.
314
314
  MESSAGES.EXEC_DROP_FK({ table: fk.table, name: fk.name ?? '' })
@@ -375,7 +375,7 @@ export function hasOldWrappers(constraints: SyncTypes.DBConstraints) {
375
375
  // and a table-level `_transform` (a function), so `Object.values` really
376
376
  // does yield a mixed union and `_oldColumn` really is absent from two of
377
377
  // its three members. Reading it off the string or the function gives
378
- // `undefined`, which is what this check wants — so the narrowing is
378
+ // `undefined`, which is what this check wants, so the narrowing is
379
379
  // deliberate rather than a lie, and the emitted code is unchanged.
380
380
  const cols = Object.values(tObj) as (
381
381
  | SyncTypes.ColumnConstraint
@@ -385,7 +385,7 @@ export function hasOldWrappers(constraints: SyncTypes.DBConstraints) {
385
385
  })
386
386
  }
387
387
 
388
- /** The `foreign()` declarations, normalised into the shape the diff uses. */
388
+ /** The `foreign()` declarations, normalized into the shape the diff uses. */
389
389
  export function collectForeignKeys(
390
390
  tsIndexes: SyncTypes.DBIndexes,
391
391
  ): SyncTypes.DBForeignKeys {
@@ -412,7 +412,7 @@ export function collectForeignKeys(
412
412
  * Which foreign keys to add and which to drop.
413
413
  *
414
414
  * Keyed by the tuple, so a constraint the database named itself still matches
415
- * the declaration that produced it — including on SQLite, which reports no name
415
+ * the declaration that produced it, including on SQLite, which reports no name
416
416
  * at all.
417
417
  */
418
418
  export function calculateForeignKeyDiff(
@@ -422,7 +422,7 @@ export function calculateForeignKeyDiff(
422
422
  /**
423
423
  * Tables that already exist, so a key on a table being *created* is left out.
424
424
  *
425
- * `CREATE TABLE` emits its foreign keys inline — the only spelling SQLite
425
+ * `CREATE TABLE` emits its foreign keys inline: the only spelling SQLite
426
426
  * has. Counting those as "to add" made MySQL and Postgres ALTER in a
427
427
  * constraint that already existed, and made SQLite schedule a rebuild of a
428
428
  * table that did not exist yet: a fresh `db:sync` announced
@@ -430,7 +430,7 @@ export function calculateForeignKeyDiff(
430
430
  *
431
431
  * Phrased as "being created" rather than "already exists" deliberately. The
432
432
  * inverse defaults to an *empty* set, which reads as "nothing exists" and so
433
- * suppressed every key precisely when the database was new — the case that
433
+ * suppressed every key precisely when the database was new: the case that
434
434
  * exposed this in the first place.
435
435
  */
436
436
  tablesBeingCreated: Set<string> = new Set(),
@@ -439,7 +439,7 @@ export function calculateForeignKeyDiff(
439
439
  const fksToDrop = new Map<string, SyncTypes.ForeignKeyInfo>()
440
440
 
441
441
  // `NO ACTION` on both sides of the comparison, because that is what every
442
- // dialect reports for a key declared without one — so an omitted action and
442
+ // dialect reports for a key declared without one, so an omitted action and
443
443
  // an explicit `NO ACTION` must not read as a difference.
444
444
  const act = (a?: string) => a ?? 'NO ACTION'
445
445
  const sameActions = (
@@ -456,8 +456,8 @@ export function calculateForeignKeyDiff(
456
456
 
457
457
  const existing = dbFks[id]
458
458
  if (existing) {
459
- // Same columns, different behaviour. No dialect alters a referential
460
- // action in place, so the constraint is replaced — and the drop carries
459
+ // Same columns, different behavior. No dialect alters a referential
460
+ // action in place, so the constraint is replaced, and the drop carries
461
461
  // the name the *database* gave it, which is not necessarily the one we
462
462
  // would generate for it.
463
463
  if (!sameActions(existing, fk)) {
@@ -479,14 +479,14 @@ export function calculateForeignKeyDiff(
479
479
  * Table names ordered so a parent is always created before its children.
480
480
  *
481
481
  * Not cosmetic: a foreign key requires the referenced table to exist, so an
482
- * unordered CREATE fails outright — verified against Postgres, which answers
482
+ * unordered CREATE fails outright, verified against Postgres, which answers
483
483
  * `relation "o_parent" does not exist`. Dropping runs in reverse for the
484
484
  * mirror-image reason.
485
485
  *
486
486
  * A cycle cannot be ordered at all. Rather than loop forever or drop tables,
487
487
  * the remainder is appended in declaration order: the foreign key that closes
488
- * the cycle then fails loudly at the database, which is the honest outcome —
489
- * breaking it needs a deferred constraint, which no dialect here spells alike.
488
+ * the cycle then fails loudly at the database, which is the honest outcome.
489
+ * Breaking it needs a deferred constraint, which no dialect here spells alike.
490
490
  */
491
491
  export function orderTablesByDependency(
492
492
  tables: string[],
@@ -9,8 +9,8 @@ const logger = new Logger('db-history')
9
9
  /**
10
10
  * What changed between two applied schemas, by name only.
11
11
  *
12
- * Names, not types — the same restraint `shapesMatch` uses and for the same
13
- * reason: a type comparison here would need dialect normalisation, which is the
12
+ * Names, not types: the same restraint `shapesMatch` uses and for the same
13
+ * reason: a type comparison here would need dialect normalization, which is the
14
14
  * thing the ledger exists to avoid depending on. A summary that says "column
15
15
  * changed" when a Postgres default merely re-rendered itself would be worse
16
16
  * than saying nothing.
@@ -63,7 +63,7 @@ export function isEmptyDiff(d: HistoryDiff): boolean {
63
63
  )
64
64
  }
65
65
 
66
- /** `applied_at` is stored as whole seconds — see `writeLedger`. */
66
+ /** `applied_at` is stored as whole seconds. See `writeLedger`. */
67
67
  export function formatWhen(appliedAt: number): string {
68
68
  if (!Number.isFinite(appliedAt) || appliedAt <= 0) return 'unknown'
69
69
  return new Date(appliedAt * 1000).toISOString().replace('T', ' ').slice(0, 19)
@@ -102,7 +102,7 @@ export function formatEntry(
102
102
  // Only worth saying on rows that predate the payload carrying indexes,
103
103
  // because those are exactly the rows `db:rollback` will refuse.
104
104
  if (entry.indexes === undefined) {
105
- lines.push(' (no index record — written before ledger v2)')
105
+ lines.push(' (no index record: written before ledger v2)')
106
106
  }
107
107
  return lines
108
108
  }
@@ -115,7 +115,7 @@ export class HistoryService {
115
115
  }
116
116
 
117
117
  static printHelp(): void {
118
- // Program output, not a log line — the same call the other commands' usage
118
+ // Program output, not a log line: the same call the other commands' usage
119
119
  // text makes, and one of the two documented `console` exceptions.
120
120
  console.log(`
121
121
  Usage: bun run db:history
package/src/sync/index.ts CHANGED
@@ -15,7 +15,7 @@ const syncMsgs = {
15
15
  'E %rforeign() is declared but not implemented%*: {names}. No adapter emits FOREIGN KEY DDL, so it would be created as a plain index and then re-diffed on every sync. Use index() on the column and enforce the reference in your application.',
16
16
  SCHEMA_NOT_FOUND:
17
17
  'E %rConfigured schema path not found%*: {path}. %yschema%* in server.config.ts must name a file or an orm/ folder that exists; remove it to auto-detect. Generating one from the database? Create the (empty) file first, or run %ydb:sync --migrate%*.',
18
- MIGRATE_SCAFFOLDED: 'I Created %y{dir}%* — the generator owns tables.ts.',
18
+ MIGRATE_SCAFFOLDED: 'I Created %y{dir}%*: the generator owns tables.ts.',
19
19
  MIGRATE_RETIRED:
20
20
  'I Converted to the orm/ folder. The previous %yschema.ts%* was moved to %y{to}%*, not deleted.',
21
21
  } as const
@@ -23,7 +23,7 @@ const syncMsgs = {
23
23
  const MESSAGES = messageLogger(logger, syncMsgs)
24
24
 
25
25
  /**
26
- * `orm/index.ts` — the one file in the folder layout nothing else writes.
26
+ * `orm/index.ts`: the one file in the folder layout nothing else writes.
27
27
  *
28
28
  * `tables.ts` belongs to the generator, and `views.ts` / `indexes.ts` are seeded
29
29
  * by it. This is the re-export barrel plus the type registration, and without
@@ -50,8 +50,8 @@ ${hasViews ? "export * from './views'\n" : ''}${hasIndexes ? "export * from './i
50
50
  type Model = ${model}
51
51
 
52
52
  // Without this block the ORM still runs, untyped: every table and column falls
53
- // back to \`any\`. The framework never imports this file at runtime — schema
54
- // values are loaded by path — so this is purely the type registration.
53
+ // back to \`any\`. The framework never imports this file at runtime (schema
54
+ // values are loaded by path), so this is purely the type registration.
55
55
  declare module '@bakery-framework/orm/schema-registry' {
56
56
  interface SchemaRegistry {
57
57
  schema: {
@@ -72,7 +72,7 @@ export class SyncService {
72
72
  *
73
73
  * Separated from `run()` so the answer is available before anything is
74
74
  * opened. It used to be the last check in `run()`, after `initConfig`,
75
- * `initDB`, `loadSchema` and both fatal guards — so the one flag whose whole
75
+ * `initDB`, `loadSchema` and both fatal guards, so the one flag whose whole
76
76
  * job is to explain the others exited 1 on an unreachable database or a
77
77
  * single `foreign()` declaration, and creating `bakery/server.db` as a side
78
78
  * effect of asking for help.
@@ -82,7 +82,7 @@ export class SyncService {
82
82
  }
83
83
 
84
84
  static printHelp(): void {
85
- // CLI usage text goes to stdout verbatim — it is program output, not a
85
+ // CLI usage text goes to stdout verbatim: it is program output, not a
86
86
  // log line, so it deliberately bypasses the structured logger.
87
87
  console.log(`
88
88
  Usage: bun run db:sync [--migrate] [--choose=db|ts] [--dry-run] [--force-sync] [--help]
@@ -141,7 +141,7 @@ Flags:
141
141
  *
142
142
  * Moved, never deleted: it goes to `bakery/backups/`, beside the copies the
143
143
  * generator already keeps there. `loadSchema` prefers `orm/index.ts`, so a
144
- * leftover `schema.ts` would be *ignored* rather than used — which is the
144
+ * leftover `schema.ts` would be *ignored* rather than used, which is the
145
145
  * quiet kind of wrong, since it looks like the file still describes the app
146
146
  * while nothing reads it.
147
147
  */
@@ -178,7 +178,7 @@ Flags:
178
178
 
179
179
  // A configured path that does not exist is a config error, not an empty
180
180
  // project. Continuing would sync against no schema and then generate a new
181
- // one at the wrong location — with the app's real model still sitting
181
+ // one at the wrong location, with the app's real model still sitting
182
182
  // where the typo missed it.
183
183
  if (loaded.missing) {
184
184
  MESSAGES.SCHEMA_NOT_FOUND({ path: loaded.missing })
@@ -193,12 +193,12 @@ Flags:
193
193
  }
194
194
 
195
195
  // `foreign()` used to abort here, because no adapter emitted FOREIGN KEY
196
- // DDL and the declaration would have become a plain index — referential
196
+ // DDL and the declaration would have become a plain index: referential
197
197
  // integrity in appearance only. All three adapters now emit and read back
198
198
  // real foreign keys, so the guard is gone.
199
199
  //
200
200
  // `findUnsupportedForeignKeys` is kept and still exported *from
201
- // `sync/load`* — it is what a future adapter without support would use to
201
+ // `sync/load`*: it is what a future adapter without support would use to
202
202
  // refuse rather than pretend. It is no longer imported here, which is the
203
203
  // distinction: the function has a reason to exist, the dead import did not.
204
204
 
@@ -209,14 +209,14 @@ Flags:
209
209
  // `--migrate` on a project with no schema at all creates the folder layout
210
210
  // rather than a single `schema.ts`. Adoption is exactly the case where the
211
211
  // folder earns its keep: the generator owns `tables.ts` and regenerating it
212
- // cannot touch the views, indexes and registration beside it — which for an
212
+ // cannot touch the views, indexes and registration beside it, which for an
213
213
  // adopted database is the difference between re-running the command and
214
214
  // hand-restoring what it overwrote.
215
215
  // `--migrate` always lands on the folder layout, including from an existing
216
216
  // single-file `schema.ts`. It used to convert only from *nothing*, which
217
217
  // read as an arbitrary distinction: the reason to prefer the folder is that
218
218
  // the generator owns `tables.ts` and cannot touch the views, indexes and
219
- // registration beside it — and that is worth exactly as much to a project
219
+ // registration beside it, and that is worth exactly as much to a project
220
220
  // that already has a schema as to one that does not.
221
221
  const adopting = SyncService.migrateRequested()
222
222
  const layout = adopting ? 'folder' : loaded.layout
@@ -16,20 +16,20 @@ import type * as SyncTypes from './types'
16
16
  * the table on every single sync.
17
17
  *
18
18
  * Comparing against what we *wrote down* removes that whole class. Two JSON
19
- * objects, no dialect spellings, no normalisation — and it can carry things
19
+ * objects, no dialect spellings, no normalization, and it can carry things
20
20
  * introspection cannot report reliably, such as a `VARCHAR` width.
21
21
  *
22
22
  * **Why it is not simply trusted.** Introspection cannot lie about what exists;
23
23
  * a ledger can. If someone alters the database outside Bakery, the ledger is
24
- * stale, and migrating from a stale premise is worse than a spurious rebuild —
24
+ * stale, and migrating from a stale premise is worse than a spurious rebuild:
25
25
  * it can drop a column somebody added. So the ledger is used only when its
26
26
  * *shape* still matches the live database, and shape is checked by comparing
27
- * table and column names, which needs no normalisation and therefore cannot
27
+ * table and column names, which needs no normalization and therefore cannot
28
28
  * suffer the bugs above. Anything else falls back to introspection.
29
29
  *
30
30
  * One consequence to know: **MySQL commits DDL implicitly**, so the schema
31
31
  * change and the ledger write cannot be one atomic unit there. A crash between
32
- * them leaves the ledger behind, which the shape check then catches — it fails
32
+ * them leaves the ledger behind, which the shape check then catches: it fails
33
33
  * safe, toward introspection.
34
34
  */
35
35
  export const LEDGER_TABLE = '__bakery_schema'
@@ -58,7 +58,7 @@ export interface LedgerEntry {
58
58
  appliedAt: number
59
59
  constraints: SyncTypes.DBConstraints
60
60
  /**
61
- * Absent on rows written before the payload carried indexes — see
61
+ * Absent on rows written before the payload carried indexes. See
62
62
  * {@link parsePayload}. `undefined` means "not recorded", which is not the
63
63
  * same as `{}` ("recorded, and there were none"), and `db:rollback` refuses
64
64
  * the difference rather than guessing.
@@ -72,11 +72,11 @@ export interface LedgerEntry {
72
72
  * v1 rows are a bare `DBConstraints` object; v2 wraps constraints alongside the
73
73
  * indexes that were applied with them. The version is detected by the `v` key
74
74
  * rather than by a column, so existing rows keep working with no migration of
75
- * the migration table — which would be a fine joke and a bad idea.
75
+ * the migration table, which would be a fine joke and a bad idea.
76
76
  *
77
77
  * Indexes were missing from v1 because the ledger only ever fed the *diff*,
78
78
  * which reads constraints. `db:rollback` replays a stored schema as a target,
79
- * and a target with no indexes means "drop every index" — so the payload had to
79
+ * and a target with no indexes means "drop every index", so the payload had to
80
80
  * grow before rollback could be honest.
81
81
  */
82
82
  function parsePayload(
@@ -186,7 +186,7 @@ export async function writeLedger(
186
186
  * Every spelling of the ledger's name that can appear in a constraints object.
187
187
  *
188
188
  * `getConstraints()` camelCases table names, so the table created as
189
- * `__bakery_schema` comes back as `bakerySchema` — matching only the literal
189
+ * `__bakery_schema` comes back as `bakerySchema`: matching only the literal
190
190
  * name silently stripped nothing, and the ledger showed up in its own diff as
191
191
  * a table the schema did not declare. Which then made the shape check fail
192
192
  * forever and the ledger never get used at all.
@@ -213,28 +213,28 @@ export function stripLedger(
213
213
  * live database?
214
214
  *
215
215
  * **Names only, deliberately.** Comparing types or defaults here would need the
216
- * same normalisation the ledger exists to avoid, and a disagreement there is
216
+ * same normalization the ledger exists to avoid, and a disagreement there is
217
217
  * exactly what the ledger is more trustworthy about. Names are names in every
218
218
  * dialect, so this check cannot itself be wrong in the way the others were.
219
219
  *
220
220
  * **But a name has two spellings, and the two sides do not use the same one.**
221
- * The ledger stores the keys of your TypeScript schema — whatever you passed to
222
- * `table()` / `view()` — while `getConstraints()` camelCases everything it reads
221
+ * The ledger stores the keys of your TypeScript schema (whatever you passed to
222
+ * `table()` / `view()`), while `getConstraints()` camelCases everything it reads
223
223
  * back. So a table declared `view('published_posts', …)` is `published_posts` in
224
224
  * the ledger and `publishedPosts` from introspection, and this check called that
225
225
  * a drifted database: *"tables differ (+publishedPosts; -published_posts)"*, on
226
- * a database nothing had touched. Permanently — the spellings never converge, so
226
+ * a database nothing had touched. Permanently: the spellings never converge, so
227
227
  * every later sync re-reported it and the ledger was never used again. Every app
228
228
  * `bun create bakery` generated hit it on the first `db:sync`, because the
229
229
  * generated schema declares exactly that view.
230
230
  *
231
231
  * The damage is quieter than the warning. Falling back to introspection is
232
- * *safe*, so nothing breaks loudly — it just silently withdraws the thing the
232
+ * *safe*, so nothing breaks loudly: it just silently withdraws the thing the
233
233
  * ledger is for. Enum member changes, for one, only migrate when the diff runs
234
234
  * against the ledger (`plan.ledgerSource === 'ledger'`), so on any such app that
235
235
  * feature was inert.
236
236
  *
237
- * Comparing camel-normalised names fixes it at the one place the two spellings
237
+ * Comparing camel-normalized names fixes it at the one place the two spellings
238
238
  * meet. `LEDGER_ALIASES` below is the same bug, found earlier and patched for a
239
239
  * single known name; this is the general form of it.
240
240
  */
@@ -243,7 +243,7 @@ export function shapesMatch(
243
243
  live: SyncTypes.DBConstraints,
244
244
  ): { ok: true } | { ok: false; reason: string } {
245
245
  const meta = (k: string) => k.startsWith('_')
246
- // Compare on the normalised spelling, report the declared one — a diff that
246
+ // Compare on the normalized spelling, report the declared one: a diff that
247
247
  // named tables the reader cannot find in either their schema or their database
248
248
  // would trade one confusion for another.
249
249
  const namesOf = (o: object) => {
@@ -284,13 +284,13 @@ export function shapesMatch(
284
284
  * The ledger stores the keys of your TypeScript schema verbatim; every consumer
285
285
  * of "current state" downstream looks tables up by `Case.camel(name)`, because
286
286
  * that is what `getConstraints()` produces. Handing the raw ledger to the
287
- * planner therefore made every lookup miss — `diffViews` asked for
287
+ * planner therefore made every lookup miss: `diffViews` asked for
288
288
  * `publishedPosts`, the ledger held `published_posts`, and a miss reads as "the
289
289
  * database does not have this view", so the view was recreated on every single
290
290
  * sync. Silent and harmless-looking; a view holds no data, so the only symptom
291
291
  * is churn in the log.
292
292
  *
293
- * Normalising on read rather than on write is deliberate: it repairs the ledgers
293
+ * Normalizing on read rather than on write is deliberate: it repairs the ledgers
294
294
  * already written by earlier versions, which a write-side fix could not.
295
295
  *
296
296
  * Meta keys (`_view`, `_references`, …) are values, not identifiers, and are
@@ -319,7 +319,7 @@ function normalizeLedgerKeys(
319
319
  * database, introspection otherwise.
320
320
  *
321
321
  * Returning the *source* as well as the constraints is what lets the caller say
322
- * out loud which one it used — a sync that quietly changed its mind about where
322
+ * out loud which one it used: a sync that quietly changed its mind about where
323
323
  * truth lives would be very hard to debug later.
324
324
  */
325
325
  export async function resolveCurrentState(
@@ -333,8 +333,8 @@ export async function resolveCurrentState(
333
333
  const live = stripLedger(await adapter.getConstraints())
334
334
  // `--no-ledger`, and it exists because the ledger can be *wrong about types*
335
335
  // in a way `shapesMatch` cannot see. That check compares names only, on
336
- // purpose — comparing types would need the dialect normalisation the ledger
337
- // exists to avoid — so a ledger claiming `VARCHAR(64)` where the column is
336
+ // purpose (comparing types would need the dialect normalization the ledger
337
+ // exists to avoid), so a ledger claiming `VARCHAR(64)` where the column is
338
338
  // really `TEXT` matches its shape and wins the diff. That happens whenever a
339
339
  // sync legitimately concluded "no change needed" and the *rule* it used later
340
340
  // got stricter, which is exactly what adding width to the diff did.
@@ -369,8 +369,8 @@ export async function resolveCurrentState(
369
369
  * Has the database stopped matching what Bakery last applied?
370
370
  *
371
371
  * The same comparison {@link resolveCurrentState} makes, exposed on its own so
372
- * a caller can *report* it. Sync already handles drift correctly — it quietly
373
- * falls back to introspection — but quietly is the problem: a column added by
372
+ * a caller can *report* it. Sync already handles drift correctly (it quietly
373
+ * falls back to introspection), but quietly is the problem: a column added by
374
374
  * hand in production is indistinguishable, from the outside, from a normal run.
375
375
  * This is how you find out.
376
376
  *
@@ -378,7 +378,7 @@ export async function resolveCurrentState(
378
378
  * synced is not "drifted"), or the shape still matches.
379
379
  *
380
380
  * Never throws. It runs at boot, and a database that cannot answer must not be
381
- * the reason a server fails to start — the sync path will raise anything that
381
+ * the reason a server fails to start: the sync path will raise anything that
382
382
  * genuinely matters.
383
383
  */
384
384
  export async function detectDrift(
package/src/sync/load.ts CHANGED
@@ -6,14 +6,14 @@
6
6
  * `orm/index.ts`. A single `schema.ts` at the root still works, so nothing
7
7
  * has to move.
8
8
  *
9
- * The folder is not only tidier — it separates what the generator owns from
9
+ * The folder is not only tidier: it separates what the generator owns from
10
10
  * what a person wrote. `--choose=db` regenerates tables; with everything in
11
11
  * one file it has to rewrite indexes and foreign keys too, and anything
12
12
  * hand-authored alongside them is collateral.
13
13
  *
14
14
  * Both of those are *defaults*, probed in that order from the app's cwd. An
15
- * app that keeps its model somewhere else — `db/`, `src/database/`, a path
16
- * shared with another tool — sets `schema` in `server.config.ts` and this
15
+ * app that keeps its model somewhere else (`db/`, `src/database/`, a path
16
+ * shared with another tool) sets `schema` in `server.config.ts` and this
17
17
  * stops guessing:
18
18
  *
19
19
  * ```ts
@@ -40,7 +40,7 @@ import type * as SyncTypes from './types'
40
40
  * `folder` means `table()` values in `<dir>/schema.ts`, with `<dir>/index.ts`
41
41
  * owning the re-exports and the schema registration. `file` and `none` mean the
42
42
  * single-file `DBInfo` namespace, which carries its own registration block.
43
- * Emitting the wrong one is not cosmetic — see `SchemaBuilder.generate`.
43
+ * Emitting the wrong one is not cosmetic. See `SchemaBuilder.generate`.
44
44
  */
45
45
  export type SchemaLayout = 'folder' | 'file' | 'none'
46
46
 
@@ -53,7 +53,7 @@ export interface LoadedSchema {
53
53
  *
54
54
  * SQL forbids those. MySQL and Postgres refuse the CREATE outright; SQLite
55
55
  * accepts the DDL and then fails *every insert* with `foreign key mismatch`,
56
- * naming two tables and nothing else — so the caller aborts on this rather
56
+ * naming two tables and nothing else, so the caller aborts on this rather
57
57
  * than letting it surface at runtime.
58
58
  */
59
59
  unreferenceable?: string[]
@@ -62,7 +62,7 @@ export interface LoadedSchema {
62
62
  layout: SchemaLayout
63
63
  /**
64
64
  * The path a configured `schema` pointed at that does not exist. Set only
65
- * for that case — never when nothing is configured, since absence is a
65
+ * for that case, never when nothing is configured, since absence is a
66
66
  * supported state for the defaults. The caller must abort rather than sync.
67
67
  */
68
68
  missing?: string
@@ -74,8 +74,8 @@ const CONSTRAINT_TYPES = new Set(['index', 'unique', 'foreign'])
74
74
  * Foreign keys are declarable but not implemented, and the failure is nasty:
75
75
  * no adapter emits FOREIGN KEY DDL, so a declaration is created as an ordinary
76
76
  * index. The next diff then compares `foreign` in TypeScript against `index`
77
- * in the database, decides to drop and re-add it, and — because index drops
78
- * count as destructive — aborts the sync. The dev server stops starting, with
77
+ * in the database, decides to drop and re-add it, and (because index drops
78
+ * count as destructive) aborts the sync. The dev server stops starting, with
79
79
  * nothing pointing at the cause.
80
80
  *
81
81
  * Failing here converts that into one clear message. The alternative would be
@@ -110,7 +110,7 @@ function collectIndexes(module: Record<string, unknown>) {
110
110
  *
111
111
  * Takes the whole config object rather than the field so the one place that
112
112
  * knows the config's shape is here. `unknown` because core's `AppConfig` does
113
- * not declare `schema` yet — `defaultConfig` in `core/core/config.ts` is
113
+ * not declare `schema` yet: `defaultConfig` in `core/core/config.ts` is
114
114
  * annotated `Required<AppConfig>`, so declaring the field there forces a
115
115
  * default value for it, and there is no single path to give: the default is
116
116
  * the *probe*, not a location. Core spreads unknown keys from
@@ -182,7 +182,7 @@ type Resolved =
182
182
  /**
183
183
  * Turn a configured `schema` value into an entry file and a write target.
184
184
  *
185
- * Relative paths resolve against the app's cwd — the framework's own location
185
+ * Relative paths resolve against the app's cwd: the framework's own location
186
186
  * is irrelevant and, once it is an installed package, meaningless.
187
187
  *
188
188
  * A directory means the folder layout. So does a path ending in `index.ts`,
@@ -193,13 +193,13 @@ type Resolved =
193
193
  /**
194
194
  * Where the folder layout's table declarations live.
195
195
  *
196
- * `tables.ts`, beside `views.ts` and `indexes.ts` — one file per kind of
196
+ * `tables.ts`, beside `views.ts` and `indexes.ts`: one file per kind of
197
197
  * declaration, which is the separation the folder layout exists for. It was
198
198
  * `schema.ts`, which read oddly next to its siblings and collided with the
199
199
  * single-file layout's `schema.ts` in conversation.
200
200
  *
201
- * The old name is still honoured when it is the one on disk. Loading never
202
- * cared — that goes through `index.ts`'s re-exports, so any filename works —
201
+ * The old name is still honored when it is the one on disk. Loading never
202
+ * cared: that goes through `index.ts`'s re-exports, so any filename works,
203
203
  * but *generation* writes here, and writing `tables.ts` beside someone's
204
204
  * existing `schema.ts` would leave two files declaring the same tables.
205
205
  */
@@ -280,8 +280,8 @@ export async function loadSchema(
280
280
  * cannot do on its own:
281
281
  *
282
282
  * 1. **Copies the referenced column's type onto the child.** MySQL rejects a
283
- * foreign key whose types do not match exactly — an `INT` child against a
284
- * `BIGINT` parent is refused — and the two declarations are usually pages
283
+ * foreign key whose types do not match exactly (an `INT` child against a
284
+ * `BIGINT` parent is refused), and the two declarations are usually pages
285
285
  * apart. Copying makes the mismatch unrepresentable rather than merely
286
286
  * unlikely. `length` comes along for a `Varchar` key, for the same reason.
287
287
  * 2. **Emits the key into the index map**, which is where `collectForeignKeys`
@@ -321,7 +321,7 @@ export function resolveColumnForeignKeys(
321
321
 
322
322
  for (const [tableName, cols] of Object.entries(constraints)) {
323
323
  // A view cannot carry a foreign key, and `view(name, sourceTable, body)`
324
- // borrows the source table's columns — `_references` included. Without this
324
+ // borrows the source table's columns: `_references` included. Without this
325
325
  // the view gets a key of its own, which no dialect will create, so every
326
326
  // sync plans to add it again: an empty printed plan and a run that never
327
327
  // reports a perfectly synced database.
package/src/sync/plan.ts CHANGED
@@ -30,8 +30,7 @@ export async function buildSyncPlan(
30
30
  dbConstraintsForDiff: {},
31
31
  }
32
32
 
33
- // The one place sync decides what "currently" means. Prefers the ledger —
34
- // what Bakery last applied — and falls back to introspection whenever the
33
+ // The one place sync decides what "currently" means. Prefers the ledger ( // what Bakery last applied), and falls back to introspection whenever the
35
34
  // ledger no longer describes the same tables and columns. See sync/ledger.ts
36
35
  // for why that fallback is the whole safety argument.
37
36
  const current = await resolveCurrentState(adapter, {
@@ -125,7 +125,7 @@ export function handleTableRenames(
125
125
  )
126
126
  // Views are excluded, not merely absent. `initDbTablesMap` skips `_view`
127
127
  // entries, so a view is never in `dbTables` and would look like a table that
128
- // still needs creating — on every run, forever. `evaluateChanges` counts
128
+ // still needs creating: on every run, forever. `evaluateChanges` counts
129
129
  // `unmappedTsTables`, so a schema with a view could never report a perfectly
130
130
  // synced database. The view phase creates them; this set is about tables.
131
131
  plan.unmappedTsTables = new Set(
@@ -26,7 +26,7 @@ const rollbackMsgs = {
26
26
  'W %ySchema #{id} predates the ledger recording indexes%*, so this rollback leaves indexes exactly as they are. Tables and columns roll back; index changes made since then do not.',
27
27
  SCHEMA_REWRITTEN: 'I Rewrote %y{path}%* to match the restored schema.',
28
28
  SCHEMA_KEPT:
29
- 'W %y--keep-schema: schema.ts still describes the newer schema.%* The next %ydb:sync%* — including the one a dev boot runs — will apply it again and undo this rollback. Revert the file yourself, or re-run without the flag.',
29
+ 'W %y--keep-schema: schema.ts still describes the newer schema.%* The next %ydb:sync%* (including the one a dev boot runs) will apply it again and undo this rollback. Revert the file yourself, or re-run without the flag.',
30
30
  DONE: 'I %gRollback complete%*.',
31
31
  } as const
32
32
 
@@ -127,7 +127,7 @@ changes, exactly as db:sync does.
127
127
  })
128
128
 
129
129
  // A v1 row recorded no indexes, and replaying it with an empty index set
130
- // would read as "drop every index" — a silent, permanent performance
130
+ // would read as "drop every index": a silent, permanent performance
131
131
  // change dressed up as a rollback. Restoring the *live* indexes instead
132
132
  // means the plan contains no index work at all, which is the honest
133
133
  // degradation: it does less than asked, and says so.
@@ -163,7 +163,7 @@ changes, exactly as db:sync does.
163
163
  // The whole point of the ledger: a rollback is just a sync whose target is
164
164
  // a schema we already stored. Backups, the destructive-change prompt,
165
165
  // `--dry-run`, `--force-sync` and the new ledger row all come from
166
- // `SyncEngine.run` unchanged — there is no second migration path to keep
166
+ // `SyncEngine.run` unchanged: there is no second migration path to keep
167
167
  // correct, which is the only reason this file is as short as it is.
168
168
  await SyncEngine.run(
169
169
  adapter,
package/src/sync/types.ts CHANGED
@@ -15,7 +15,7 @@ export interface ColumnConstraint {
15
15
  * **Now part of the column diff**, so widening a `Varchar` migrates. This
16
16
  * used to say the opposite, and the reason was sound at the time: it needed
17
17
  * every adapter to report the width back exactly, and any that did not would
18
- * rebuild the table on every sync. That is no longer a guess — all three
18
+ * rebuild the table on every sync. That is no longer a guess: all three
19
19
  * were measured against live servers, and the one real trap (MySQL reporting
20
20
  * `character_maximum_length = 65535` for an unsized `TEXT`) is handled in
21
21
  * `SQLAdapter.sizedTextLength`.
@@ -25,7 +25,7 @@ export interface ColumnConstraint {
25
25
  */
26
26
  length?: number
27
27
  /**
28
- * The permitted values of an enum column — `Field.Enum([...])`.
28
+ * The permitted values of an enum column: `Field.Enum([...])`.
29
29
  *
30
30
  * Not part of the column diff, for exactly the reason `length` is not:
31
31
  * MySQL reports an ENUM's members back in its own spelling, Postgres reports
@@ -55,13 +55,29 @@ export interface IndexConstraint {
55
55
  type: 'index' | 'unique' | 'foreign'
56
56
  table: string
57
57
  cols: string[]
58
+ /**
59
+ * The column names exactly as the database reported them, aligned with
60
+ * `cols` by position. Introspection always fills this; a TS-declared index
61
+ * (`Field.Unique`, `Field.Index`) has no database spelling to carry, which
62
+ * is why it is optional.
63
+ *
64
+ * It exists because `Case.snake` is not the inverse of `Case.camel`
65
+ * (`SKU_code` → `sKUCode` → `s_k_u_code`), so a consumer that needs the raw
66
+ * spelling (a statement, a display) cannot recover it from `cols` and had
67
+ * to rebuild a camel→raw map from the schema's column list. That rebuild was
68
+ * first-wins over collisions: a table holding both `user_id` and `userId`
69
+ * files them under one camel key, and an index over the *second* resolved to
70
+ * the first, a predicate over the wrong column. The adapter knows the real
71
+ * names at parse time; carrying them costs an array.
72
+ */
73
+ rawCols?: string[]
58
74
  /** Set only when `type` is 'foreign'. */
59
75
  refTable?: string
60
76
  refCols?: string[]
61
77
  }
62
78
 
63
79
  /**
64
- * Referential actions, normalised to the SQL spelling.
80
+ * Referential actions, normalized to the SQL spelling.
65
81
  *
66
82
  * One vocabulary for three dialects: MySQL and SQLite report these words back
67
83
  * verbatim, Postgres reports single characters (`c`, `a`, `r`, `n`, `d`) which
@@ -99,7 +115,7 @@ export interface ForeignKeyInfo {
99
115
  *
100
116
  * It lives here rather than beside `buildSyncPlan` because `sync/rename.ts` and
101
117
  * `sync/diff.ts` both mutate a plan and are both imported *by* the planner. A
102
- * home in `plan.ts` would make that pair of edges circular — type-only, and so
118
+ * home in `plan.ts` would make that pair of edges circular: type-only, and so
103
119
  * erased under `verbatimModuleSyntax`, but this repo has been bitten by import
104
120
  * cycles often enough that not creating one is worth more than the adjacency.
105
121
  */