@bakery-framework/core 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.
Files changed (78) hide show
  1. package/README.md +8 -8
  2. package/package.json +2 -5
  3. package/src/cache/lru.ts +2 -2
  4. package/src/cache/shared-db.ts +81 -11
  5. package/src/cache/string.ts +4 -0
  6. package/src/cache/tiered.ts +11 -5
  7. package/src/client/globals.d.ts +7 -7
  8. package/src/client/livereload.ts +2 -2
  9. package/src/client/utils.ts +11 -6
  10. package/src/compiler/compiler.ts +48 -44
  11. package/src/compiler/dev-service.ts +29 -30
  12. package/src/compiler/prompt-tracker.ts +2 -2
  13. package/src/compiler/tsconfig-sync.ts +130 -59
  14. package/src/core/bakery.ts +6 -3
  15. package/src/core/cache-version.ts +68 -18
  16. package/src/core/config.ts +16 -16
  17. package/src/core/context.ts +8 -8
  18. package/src/core/define-route.ts +5 -6
  19. package/src/core/index.ts +19 -7
  20. package/src/core/init.ts +38 -35
  21. package/src/core/jsx.ts +19 -4
  22. package/src/core/paths.ts +3 -1
  23. package/src/core/plugins.ts +15 -4
  24. package/src/core/port.ts +6 -6
  25. package/src/global.d.ts +36 -18
  26. package/src/handlers/assets/google-font.ts +6 -6
  27. package/src/handlers/assets/image.ts +36 -3
  28. package/src/handlers/assets/nm.ts +7 -7
  29. package/src/handlers/assets/static.ts +55 -12
  30. package/src/handlers/assets/ts.ts +3 -3
  31. package/src/handlers/assets/tsx.ts +1 -1
  32. package/src/handlers/assets/virtual-asset.ts +1 -1
  33. package/src/handlers/core/$base.ts +24 -8
  34. package/src/handlers/core/$dynamic.ts +66 -23
  35. package/src/handlers/core/$error.ts +29 -17
  36. package/src/handlers/core/$middleware.ts +6 -6
  37. package/src/handlers/core/$mounts.ts +2 -2
  38. package/src/handlers/core/$registry.ts +16 -6
  39. package/src/handlers/core/$routing.ts +18 -19
  40. package/src/handlers/core/$static.ts +11 -12
  41. package/src/handlers/routes/api.ts +5 -5
  42. package/src/handlers/routes/html.ts +2 -2
  43. package/src/handlers/routes/proxy.ts +1 -1
  44. package/src/logger/clients.ts +2 -2
  45. package/src/logger/logger.ts +4 -4
  46. package/src/logger/serve-log.ts +32 -18
  47. package/src/plugins/index.ts +1 -1
  48. package/src/plugins/routes.ts +7 -7
  49. package/src/plugins/types.ts +13 -11
  50. package/src/router.ts +12 -12
  51. package/src/session.ts +41 -23
  52. package/src/shared.d.ts +5 -5
  53. package/src/startup.ts +3 -3
  54. package/src/types.d.ts +10 -10
  55. package/src/utils/common/case.ts +1 -1
  56. package/src/utils/common/match.ts +1 -1
  57. package/src/utils/common/misc.ts +1 -1
  58. package/src/utils/constants.ts +15 -15
  59. package/src/utils/fs.ts +97 -39
  60. package/src/utils/http/authorize.ts +24 -17
  61. package/src/utils/http/cors.ts +6 -6
  62. package/src/utils/http/credential.ts +4 -4
  63. package/src/utils/http/csrf.ts +6 -7
  64. package/src/utils/http/dom.ts +9 -9
  65. package/src/utils/http/etag.ts +54 -13
  66. package/src/utils/http/html.ts +29 -20
  67. package/src/utils/http/ip.ts +1 -1
  68. package/src/utils/http/sse.ts +11 -11
  69. package/src/utils/http/url.ts +4 -4
  70. package/src/utils/http/validate.ts +3 -4
  71. package/src/utils/isomorphic/escape.ts +1 -1
  72. package/src/utils/isomorphic/is.ts +2 -2
  73. package/src/utils/isomorphic/misc.ts +1 -1
  74. package/src/utils/isomorphic/stringify.ts +2 -3
  75. package/src/utils/isomorphic/try.ts +1 -1
  76. package/src/utils/shared-pool.ts +23 -16
  77. package/tsconfig.app.json +3 -3
  78. package/tsconfig.vue.json +3 -3
@@ -20,13 +20,13 @@ const RX_IMPORT =
20
20
  * evaluation.
21
21
  *
22
22
  * This used to be a top-level `await` reading `<cwd>/package.json`, which put a
23
- * filesystem read on the boot path of every process that loads this module —
23
+ * filesystem read on the boot path of every process that loads this module,
24
24
  * and `handlers/assets/ts.ts` imports it, so that is every worker and every dev
25
25
  * restart. The value it produces is needed only when something actually
26
26
  * compiles; a production process serving out of a warm compile cache never
27
27
  * needs it at all.
28
28
  *
29
- * Memoised as a promise, not a value, so two concurrent first compiles share
29
+ * Memoized as a promise, not a value, so two concurrent first compiles share
30
30
  * one read instead of racing to do it twice.
31
31
  */
32
32
  let definesPromise: Promise<MapOf<string>> | null = null
@@ -37,8 +37,7 @@ async function buildDefines(): Promise<MapOf<string>> {
37
37
  const file = await Bun.file(`${process.cwd()}/package.json`).json()
38
38
  if (file?.version) bakeryVersion = file.version
39
39
  } catch {
40
- // No package.json in cwd, or it is unreadable. The version is cosmetic —
41
- // it lands in a banner and a build define — so the default above stands.
40
+ // No package.json in cwd, or it is unreadable. The version is cosmetic ( // it lands in a banner and a build define), so the default above stands.
42
41
  }
43
42
 
44
43
  return {
@@ -80,7 +79,13 @@ function nextVirtualId(): string {
80
79
  }
81
80
 
82
81
  function preprocessImports(source: string, filePath: fs.AbsolutePath): string {
83
- const fileDir = fs.resolve(filePath)
82
+ // The file's **directory**, not the file. A relative import resolves against
83
+ // the directory containing the importer, and resolving against the path
84
+ // itself produced `…/src/entry.ts/a.css`, which exists nowhere, so every
85
+ // `import './x.css'` from a compiled module registered a virtual asset that
86
+ // could only 404. It has been wrong since before the workspace split, with
87
+ // no test and neither app using the feature.
88
+ const fileDir = fs.dirname(filePath)
84
89
 
85
90
  const matches = [...source.matchAll(RX_IMPORT)]
86
91
 
@@ -91,7 +96,7 @@ function preprocessImports(source: string, filePath: fs.AbsolutePath): string {
91
96
 
92
97
  // The key is the bare id, never the `/_virtual/` URL. Storing the full URL
93
98
  // meant getKey() returned an already-prefixed value that got prefixed again
94
- // on the next compile — which both threw a StringCache collision and left
99
+ // on the next compile, which both threw a StringCache collision and left
95
100
  // VirtualAssetHandler (which looks up by bare id) unable to resolve it.
96
101
  const id =
97
102
  Strings.getKey(assetPath) || `${Date.now()}_${nextVirtualId()}.${ext}`
@@ -113,15 +118,15 @@ function preprocessImports(source: string, filePath: fs.AbsolutePath): string {
113
118
  * Compile `source`, naming `path` so a failure can say where it happened.
114
119
  *
115
120
  * The two branches are not symmetric on purpose. Without a path there is no
116
- * file to name and no caller that can render a 500 — the Vue plugin compiles
117
- * fragments of an SFC it has already parsed — so a failure logs and hands the
121
+ * file to name and no caller that can render a 500 (the Vue plugin compiles
122
+ * fragments of an SFC it has already parsed), so a failure logs and hands the
118
123
  * source back. With a path the failure is a served request, and the answer is
119
124
  * `null`: `TSHandler` turns that into the 500 it has always had a branch for.
120
125
  *
121
126
  * It used to be neither. The transpiler was wrapped only on the pathless
122
127
  * branch, so a syntax error in a `.ts` asset threw straight past `compile()`,
123
- * past `TSHandler` — leaving its `'Compilation Failed'` arm permanently
124
- * unreachable — and into the worker's catch-all, which printed
128
+ * past `TSHandler` (leaving its `'Compilation Failed'` arm permanently
129
+ * unreachable), and into the worker's catch-all, which printed
125
130
  * `Unhandled Server Error: <message>` with no file and no line while the client
126
131
  * got `An unexpected error occurred.` A total blackout for a one-character typo.
127
132
  */
@@ -149,8 +154,7 @@ export async function compileText(source: string, path?: fs.AbsolutePath) {
149
154
 
150
155
  if (failed) {
151
156
  // `errorWithPosition`, not `errorMsg`: the thrown `BuildMessage` has no
152
- // stack, so `errorMsg` degrades to the bare message and the line number —
153
- // the only thing that makes this actionable — is dropped. The file comes
157
+ // stack, so `errorMsg` degrades to the bare message and the line number ( // the only thing that makes this actionable) is dropped. The file comes
154
158
  // from `{file}`; the diagnostic's own `position.file` is `input.ts`,
155
159
  // because `transform()` was handed a string.
156
160
  compLog.COMPILE_FAIL({ file: path, error: errorWithPosition(failed) })
@@ -165,7 +169,7 @@ export async function compileText(source: string, path?: fs.AbsolutePath) {
165
169
  // relative directory imports went second, once `ts.test.ts` pinned that the
166
170
  // handler already resolves `/lib`, `/lib.js` and their nested forms to the
167
171
  // directory's index server-side. Resolution belongs to the import map and
168
- // the handlers — the compiler only transpiles.
172
+ // the handlers: the compiler only transpiles.
169
173
  return await PluginHooks.onCompile(transformed!, path)
170
174
  }
171
175
 
@@ -190,7 +194,7 @@ type CompileResult = {
190
194
  * `export default …` and nothing else.
191
195
  *
192
196
  * Not all CJS: `exports.greet = …` is statically analysable and Bun emits a real
193
- * named export for it. It is the whole-object form — `module.exports = { … }` —
197
+ * named export for it. It is the whole-object form: `module.exports = { … }`,
194
198
  * whose members cannot be known without running the module, and that is the one
195
199
  * that breaks.
196
200
  *
@@ -208,7 +212,7 @@ type CompileResult = {
208
212
  /**
209
213
  * Bun's CJS wrapper, in a spelling minification cannot destroy.
210
214
  *
211
- * The obvious check — `content.includes('__commonJS')` — held in dev and
215
+ * The obvious check: `content.includes('__commonJS')`: held in dev and
212
216
  * silently never matched in PROD, where minification renames the helper to a
213
217
  * single letter. The consequence was the worst kind of split: named imports
214
218
  * of CJS packages worked all through development and broke only in the
@@ -220,7 +224,7 @@ const RX_CJS_WRAPPER = /\{\s*exports\s*:\s*\{\s*\}\s*\}/
220
224
  /**
221
225
  * A `module.exports = { … }` assignment, whatever the module variable is
222
226
  * called after minification (`n.exports={…}`). The helper's own
223
- * `mod.exports);` never matches — no `= {` follows it.
227
+ * `mod.exports);` never matches: no `= {` follows it.
224
228
  */
225
229
  const RX_MODULE_EXPORTS = /[A-Za-z_$][\w$]*\.exports\s*=\s*\{/g
226
230
 
@@ -241,7 +245,7 @@ export function isCjsDefaultOnly(content: string): boolean {
241
245
  * A bundle consisting of nothing but a non-empty `export { … }` list.
242
246
  *
243
247
  * Such a file names bindings that were never declared, so every one of them is
244
- * a `ReferenceError` the moment the browser evaluates it — and `Bun.build`
248
+ * a `ReferenceError` the moment the browser evaluates it, and `Bun.build`
245
249
  * reports it as **`success: true` with zero diagnostics**.
246
250
  *
247
251
  * The cause is `sideEffects`, and it is not specific to one package. When the
@@ -260,11 +264,11 @@ export function isCjsDefaultOnly(content: string): boolean {
260
264
  * `["./dist/attach-styles.js", "./dist/assets/*.css.js"]`, so its barrel
261
265
  * bundles to 3,549 bytes of pure export list and throws `AggregateError: 189
262
266
  * errors` on import. Most modern libraries set `sideEffects: false`, so any
263
- * re-export barrel among them is a candidate — this is a wide class, not a
267
+ * re-export barrel among them is a candidate. This is a wide class, not a
264
268
  * single broken package.
265
269
  *
266
- * `bundleReExportShim` repairs it; this only recognises it. `export {}` on its
267
- * own is a legal empty module and is not flagged — the list has to name
270
+ * `bundleReExportShim` repairs it; this only recognizes it. `export {}` on its
271
+ * own is a legal empty module and is not flagged: the list has to name
268
272
  * something for the file to be self-contradictory.
269
273
  */
270
274
  export function isEmptyExportList(content: string): boolean {
@@ -280,8 +284,8 @@ export function isEmptyExportList(content: string): boolean {
280
284
  *
281
285
  * The `sideEffects` drop described on {@link isEmptyExportList} keys on the
282
286
  * *entry* being inside the offending package. Re-exporting the very same file
283
- * from a module that is not — the shim lands in `.cache/`, which is never
284
- * inside `node_modules` — leaves the tree-shaker with an entry it has no
287
+ * from a module that is not: the shim lands in `.cache/`, which is never
288
+ * inside `node_modules`: leaves the tree-shaker with an entry it has no
285
289
  * manifest for, and the imports survive. Measured on `@vue-material/core`:
286
290
  * 3,549 bytes of husk becomes a 428,470-byte bundle with all 189 exports.
287
291
  *
@@ -290,16 +294,16 @@ export function isEmptyExportList(content: string): boolean {
290
294
  * than a no-op. So: try with it, fall back to without.
291
295
  *
292
296
  * **Every installed package is `external`, and that is load-bearing rather than
293
- * an optimisation.** Left to itself the shim inlines the whole reachable tree —
297
+ * an optimization.** Left to itself the shim inlines the whole reachable tree:
294
298
  * `@vue-material/core` came out at 428KB with `vue` baked in, and `vue` is a
295
299
  * *peer* dependency the app resolves for itself. Two Vue runtimes in one page
296
300
  * is not a size problem, it is broken reactivity and a duplicated component
297
- * registry. Externalised, each dependency stays a bare specifier that the
301
+ * registry. Externalized, each dependency stays a bare specifier that the
298
302
  * import map sends to its own `/_nm/<dep>`, so there is exactly one copy of
299
303
  * each; the same bundle drops to 260KB.
300
304
  *
301
- * Externalising *installed packages* specifically, rather than Bun's
302
- * `packages: 'external'`, because that switch also externalises `node:*` —
305
+ * Externalizing *installed packages* specifically, rather than Bun's
306
+ * `packages: 'external'`, because that switch also externalizes `node:*`,
303
307
  * turning a builtin Bun would otherwise polyfill for the browser into a bare
304
308
  * import nothing can resolve.
305
309
  */
@@ -324,7 +328,7 @@ async function bundleReExportShim(
324
328
  if (writeErr) return null
325
329
 
326
330
  // Wrapped, because naming a `default` the package does not export is a
327
- // *throw* from `Bun.build`, not a `success: false` — and that throw is the
331
+ // *throw* from `Bun.build`, not a `success: false`, and that throw is the
328
332
  // expected outcome of the first attempt for any package without one.
329
333
  const [buildErr, build] = await Try.catch(() =>
330
334
  Bun.build({
@@ -356,7 +360,7 @@ const RESERVED_EXPORT_NAMES = new Set(['default', '__esModule'])
356
360
  * How long the export probe may take before it is killed.
357
361
  *
358
362
  * Generous, because it pays a process start and a module evaluation, and it runs
359
- * once per package — the result is cached with the bundle. Short enough that a
363
+ * once per package: the result is cached with the bundle. Short enough that a
360
364
  * package which hangs on import costs a pause rather than a wedged server.
361
365
  */
362
366
  const CJS_PROBE_TIMEOUT_MS = 5_000
@@ -421,7 +425,7 @@ function objectLiteralKeys(src: string, open: number): string[] | null {
421
425
 
422
426
  if (c === ':') {
423
427
  take(i)
424
- // `{ a: expr, b }` — the value is skipped wholesale so a comma inside it
428
+ // `{ a: expr, b }`. The value is skipped wholesale so a comma inside it
425
429
  // cannot be mistaken for the next key.
426
430
  i = endOfValue(src, i + 1)
427
431
  if (src[i] !== ',') return keys
@@ -435,25 +439,25 @@ function objectLiteralKeys(src: string, open: number): string[] | null {
435
439
  /**
436
440
  * Export names read out of the **bundled** output, without running anything.
437
441
  *
438
- * This is the job `cjs-module-lexer` does for Node and Vite: recognise a small
442
+ * This is the job `cjs-module-lexer` does for Node and Vite: recognize a small
439
443
  * set of known-safe `module.exports` shapes and answer nothing for the rest. It
440
- * is deliberately one shape here — `module.exports = { … }`, the object literal
441
- * — because that is the only form that reaches this code path at all. Bun
444
+ * is deliberately one shape here: `module.exports = { … }`, the object literal
445
+ *, because that is the only form that reaches this code path at all. Bun
442
446
  * already emits real named exports for `exports.name = …`, so a package written
443
447
  * that way never gets here.
444
448
  *
445
449
  * Reading the *bundle* rather than the source matters. Bun has already
446
- * normalised the module into a `__commonJS((exports, module) => { … })` wrapper,
450
+ * normalized the module into a `__commonJS((exports, module) => { … })` wrapper,
447
451
  * so there is a single known shape to look inside. The string-literal hazard
448
452
  * that made the old import rewriter corrupt user code is reduced, not
449
- * eliminated — a string containing `module.exports = {` would still fool this —
453
+ * eliminated: a string containing `module.exports = {` would still fool this,
450
454
  * which is why a failed or empty reading falls through to the probe instead of
451
455
  * being trusted as "no exports".
452
456
  */
453
457
  export function staticCjsExportNames(bundled: string): string[] {
454
458
  // `RX_MODULE_EXPORTS`, not the literal `module.exports = {`: minification
455
459
  // renames the module variable and drops the spaces (`n.exports={`), and the
456
- // literal spelling made this reader dev-only — the probe silently took over
460
+ // literal spelling made this reader dev-only, the probe silently took over
457
461
  // every PROD bundle.
458
462
  const matches = [...bundled.matchAll(RX_MODULE_EXPORTS)]
459
463
  if (matches.length === 0) return []
@@ -483,12 +487,12 @@ export function staticCjsExportNames(bundled: string): string[] {
483
487
  * The fallback for whatever `staticCjsExportNames` cannot see. Something has to
484
488
  * run the module, and the question is only *where*: it used to be here, which
485
489
  * meant an arbitrary `node_modules` package executing inside the server process
486
- * — in production as well as dev — free to start a timer, open a socket, or
490
+ * (in production as well as dev) free to start a timer, open a socket, or
487
491
  * mutate a global that then outlives the request that caused it.
488
492
  *
489
493
  * A child process does not make execution safe, and nothing can: a module-scope
490
494
  * write to the filesystem still happens. What it buys is containment of
491
- * everything *in-process* — crashes, hangs, globals, listeners — and a package
495
+ * everything *in-process* (crashes, hangs, globals, listeners), and a package
492
496
  * that hangs on import is killed by the timeout rather than wedging a bundle.
493
497
  */
494
498
  async function cjsExportNames(path: string): Promise<string[]> {
@@ -511,7 +515,7 @@ async function cjsExportNames(path: string): Promise<string[]> {
511
515
  proc.kill()
512
516
 
513
517
  // **The exit code is deliberately not consulted.** A package that leaves a
514
- // timer or a listener running — which plenty do at module scope — has
518
+ // timer or a listener running (which plenty do at module scope) has
515
519
  // already printed its answer and then simply fails to exit, so the probe
516
520
  // kills it and the exit code reports the kill. Requiring a clean exit threw
517
521
  // away a correct result and fell back to default-only, which is the very
@@ -539,8 +543,8 @@ async function cjsExportNames(path: string): Promise<string[]> {
539
543
  * export const greet = __cjs.greet
540
544
  *
541
545
  * That shim is what gets bundled, so the browser gets a module that really does
542
- * provide `greet`. A package that throws on import — one touching `window` at
543
- * module scope, say — yields no names, and the caller keeps the plain bundle it
546
+ * provide `greet`. A package that throws on import (one touching `window` at
547
+ * module scope, say) yields no names, and the caller keeps the plain bundle it
544
548
  * already has.
545
549
  *
546
550
  * Non-identifier keys are skipped rather than mangled: `module.exports['a-b']`
@@ -605,14 +609,14 @@ export async function bundleModule(
605
609
  //
606
610
  // That is reachable from outside this repo. `PROD` is an accessor on
607
611
  // `process.env`, and Bun's `process.env` proxy stringifies on write, so an
608
- // embedder that assigns the flag at all — even `process.env.PROD = true` —
612
+ // embedder that assigns the flag at all, even `process.env.PROD = true`:
609
613
  // leaves a string behind. The suite's own instance of this is fixed at the
610
614
  // source (see `setModeFlag` in `src/tests/fixtures.ts`); this guards the
611
615
  // writes we do not own.
612
616
  minify: Boolean(import.meta.env.PROD),
613
617
  define: await getDefines(),
614
618
  // Installed packages stay bare imports and resolve through the import map
615
- // — which covers every one of them by construction (`initImportMap`). The
619
+ //, which covers every one of them by construction (`initImportMap`). The
616
620
  // alternative was measured on `@vue-material/core`: each `/_nm/` bundle
617
621
  // inlined its own copy of `vue`, and two Vue instances in one page is
618
622
  // broken reactivity, not a size problem. A ref created by one Vue is
@@ -625,7 +629,7 @@ export async function bundleModule(
625
629
  const content = await build.outputs[0].text()
626
630
 
627
631
  // A bundle that cannot possibly work is not a success, whatever `build`
628
- // says — see `isEmptyExportList`. Repairable in the common case, so try
632
+ // says. See `isEmptyExportList`. Repairable in the common case, so try
629
633
  // that before refusing.
630
634
  if (isEmptyExportList(content)) {
631
635
  const repaired = await bundleReExportShim(path, await getDefines())
@@ -639,7 +643,7 @@ export async function bundleModule(
639
643
  }
640
644
 
641
645
  // Only for the shape that breaks, and only after the cheap build has proved
642
- // it is that shape — so nothing is imported speculatively.
646
+ // it is that shape, so nothing is imported speculatively.
643
647
  if (isCjsDefaultOnly(content)) {
644
648
  const interop = await bundleCjsWithNamedExports(
645
649
  path,
@@ -29,7 +29,7 @@ export function notifySockets(server: any, filename: string) {
29
29
  * `{type: 'error', title, body}`. client/livereload.ts renders it as an
30
30
  * overlay and removes it on the next successful reload frame.
31
31
  *
32
- * Legacy reload frames stay plain filename strings — the client distinguishes
32
+ * Legacy reload frames stay plain filename strings: the client distinguishes
33
33
  * the two by a leading `{`, so this must always be JSON and filenames must
34
34
  * never be. Paths that cannot reach a live socket at all (worker dead, boot
35
35
  * failure before `Bun.serve`) are covered client-side by the disconnect
@@ -38,7 +38,7 @@ export function notifySockets(server: any, filename: string) {
38
38
  * Two producers reach here. The watcher's `catch` below sends internal watcher
39
39
  * faults directly; every *request-time* failure arrives through `emitDevError`
40
40
  * and the sink `startCompileService` installs. That second producer is the
41
- * point of the whole apparatus — for a long time only the first existed, so the
41
+ * point of the whole apparatus: for a long time only the first existed, so the
42
42
  * overlay was reachable solely on an IO fault inside the watcher loop and never
43
43
  * appeared for the failures developers actually hit.
44
44
  */
@@ -65,7 +65,7 @@ export const DEV_ERROR_PLUGIN = 'bakery:dev-error-overlay'
65
65
  *
66
66
  * 4xx is deliberately excluded. A 404 for a mistyped URL or an absent
67
67
  * `favicon.ico` is the router working as designed, and covering the page for it
68
- * would train developers to dismiss the overlay reflexively — which is how the
68
+ * would train developers to dismiss the overlay reflexively, which is how the
69
69
  * feature dies a second time. 5xx is the band that means "your code, or the
70
70
  * framework, broke", and that is the band this overlay exists for.
71
71
  *
@@ -113,7 +113,7 @@ export function formatDevErrorFrame(
113
113
  const body = String(error?.errorBody ?? '')
114
114
 
115
115
  return {
116
- title: path ? `${code} ${text} — ${path}` : `${code} ${text}`,
116
+ title: path ? `${code} ${text}: ${path}` : `${code} ${text}`,
117
117
  body:
118
118
  body.length > MAX_DEV_ERROR_BODY
119
119
  ? `${body.slice(0, MAX_DEV_ERROR_BODY)}\n… truncated`
@@ -132,7 +132,7 @@ export function formatDevErrorFrame(
132
132
  * emits through a hook that is a no-op until it does.
133
133
  *
134
134
  * Null in PROD, in a cluster worker, and in every test that does not install
135
- * one — `emitDevError` is then a single optional call and nothing else runs.
135
+ * one: `emitDevError` is then a single optional call and nothing else runs.
136
136
  */
137
137
  let devErrorSink: ((title: string, body: string) => void) | null = null
138
138
 
@@ -154,14 +154,14 @@ export function emitDevError(title: string, body: string): void {
154
154
  * `ServerPlugin.onError` is the framework's own observation point for request
155
155
  * errors: `router.ts handleRequestError` calls it first, before the plugin
156
156
  * response check, before `config.onError`, and before the error-handler
157
- * registry — so it sees every failure the error path sees (thrown handler,
157
+ * registry, so it sees every failure the error path sees (thrown handler,
158
158
  * 5xx `Response`, and Bun's `error()` callback), with `errorBody` still holding
159
159
  * the full stack. Using it is what lets the overlay be wired without router.ts
160
160
  * or the error handlers ever hearing about the compiler.
161
161
  *
162
162
  * It returns `undefined` unconditionally. `PluginHooks.onError` treats that as
163
163
  * "no opinion" and carries on down the list, so registering this cannot change
164
- * which page a failing request answers with — it only observes.
164
+ * which page a failing request answers with: it only observes.
165
165
  */
166
166
  export function createDevErrorPlugin(): ServerPlugin {
167
167
  return {
@@ -186,7 +186,7 @@ export function createDevErrorPlugin(): ServerPlugin {
186
186
  * running it first costs the others nothing.
187
187
  *
188
188
  * The array is mutated in place because `initConfig` hands back a frozen
189
- * config — `config.plugins = [...]` would throw, and the array itself is the
189
+ * config: `config.plugins = [...]` would throw, and the array itself is the
190
190
  * only writable seam. Host configs are built with `{...base}` and never
191
191
  * override `plugins`, so every host shares this exact array and one
192
192
  * registration covers all of them. Idempotent by name, because a re-registration
@@ -339,7 +339,7 @@ export async function handleDevMaster(): Promise<never> {
339
339
  ...inspectArgs,
340
340
  // Respawn whatever entry started this process. The entry now lives in
341
341
  // @bakery-framework/cli, and core must not reach into a package that depends on
342
- // it — argv[1] is both correct and dependency-free.
342
+ // it: argv[1] is both correct and dependency-free.
343
343
  process.argv[1],
344
344
  '--dev',
345
345
  '--dev-worker',
@@ -386,8 +386,8 @@ export async function handleDevMaster(): Promise<never> {
386
386
  serveLog.RESTART_REQ()
387
387
  // Clear only when the exiting worker had actually come up: then the
388
388
  // scrollback is routine request logs and clearing keeps the console
389
- // readable. A worker that exited before binding — the sync engine also
390
- // exits 42 mid-boot (orm/sync/engine.ts) — printed the only record of
389
+ // readable. A worker that exited before binding: the sync engine also
390
+ // exits 42 mid-boot (orm/sync/engine.ts): printed the only record of
391
391
  // why, and wiping it left the developer staring at a blank screen where
392
392
  // the error had just been. Keeping scrollback is chosen over
393
393
  // capture-and-reprint because worker stdio is inherited, not piped.
@@ -417,7 +417,7 @@ const fileTypeGlob = Glob.fromExt([
417
417
  'jsx',
418
418
  'vue',
419
419
  ])
420
- // `.tsx`/`.jsx` are here — the cheap path — and not in `prioFilesGlob`:
420
+ // `.tsx`/`.jsx` are here (the cheap path), and not in `prioFilesGlob`:
421
421
  // TSXHandler re-imports the page module with `?v=<mtime>` (handlers/assets/
422
422
  // tsx.ts, same mechanism as ApiHandler), so an edited page never serves stale
423
423
  // and the route-cache flush + browser reload below is all a page edit needs.
@@ -430,7 +430,7 @@ const watchIgnores = Glob.strings(
430
430
  // The framework's own cache directory (`Bakery.cacheDir`).
431
431
  '**/.cache/**/*',
432
432
  // `bakery/` (`Bakery.dataDir`) holds the database plus
433
- // `backups/schema.<timestamp>.ts` files the DB backup writes at runtime —
433
+ // `backups/schema.<timestamp>.ts` files the DB backup writes at runtime,
434
434
  // without this, taking a backup while the dev server ran flushed the route
435
435
  // cache and reloaded the browser. It is un-dotted on purpose (see
436
436
  // `core/bakery.ts`), which is exactly why it has to be named here: a
@@ -443,20 +443,20 @@ const watchIgnores = Glob.strings(
443
443
  )
444
444
 
445
445
  // `'api/**/*'` used to be a third entry here, left over from when API routes
446
- // lived at the repo root. They live under `<root>/api` now — `src/api` by
447
- // default — so the pattern matched nothing and API routes never triggered the
446
+ // lived at the repo root. They live under `<root>/api` now (`src/api` by
447
+ // default), so the pattern matched nothing and API routes never triggered the
448
448
  // restart below. The route *file* still hot-reloaded through the `?v=<mtime>`
449
449
  // cache-buster, which is why this looked fine: only its imports went stale.
450
450
  // The replacement is `isBackendPriorityFile`, which asks the config where the
451
451
  // api directory actually is instead of hard-coding it.
452
452
  //
453
- // `'**/*.tsx'` was the second entry here, and it made every page edit — the
454
- // most common edit there is — cost a full process restart (config, plugins,
453
+ // `'**/*.tsx'` was the second entry here, and it made every page edit (the
454
+ // most common edit there is) cost a full process restart (config, plugins,
455
455
  // import map, tsconfig sync, schema-sync check). The only reason it needed a
456
456
  // restart was Bun's module registry caching the imported page module, and
457
457
  // TSXHandler now busts that per request in dev exactly as ApiHandler always
458
458
  // has. Pages take the cheap path via `tsScriptGlob` above. The one thing the
459
- // restart still bought — flushing *components* a page imports — is a
459
+ // restart still bought (flushing *components* a page imports) is a
460
460
  // documented limitation (docs/getting-started/first-app.md): touch
461
461
  // server.config.ts or restart when editing a shared Layout.tsx.
462
462
  const prioFilesGlob = Glob.strings('server.config.ts')
@@ -465,8 +465,8 @@ const prioFilesGlob = Glob.strings('server.config.ts')
465
465
  * Whether a change to `filePath` requires a full backend restart rather than a
466
466
  * livereload notification.
467
467
  *
468
- * `filePath` is watcher-relative — i.e. relative to the app's cwd, with forward
469
- * slashes — so the configured api root is converted to the same shape.
468
+ * `filePath` is watcher-relative, i.e. relative to the app's cwd, with forward
469
+ * slashes, so the configured api root is converted to the same shape.
470
470
  *
471
471
  * Host-specific roots are not consulted: a `hosts` entry can override `root`,
472
472
  * but the watcher runs outside any request and so has no host context. The
@@ -490,8 +490,8 @@ export function isBackendPriorityFile(filePath: string): boolean {
490
490
  * restart time. cli/dev.ts now hashes the schema sources, records the hash
491
491
  * under `.cache/` after a *successful* sync only, and consults this
492
492
  * before the next one. Pure so the fail-closed branches are testable: any
493
- * indeterminate state (`currentHash: null` — sources unreadable; `storedHash:
494
- * null` — no successful sync on record) must sync, never skip.
493
+ * indeterminate state (`currentHash: null` (sources unreadable; `storedHash:
494
+ * null`) no successful sync on record) must sync, never skip.
495
495
  */
496
496
  export function classifySchemaSync(opts: {
497
497
  /** `--sync` / `-s` was passed: the developer asked, so sync regardless. */
@@ -534,7 +534,7 @@ const MODULE_ROUTE_EXT = /\.(tsx|jsx)$/
534
534
  *
535
535
  * Editing a `.tsx` page is instant: the page module carries a `?v=<mtime>`
536
536
  * specifier, so Bun re-imports it and the change is served on the next
537
- * request. Creating one is not — Bun caches the *directory listing* it
537
+ * request. Creating one is not: Bun caches the *directory listing* it
538
538
  * resolved against, so a file that did not exist at boot fails to import at
539
539
  * any specifier, including a freshly-stamped one. The page 500s with
540
540
  * `Cannot find module '<path>?v=<mtime>'` until the process restarts. That is
@@ -543,13 +543,12 @@ const MODULE_ROUTE_EXT = /\.(tsx|jsx)$/
543
543
  * change and restarts already.
544
544
  *
545
545
  * `rename` is the creation signal. Measured on Windows: an in-place write
546
- * (`Bun.write`, `fs.writeFile`) emits only `change` and keeps the fast path —
547
- * 14-15ms edit-to-served — while creating a file emits `rename` then
546
+ * (`Bun.write`, `fs.writeFile`) emits only `change` and keeps the fast path ( * 14-15ms edit-to-served), while creating a file emits `rename` then
548
547
  * `change`. Writers that replace rather than overwrite report `rename` for an
549
548
  * *edit* too and pay one restart (~440ms): shell redirection (`> file`) does
550
549
  * this, and an editor that saves atomically will as well. That is the safe
551
- * direction to be wrong in — a slower save beats a page that does not serve
552
- * at all — but it is why this is scoped as narrowly as possible.
550
+ * direction to be wrong in (a slower save beats a page that does not serve
551
+ * at all), but it is why this is scoped as narrowly as possible.
553
552
  * Deletes are `rename` with no file, and need no restart.
554
553
  *
555
554
  * Scoped to imported modules: a new `.html`, `.css` or client `.ts` is read or
@@ -577,7 +576,7 @@ async function processFileEvent(
577
576
  }
578
577
 
579
578
  // Checked before the cheap path below: a *new* page cannot be imported by
580
- // this process at all — see `isCreatedRouteModule`.
579
+ // this process at all. See `isCreatedRouteModule`.
581
580
  if (
582
581
  isCreatedRouteModule(
583
582
  filePath,
@@ -636,7 +635,7 @@ export async function startCompileService(server: any): Promise<void> {
636
635
  if (kind === 'ignored') continue
637
636
 
638
637
  if (kind === 'package') {
639
- // Log only, no restart — deliberately. `bun install` rewrites the
638
+ // Log only, no restart, deliberately. `bun install` rewrites the
640
639
  // lockfile several times, and restarting on each would put the dev server
641
640
  // in a loop for the duration of an install. The line tells you a restart
642
641
  // is warranted; you decide when.
@@ -651,7 +650,7 @@ export async function startCompileService(server: any): Promise<void> {
651
650
  // A single bad event must not kill the watcher: before this, a throw
652
651
  // here unwound the for-await and file watching silently stopped until
653
652
  // the next manual restart. The server is alive at this point, so the
654
- // browser is told too — this is the in-worker sender for the error
653
+ // browser is told too: this is the in-worker sender for the error
655
654
  // overlay protocol.
656
655
  serveLog.WATCHER_ERR({ error: String(error) })
657
656
  notifyError(server, 'Dev watcher error', String(error))
@@ -3,7 +3,7 @@ import { cacheDir } from '../core/context'
3
3
  import { Try } from '../utils/common/try'
4
4
 
5
5
  /**
6
- * **`cacheDir` comes from `core/context`, not from `Bakery` — do not change it
6
+ * **`cacheDir` comes from `core/context`, not from `Bakery`. Do not change it
7
7
  * back.** `logger.ts` imports this module, so importing `core/bakery` here
8
8
  * completed a cycle:
9
9
  *
@@ -14,7 +14,7 @@ import { Try } from '../utils/common/try'
14
14
  * import arrived first found `Logger` still in its temporal dead zone. That
15
15
  * shipped in 1.2.3 and made `import '@bakery-framework/core'` throw
16
16
  * `ReferenceError: Cannot access 'Logger' before initialization` from a clean
17
- * install — see `tests/module-cycle.test.ts`.
17
+ * install. See `tests/module-cycle.test.ts`.
18
18
  *
19
19
  * `core/context` holds the same single definition of the path and imports
20
20
  * nothing that reaches the logger.