@bakery-framework/orm 1.2.3 → 2.0.0-alpha.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bakery-framework/orm",
3
- "version": "1.2.3",
3
+ "version": "2.0.0-alpha.2",
4
4
  "description": "Bakery database layer: adapters, query builder, schema sync and backup.",
5
5
  "keywords": [
6
6
  "bakery",
@@ -54,6 +54,6 @@
54
54
  "bun": ">=1.3.14"
55
55
  },
56
56
  "dependencies": {
57
- "@bakery-framework/core": "^1.0.0"
57
+ "@bakery-framework/core": "^1.2.3"
58
58
  }
59
59
  }
@@ -768,14 +768,6 @@ export abstract class SQLAdapter {
768
768
  })
769
769
  }
770
770
 
771
- /**
772
- * A `CHECK (col IN (…))` clause restricting a column to a set of values.
773
- *
774
- * Takes the column name because a CHECK has to name it, which is why
775
- * `colDef` grew an optional `column` argument. Values bind nowhere — this is
776
- * DDL — so they are quoted the same way `formatDefault` quotes a string
777
- * default, by doubling the single quote.
778
- */
779
771
  /**
780
772
  * The declared width of a **sized** text column, or `undefined`.
781
773
  *
@@ -804,6 +796,13 @@ export abstract class SQLAdapter {
804
796
  return Number.isInteger(n) && n > 0 ? n : undefined
805
797
  }
806
798
 
799
+ /**
800
+ * A `CHECK (col IN (…))` clause restricting a column to a set of values.
801
+ *
802
+ * Takes the column name because a CHECK has to name it. Values bind nowhere —
803
+ * this is DDL — so they are quoted the same way `formatDefault` quotes a
804
+ * string default, by doubling the single quote.
805
+ */
807
806
  protected enumClause(column: string, values: string[]): string {
808
807
  const list = values
809
808
  .map(v => `'${String(v).replaceAll("'", "''")}'`)
package/src/orm/index.ts CHANGED
@@ -12,9 +12,8 @@
12
12
  * `TypeError: undefined is not an object (evaluating 'Mutation.Insert')`.
13
13
  *
14
14
  * Biome's `organizeImports` sorts these alphabetically and puts `./mutation`
15
- * first. That happened during the 2026-08-09 sweep and cost 12 tests; the
16
- * compiler cannot see it, because the types are fine either way. Hence the
17
- * suppression.
15
+ * first. The compiler cannot see the breakage, because the types are fine
16
+ * either way. Hence the suppression.
18
17
  */
19
18
  // biome-ignore-all assist/source/organizeImports: cycle — see above
20
19
  import { DB } from './query'
package/src/orm/query.ts CHANGED
@@ -1072,6 +1072,16 @@ export namespace DB {
1072
1072
  if (op === 'IS NULL' || op === 'IS NOT NULL') {
1073
1073
  return `${left} ${op}`
1074
1074
  }
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
+ }
1075
1085
  if (op === 'BETWEEN' && Array.isArray(rightArg)) {
1076
1086
  const min = evalOperands(rightArg[0], params, false)
1077
1087
  const max = evalOperands(rightArg[1], params, false)
@@ -36,18 +36,15 @@ import type { MapOf } from '@bakery-framework/core/types'
36
36
 
37
37
  // Augmented by the app; empty until then.
38
38
  //
39
- // **It has to be an `interface`, and the empty body is the point.** Biome's
40
39
  // **`interface`, not `type`, and the empty body is the point.** A type alias
41
40
  // cannot be declaration-merged, so rewriting this to `type X = {}` turns every
42
41
  // app's `declare module '@bakery-framework/orm/schema-registry'` into
43
42
  // `TS2300: Duplicate identifier` and the whole schema registry stops working.
44
43
  //
45
- // Biome's `noBannedTypes` proposed exactly that rewrite and called it a safe
46
- // fix; the 2026-08-09 sweep applied it and `bun run typecheck` was the only
47
- // thing that caught it. That rule is off repo-wide now it had no true
48
- // positives here but `noEmptyInterface` reaches the same construct and
49
- // offers the same fix, so the suppression stays. The compiler is the real
50
- // guard: change this line and `apps/starter` fails to typecheck.
44
+ // Biome's `noBannedTypes` proposes exactly that rewrite and calls it a safe
45
+ // fix. That rule is off repo-wide now, but `noEmptyInterface` reaches the same
46
+ // construct and offers the same fix, so the suppression stays. The compiler is
47
+ // the real guard: change this line and `apps/starter` fails to typecheck.
51
48
  // biome-ignore lint/suspicious/noEmptyInterface: must stay mergeable — above
52
49
  export interface SchemaRegistry {}
53
50
 
@@ -503,21 +503,6 @@ ${body}`
503
503
  return `${result} } as const;\n`
504
504
  }
505
505
 
506
- /**
507
- * The `orm/` folder layout's `schema.ts`: `table()` values and nothing else.
508
- *
509
- * The generator only ever emitted the `DBInfo` namespace, and for a folder
510
- * project the write target is `orm/schema.ts` — which `orm/index.ts`
511
- * re-exports. So a regeneration replaced every `table()` with a namespace
512
- * *and* added a second `declare module '@bakery-framework/orm/schema-registry'` block
513
- * colliding with the one in `index.ts`. It fired on `--choose=db` and, less
514
- * visibly, after any sync involving `old()` wrappers.
515
- *
516
- * Tables only, deliberately: `index.ts` owns the re-exports and the schema
517
- * registration, `indexes.ts` owns the index and unique declarations, and
518
- * neither is the generator's to rewrite. That separation is the reason the
519
- * folder layout exists (see `load.ts`).
520
- */
521
506
  /**
522
507
  * The constraints key a `_references.table` names, or `undefined`.
523
508
  *
@@ -604,6 +589,16 @@ ${body}`
604
589
  : `Field.Foreign(${target})`
605
590
  }
606
591
 
592
+ /**
593
+ * The `orm/` folder layout's `schema.ts`: `table()` values and nothing else.
594
+ *
595
+ * Tables only, deliberately: `index.ts` owns the re-exports and the schema
596
+ * registration, `indexes.ts` owns the index and unique declarations, and
597
+ * neither is the generator's to rewrite. Emitting the single-file `DBInfo`
598
+ * namespace here instead would clobber both and leave two colliding
599
+ * `declare module '@bakery-framework/orm/schema-registry'` blocks. That
600
+ * separation is the reason the folder layout exists (see `load.ts`).
601
+ */
607
602
  private static buildTableModule(
608
603
  constraints: Record<string, any>,
609
604
  adapter: SQLAdapter,
@@ -95,22 +95,14 @@ export const MESSAGES = messageLogger(logger, syncMsgs)
95
95
  /**
96
96
  * Whether a destructive sync in this process needs an explicit `--force-sync`.
97
97
  *
98
- * The second half used to read `process.env.PROD === 'true'`, which could
99
- * never be true: `core/init.ts` installs `PROD` on `process.env` with
100
- * `Object.defineProperties` as a getter returning a **boolean**, so the guard
101
- * on the most destructive operation the framework performs rested entirely on
102
- * `NODE_ENV`. `import.meta.env` is the same object as `process.env` and
103
- * `import.meta.env.PROD` is the framework's idiom for reading these flags.
104
- *
105
- * The dead term was **deleted rather than repaired**, and that is the whole
106
- * decision here. `import.meta.env.PROD` means only "`--dev` is absent", and
107
- * `db:sync` is a separate CLI invocation that never passes `--dev` — so for
108
- * this caller the flag is a constant `true`, carrying no information about the
109
- * environment at all. Activating it would not have made the guard smarter; it
110
- * would have made the `isProd` branch unconditional and left `handleSafetyChecks`'s
111
- * interactive `Proceed with sync?` unreachable for the one workflow it exists
112
- * for. That is dead code traded for different dead code, plus a silent UX
113
- * change to the documented way of applying a schema.
98
+ * **`NODE_ENV` is the only term, deliberately — do not add a `PROD` check back.**
99
+ * There used to be a `process.env.PROD === 'true'` half that could never be
100
+ * true, since `core/init.ts` installs `PROD` as a getter returning a *boolean*.
101
+ * Repairing it rather than deleting it would have been worse: `PROD` means only
102
+ * "`--dev` is absent", and `db:sync` is a separate CLI invocation that never
103
+ * passes `--dev`, so for this caller it is a constant `true`. That would make
104
+ * the `isProd` branch unconditional and leave `handleSafetyChecks`'s interactive
105
+ * `Proceed with sync?` unreachable for the one workflow it exists for.
114
106
  *
115
107
  * `NODE_ENV` is the only term that actually says "deployment", which is what
116
108
  * the guard is protecting against. Set it, and a destructive plan requires
package/src/sync/load.ts CHANGED
@@ -1,8 +1,3 @@
1
- import { Case, fs } from '@bakery-framework/core/utils'
2
- import { Try } from '@bakery-framework/core/utils/common'
3
- import { collectConstraints } from '../define'
4
- import type * as SyncTypes from './types'
5
-
6
1
  /**
7
2
  * Where a project's data model lives, and how it is read.
8
3
  *
@@ -33,6 +28,12 @@ import type * as SyncTypes from './types'
33
28
  * a fresh one at the typo'd path while the real model sits untouched
34
29
  * somewhere else.
35
30
  */
31
+
32
+ import { Case, fs } from '@bakery-framework/core/utils'
33
+ import { Try } from '@bakery-framework/core/utils/common'
34
+ import { collectConstraints } from '../define'
35
+ import type * as SyncTypes from './types'
36
+
36
37
  /**
37
38
  * Which shape the generator must write back.
38
39
  *
package/src/sync/types.ts CHANGED
@@ -60,14 +60,6 @@ export interface IndexConstraint {
60
60
  refCols?: string[]
61
61
  }
62
62
 
63
- /**
64
- * A foreign key as the database reports it.
65
- *
66
- * Identity is the tuple, not the name: SQLite's `PRAGMA foreign_key_list`
67
- * does not return a constraint name at all, so keying on one would make every
68
- * SQLite foreign key look new on every sync — the perpetual-rebuild failure
69
- * this project keeps hitting.
70
- */
71
63
  /**
72
64
  * Referential actions, normalised to the SQL spelling.
73
65
  *
@@ -83,6 +75,13 @@ export type ForeignKeyAction =
83
75
  | 'SET NULL'
84
76
  | 'SET DEFAULT'
85
77
 
78
+ /**
79
+ * A foreign key as the database reports it.
80
+ *
81
+ * Identity is the tuple, not the name: SQLite's `PRAGMA foreign_key_list` does
82
+ * not return a constraint name at all, so keying on one would make every SQLite
83
+ * foreign key look new on every sync.
84
+ */
86
85
  export interface ForeignKeyInfo {
87
86
  table: string
88
87
  cols: string[]