@bakery-framework/core 2.0.0-alpha.9 → 2.0.0-rc.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.
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 +54 -8
  18. package/src/core/define-route.ts +5 -6
  19. package/src/core/index.ts +29 -8
  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
@@ -17,7 +17,7 @@ const DEFAULT_ERROR: Handler.Error.Data = {
17
17
  *
18
18
  * The copy is the point, not the convenience. `extractErrorData` assigns the
19
19
  * getter's result to a local and then *mutates* it, which is safe only while
20
- * every `this` it runs under hands back a new object — a plain
20
+ * every `this` it runs under hands back a new object: a plain
21
21
  * `= DEFAULT_ERROR` would let any caller write the process-wide default's
22
22
  * fields. Both `ErrorHandler` and `DynamicErrorHandler` need the getter and
23
23
  * they sit in different class hierarchies, so neither can inherit it from the
@@ -32,7 +32,7 @@ function defaultErrorData(): Handler.Error.Data {
32
32
  * route lookup, and the 404 for a path that resolves to nothing.
33
33
  *
34
34
  * `HTMLHandler`, `TSXHandler` and the Vue plugin's handler render three
35
- * different file formats and their bodies genuinely differ — but all three
35
+ * different file formats and their bodies genuinely differ, but all three
36
36
  * reach them this way. `errors` is `undefined` for an ordinary page, and
37
37
  * `DEFAULT_ERROR` exists only on the error subclasses, so the fallback stays
38
38
  * `undefined` for the ordinary handlers. `DynamicErrorHandler.resolveRoute`
@@ -63,7 +63,7 @@ export async function beginPageRoute(
63
63
  * whatever error data gets merged in, and it names the route-relative path.
64
64
  * `HTMLHandler` used to set it after the merge and `TSXHandler` before, which
65
65
  * comes out the same today only because `publicErrorData` never emits a
66
- * `__file` key — two orderings for one rule is what makes it look like the
66
+ * `__file` key: two orderings for one rule is what makes it look like the
67
67
  * order might matter. It does not; this is the order, and `params` is the
68
68
  * object because TSX also feeds that same object to `injectIfHtml`.
69
69
  */
@@ -96,7 +96,7 @@ export class HandlerError extends Error {
96
96
  }
97
97
 
98
98
  export class ErrorHandler extends Handler {
99
- /** A fresh copy every read — see `defaultErrorData`. */
99
+ /** A fresh copy every read. See `defaultErrorData`. */
100
100
  static get DEFAULT_ERROR() {
101
101
  return defaultErrorData()
102
102
  }
@@ -132,7 +132,7 @@ export class ErrorHandler extends Handler {
132
132
  /**
133
133
  * The part of `error` that may cross the wire.
134
134
  *
135
- * `extractErrorData` puts `error.stack` into `errorBody` deliberately — that
135
+ * `extractErrorData` puts `error.stack` into `errorBody` deliberately: that
136
136
  * is what reaches the server log, and losing it would be a regression. But
137
137
  * the same field was being handed straight to the client: a `SQLiteError`
138
138
  * from a failed query answered a 500 with the failing statement, the table
@@ -155,13 +155,13 @@ export class ErrorHandler extends Handler {
155
155
  * `BuildMessage` (a syntax error) and `ResolveMessage` (an import that
156
156
  * resolves to nothing) are what the transpiler and the module resolver throw,
157
157
  * and neither is `instanceof Error`. They used to reach the `is.object`
158
- * branch below, which reads only `errorCode`/`errorText`/`errorBody` — none
159
- * of which a diagnostic has — and so returned the untouched default. A typo
158
+ * branch below, which reads only `errorCode`/`errorText`/`errorBody` (none
159
+ * of which a diagnostic has), and so returned the untouched default. A typo
160
160
  * in a route file, the single most common server-side failure there is,
161
161
  * answered `An unexpected error occurred.` in development as well as in
162
162
  * production.
163
163
  *
164
- * Recognised by shape rather than by constructor: the classes are not
164
+ * Recognized by shape rather than by constructor: the classes are not
165
165
  * exported, and both report zero own enumerable keys, so a string `message`
166
166
  * on a non-`Error` object is the only tell. The error-data keys are checked
167
167
  * first by the caller, so a record that carries those still takes its own
@@ -180,8 +180,8 @@ export class ErrorHandler extends Handler {
180
180
  return {
181
181
  ...this.DEFAULT_ERROR,
182
182
  errorText: error.message,
183
- // `errorDetail` is `error.stack` whenever there is one — so the common
184
- // case is untouched — and falls back to the aggregated sub-diagnostics
183
+ // `errorDetail` is `error.stack` whenever there is one (so the common
184
+ // case is untouched), and falls back to the aggregated sub-diagnostics
185
185
  // for the stackless `AggregateError` that `import()`ing a broken `.tsx`
186
186
  // throws, where `String(error)` was a summary count and nothing else.
187
187
  errorBody: errorDetail(error) || String(error),
@@ -193,7 +193,16 @@ export class ErrorHandler extends Handler {
193
193
  ...this.DEFAULT_ERROR,
194
194
  errorCode: error.status,
195
195
  errorText: error.statusText,
196
- errorBody: `${error.status}: "${error.statusText}"`,
196
+ // The reason-phrase is optional: `new Response(null, { status })`
197
+ // leaves it `''`, and the framework's own forbidden-path denial is
198
+ // exactly that shape, so the quoting is conditional: a synthesized
199
+ // `403: ""` rendered a literal quoted empty string on the error page.
200
+ // The bare status, not `''`, because this string is also the default
201
+ // `onError` log line (`403 at /path`); an empty body would log
202
+ // ` at /path`.
203
+ errorBody: error.statusText
204
+ ? `${error.status}: "${error.statusText}"`
205
+ : String(error.status),
197
206
  }
198
207
  }
199
208
 
@@ -204,7 +213,7 @@ export class ErrorHandler extends Handler {
204
213
  errorObj.errorText !== undefined ||
205
214
  errorObj.errorBody !== undefined
206
215
 
207
- // Authored error data wins, `message` or no `message` — the branch order
216
+ // Authored error data wins, `message` or no `message`, the branch order
208
217
  // and its semantics are unchanged for everything that ever reached it.
209
218
  if (!authored && this.isDiagnostic(error)) {
210
219
  return {
@@ -232,7 +241,7 @@ export class ErrorHandler extends Handler {
232
241
 
233
242
  export class DynamicErrorHandler extends DynamicHandler {
234
243
  /**
235
- * The same fresh copy `ErrorHandler.DEFAULT_ERROR` hands back — see
244
+ * The same fresh copy `ErrorHandler.DEFAULT_ERROR` hands back. See
236
245
  * `defaultErrorData`. Declared again rather than inherited because this
237
246
  * class descends from `DynamicHandler`, not from `ErrorHandler`.
238
247
  */
@@ -270,9 +279,12 @@ export class DynamicErrorHandler extends DynamicHandler {
270
279
 
271
280
  const defsPage = `${prefix}/error`
272
281
  const codePage = `${prefix}/error-${errors.errorCode}`
282
+ // `resolveStaticRoute`, never `resolveRoute`: an error page is a file by
283
+ // name, and letting the dynamic matcher answer meant a root catch-all
284
+ // claimed every one of them. See the note on that method.
273
285
  const routeInfo =
274
- (await super.resolveRoute(codePage)) ||
275
- (await super.resolveRoute(defsPage))
286
+ (await super.resolveStaticRoute(codePage)) ||
287
+ (await super.resolveStaticRoute(defsPage))
276
288
  if (routeInfo) return routeInfo
277
289
  }
278
290
 
@@ -286,14 +298,14 @@ export class DynamicErrorHandler extends DynamicHandler {
286
298
  * `ErrorHandler.publicBody` is the rule; this is it applied to a whole record,
287
299
  * for the handlers that hand error data to a *template* rather than rendering
288
300
  * a string themselves. `HTMLErrorHandler` and `TSXErrorHandler` merge the
289
- * record into their page params, and those params reach the document twice —
301
+ * record into their page params, and those params reach the document twice,
290
302
  * through `{{...}}` substitution and through the `window.__PAGE_PARAMS__`
291
303
  * script `DOMTools.params` injects into every page. The second path is why
292
304
  * redacting in the template was never enough: an `error.html` that never
293
305
  * mentions `errorBody` still published the stack, absolute source paths and
294
306
  * all, to any anonymous request in production.
295
307
  *
296
- * `errorText` is deliberately untouched — `DefaultErrorHandler` shows it in
308
+ * `errorText` is deliberately untouched: `DefaultErrorHandler` shows it in
297
309
  * its heading in every mode, and one rule in one place beats two that drift.
298
310
  */
299
311
  export function publicErrorData(error: Handler.Error.Data): Handler.Error.Data {
@@ -11,7 +11,7 @@ import { Handler } from './$base'
11
11
  * else. `JsonResponseData` is the framework's one-envelope idiom (convention
12
12
  * 7) and `processResponse` already renders it with its own `status`, so an
13
13
  * `ApiHandler` route returning `response.json.error(401, …)` answered 401
14
- * while the identical line in a middleware did not — the value was not a
14
+ * while the identical line in a middleware did not: the value was not a
15
15
  * `Response`, so the chain ignored it and the request carried on.
16
16
  *
17
17
  * Widening further, to "anything truthy", is the tempting version and is
@@ -30,8 +30,8 @@ function isMiddlewareResult(value: unknown): value is MiddlewareResult {
30
30
  /**
31
31
  * Per-request slot for the response produced during `canHandle`, so `handle`
32
32
  * can return it without re-running the chain. This was previously a static
33
- * field, which meant two concurrent requests could swap responses — including
34
- * each other's `Set-Cookie` headers — at any `await` boundary.
33
+ * field, which meant two concurrent requests could swap responses (including
34
+ * each other's `Set-Cookie` headers) at any `await` boundary.
35
35
  */
36
36
  const pending = new WeakMap<Request, MiddlewareResult>()
37
37
 
@@ -60,13 +60,13 @@ export class MiddlewareHandler extends Handler {
60
60
  return cached
61
61
  }
62
62
 
63
- // One config read for both `onRequest` and the middleware chain — same
63
+ // One config read for both `onRequest` and the middleware chain: same
64
64
  // request, same host store, so the snapshot cannot go stale mid-call.
65
65
  const config = Bakery.config
66
66
  const intercepted = await config.onRequest(req!)
67
67
  if (intercepted) {
68
68
  // `|| undefined` used to sit here, and `injectIfHtml` returns null for
69
- // anything that is not HTML — so a plain-text 403 from `onRequest`
69
+ // anything that is not HTML, so a plain-text 403 from `onRequest`
70
70
  // vanished and the request carried on. Inject when it is HTML, keep the
71
71
  // original otherwise, exactly as the middleware chain below does.
72
72
  return (await injectIfHtml(intercepted)) || intercepted
@@ -91,7 +91,7 @@ export class MiddlewareHandler extends Handler {
91
91
 
92
92
  // An envelope is JSON by construction, so it skips injection rather than
93
93
  // paying `DOMTools.isHTML` to be told so. `processResponse` reads its
94
- // `status` and serialises it.
94
+ // `status` and serializes it.
95
95
  if (data instanceof JsonResponseData) return data
96
96
 
97
97
  const injectedRes = await injectIfHtml(data!)
@@ -4,7 +4,7 @@ import { fs } from '../../utils'
4
4
  * Route mounts: map a URL prefix onto a directory outside the app's serve root.
5
5
  *
6
6
  * Handlers resolve files under `Bakery.serveRoot`, so a plugin wanting to serve
7
- * its own pages or client assets had no choice but to hand-roll it — reading
7
+ * its own pages or client assets had no choice but to hand-roll it: reading
8
8
  * files itself, bundling on the fly, and reimplementing the caching and
9
9
  * containment the handler pipeline already does. `DashboardHandler` is the
10
10
  * worked example of that cost.
@@ -17,7 +17,7 @@ import { fs } from '../../utils'
17
17
  * the search dir *and* the root, so traversal cannot escape it.
18
18
  */
19
19
  export interface RouteMount {
20
- /** URL prefix, leading slash, no trailing slash — e.g. `/_dashboard`. */
20
+ /** URL prefix, leading slash, no trailing slash, e.g. `/_dashboard`. */
21
21
  prefix: string
22
22
  /** Absolute directory the prefix resolves against. */
23
23
  dir: fs.AbsolutePath
@@ -1,4 +1,5 @@
1
1
  import { LRUCache } from '../../cache/lru'
2
+ import { hostKey } from '../../core/bakery'
2
3
  import type { Handler } from './$base'
3
4
 
4
5
  /**
@@ -51,8 +52,8 @@ export class HandlerMap<T extends typeof Handler = typeof Handler> extends Map<
51
52
  * `delete` and `clear` invalidate too, and neither used to.
52
53
  *
53
54
  * `list()` memoizes the sorted handler array and only `set` cleared it, so a
54
- * removed handler stayed in the list — and therefore stayed *in the request
55
- * pipeline* — until something happened to add one. Registration is
55
+ * removed handler stayed in the list (and therefore stayed *in the request
56
+ * pipeline*) until something happened to add one. Registration is
56
57
  * add-only in a served process, which is why this never bit: it is reachable
57
58
  * only by code that unregisters, and the first thing to do that was a test.
58
59
  *
@@ -98,7 +99,7 @@ export class HandlerMap<T extends typeof Handler = typeof Handler> extends Map<
98
99
  * `canHandle` without the microtask hop when the answer is synchronous.
99
100
  *
100
101
  * Most canHandles are plain predicates, and `await`ing their boolean cost a
101
- * microtask hop (~426ns) per probe, 2–4 probes per request. Returns a
102
+ * microtask hop (~426ns) per probe, 2 to 4 probes per request. Returns a
102
103
  * boolean for a sync answer and the promise itself for an async one, so
103
104
  * callers only `await` a value that is actually a promise:
104
105
  *
@@ -126,8 +127,17 @@ export class HandlerMap<T extends typeof Handler = typeof Handler> extends Map<
126
127
  // must not run twice. That constraint is what the branching encodes.
127
128
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: cache-path + side-effect-sensitive probe loop
128
129
  async resolve(path: string, req?: Request, ...rest: any[]) {
129
- const host = req?.__hostname || ''
130
- const pathId = `${this.id}:${host}:${path}`
130
+ // The **resolved** host, not the header. `__hostname` is whatever arrived
131
+ // in `Host`, and `routeCache` is one bounded LRU shared by every tenant, so
132
+ // a client varying that header minted an entry per spelling and walked the
133
+ // real hosts' entries out of the cache for as long as it kept asking. The
134
+ // file cache carried the same bug and was fixed; this one kept it.
135
+ //
136
+ // `hostKey` resolves through the multi-host config the way every other
137
+ // per-tenant key does, so an unknown or unconfigured host collapses to a
138
+ // single entry instead of one per spelling. The shape is unchanged when
139
+ // there is no host: `hostKey` returns the bare path.
140
+ const pathId = `${this.id}:${hostKey(path)}`
131
141
  const cached: any = HandlerMap.routeCache.get(pathId)
132
142
 
133
143
  // Only tracked once there is a cache hit to skip past. Middleware has
@@ -138,7 +148,7 @@ export class HandlerMap<T extends typeof Handler = typeof Handler> extends Map<
138
148
  if (cached) {
139
149
  probed = new Set()
140
150
  // A cache hit skips every handler above the cached one. Ask the
141
- // gatekeepers that outrank it first — the same handlers, in the same
151
+ // gatekeepers that outrank it first: the same handlers, in the same
142
152
  // order, a cold resolve would have reached before the cached one.
143
153
  const rank = this.order().get(cached) ?? Number.POSITIVE_INFINITY
144
154
  for (const gate of this.gatekeepers()) {
@@ -15,14 +15,14 @@ export type RouteScanOptions = {
15
15
  }
16
16
 
17
17
  // `[!.]` keeps catch-alls (`[...name]`) out of the single-param globs: they
18
- // are matched separately, and last — a catch-all is the weakest route form,
18
+ // are matched separately, and last. A catch-all is the weakest route form,
19
19
  // consulted only after specific files, single-param siblings and child-index
20
20
  // descent have all missed.
21
21
  const catchAllGlob = (ext: string) => new Bun.Glob(`[[]...*${ext || '.*'}`)
22
22
 
23
23
  // The single-param route forms, in the order they are tried: `[name].ext`
24
24
  // first, then the literal-asterisk `*.ext`. Built here rather than twice inside
25
- // `routeGlobs` — the `dynamicOnly` branch and the combined branch returned
25
+ // `routeGlobs`: the `dynamicOnly` branch and the combined branch returned
26
26
  // character-identical pairs, and the two must stay in step or a route form
27
27
  // resolves under one caller and not the other. A function, not a hoisted
28
28
  // constant: `ext` varies per handler, and `staticOnly` returns before it needs
@@ -33,22 +33,22 @@ const catchAllGlob = (ext: string) => new Bun.Glob(`[[]...*${ext || '.*'}`)
33
33
  // a path separator: Bun read `\*.*` as a drive-absolute pattern, ignored the
34
34
  // `cwd` in `GETFILE` entirely, and matched files at `C:\`. A route lookup under
35
35
  // a serve root six levels down returned `C:\$WINRE_BACKUP_PARTITION.MARKER`,
36
- // and `fs.isForbidden` — whose walk is bounded by `startsWith(root)` — waved it
36
+ // and `fs.isForbidden` (whose walk is bounded by `startsWith(root)`) waved it
37
37
  // through because an out-of-root path skipped the loop and answered "allowed".
38
38
  // That clamp now fails closed, so this is belt and braces; both halves are
39
39
  // pinned, and neither test can see the other's bug.
40
40
  //
41
41
  // Measured with a scan whose cwd was a temp directory holding one file: `[*]`
42
42
  // yields nothing, `\*` yields four files from the drive root. The escape is
43
- // also unreachable on Windows in the direction it was meant for — `*` is a
43
+ // also unreachable on Windows in the direction it was meant for: `*` is a
44
44
  // reserved character in a Windows filename, so a route file literally named
45
45
  // `*.ts` can only exist on POSIX, where both spellings match it identically
46
46
  // (verified on Linux). The character class costs nothing and means the same
47
47
  // thing on both platforms.
48
48
  //
49
49
  // Exported only as a test seam: with the clamp in place no test driving
50
- // `getRoute` can tell the two spellings apart — verified by reverting this line
51
- // and watching all 17 pass — so the pattern has to be asserted directly.
50
+ // `getRoute` can tell the two spellings apart (verified by reverting this line
51
+ // and watching all 17 pass), so the pattern has to be asserted directly.
52
52
  export const dynamicGlobs = (ext: string) => [
53
53
  new Bun.Glob(`[[][!.]*${ext || '.*'}`),
54
54
  new Bun.Glob(`[*]${ext || '.*'}`),
@@ -88,8 +88,7 @@ const routeGlobs = (
88
88
  * The catch-all fallback for one directory level: `dir/[...name].ext`,
89
89
  * containment-checked like every other candidate. Skipped for `staticOnly`
90
90
  * (the caller wants a literal file), and it *yields to any real file*: when
91
- * the request's remaining segments name an existing file under `dir` —
92
- * whatever its extension — the catch-all declines, so a lower-priority
91
+ * the request's remaining segments name an existing file under `dir` ( * whatever its extension) the catch-all declines, so a lower-priority
93
92
  * handler (TSHandler, StaticHandler) can serve the file itself. A directory
94
93
  * is not a file and does not trigger the yield. `findDynamicRoute` applies
95
94
  * the same rule on the cached path; the two must agree.
@@ -102,7 +101,7 @@ const routeGlobs = (
102
101
  * `provides.ts` at `/teacher/provides` and `/teacher/provides.js`, so neither
103
102
  * spelling named a file on disk and a Vue catch-all above it (priority 58 vs
104
103
  * 50) served HTML to a browser that asked for a module. The probe now also
105
- * tries the registered dynamic extensions against the extensionless base —
104
+ * tries the registered dynamic extensions against the extensionless base:
106
105
  * the same mapping the serving handlers apply, read from the live registry so
107
106
  * a plugin's extension (`.vue`) counts without core naming it.
108
107
  *
@@ -118,7 +117,7 @@ export function servedSourceExists(target: string): boolean {
118
117
  try {
119
118
  exts = (handler as { config?: { ext?: unknown } }).config?.ext
120
119
  } catch {
121
- // A config getter that needs state this process lacks — a handler with
120
+ // A config getter that needs state this process lacks: a handler with
122
121
  // no ext table cannot claim a source file either way.
123
122
  continue
124
123
  }
@@ -147,13 +146,13 @@ async function getCatchAllRoute(
147
146
  const target = fs.resolve(dir, restSegments.join('/'))
148
147
  // Stat only inside `dir`. A rest containing `..` resolves outside it, and
149
148
  // statting there would make this yield a boolean existence probe for any
150
- // path on the filesystem — the 404-vs-render difference is observable.
151
- // URL parsing normalises `..` and `%2e%2e` away, so no HTTP request
149
+ // path on the filesystem: the 404-vs-render difference is observable.
150
+ // URL parsing normalizes `..` and `%2e%2e` away, so no HTTP request
152
151
  // reaches here with one; this closes the door for any caller that skips
153
- // that normalisation. An escape skips the yield rather than refusing, so
152
+ // that normalization. An escape skips the yield rather than refusing, so
154
153
  // the catch-all answers exactly as it did before the yield rule existed.
155
154
  // `dir` comes from `fs.resolve`, so the separator-suffixed prefix test is
156
- // exact — plain `startsWith(dir)` would also accept a sibling directory
155
+ // exact: plain `startsWith(dir)` would also accept a sibling directory
157
156
  // whose name merely begins with it.
158
157
  if (
159
158
  (target === dir || target.startsWith(`${dir}/`)) &&
@@ -169,7 +168,7 @@ async function getCatchAllRoute(
169
168
 
170
169
  // A bare-directory request (`/docs` with no index) reaches here with no
171
170
  // rest segments, and only the `[...name!]` spelling opted into claiming
172
- // it — the plain form keeps requiring at least one segment.
171
+ // it: the plain form keeps requiring at least one segment.
173
172
  if (!restSegments.length && !info.optionalCatchAll) return null
174
173
 
175
174
  return info
@@ -183,7 +182,7 @@ export async function getRoute(
183
182
  root?: fs.AbsolutePath,
184
183
  options: RouteScanOptions = {},
185
184
  ): Promise<Handler.Route.Info | null> {
186
- // Both used to default to `Bakery.serveRoot` independently — two reads of
185
+ // Both used to default to `Bakery.serveRoot` independently. Two reads of
187
186
  // the config getter (an AsyncLocalStorage getStore each) when a caller
188
187
  // omits both. One read now covers whichever is missing; the values are
189
188
  // unchanged.
@@ -234,7 +233,7 @@ export async function getRoute(
234
233
  // statement is `isForbidden(dir, root)` with these exact arguments, and its
235
234
  // `null` reaches the same `return null` this function ends on. Checking
236
235
  // here as well made every level of a nested route pay two identical
237
- // directory tree-walks — and `isForbidden` walks from the file to the root
236
+ // directory tree-walks, and `isForbidden` walks from the file to the root
238
237
  // doing an existsSync at each level, so that is the most expensive thing
239
238
  // this function does. `src/tests/forbidden.test.ts` pins both branches.
240
239
  if (first !== 'index') {
@@ -246,7 +245,7 @@ export async function getRoute(
246
245
  // `first === 'index'` is the bare-directory request (`/docs` arrives here
247
246
  // as an injected 'index' segment, after no index file matched). The plain
248
247
  // `[...name]` pattern requires at least one rest segment and cannot claim
249
- // it; `[...name!]` exists to — `getCatchAllRoute` tells them apart.
248
+ // it; `[...name!]` exists to: `getCatchAllRoute` tells them apart.
250
249
  if (!options.staticOnly) {
251
250
  return await getCatchAllRoute(
252
251
  ext,
@@ -263,7 +262,7 @@ export async function getRoute(
263
262
  if (route) return route
264
263
 
265
264
  // The walk above descends one real segment at a time and dead-ends when
266
- // the next directory does not exist — which for a catch-all request
265
+ // the next directory does not exist, which for a catch-all request
267
266
  // (`/docs/a/b/c` against `docs/[...slug].tsx`) is the common case. Each
268
267
  // level unwinds through here, so the deepest existing directory gets the
269
268
  // first chance to claim the rest.
@@ -4,14 +4,13 @@ import { fs } from '../../utils/fs'
4
4
  import { resolveMount } from './$mounts'
5
5
 
6
6
  /**
7
- * Literal file resolution — the static counterpart to `getRoute`.
7
+ * Literal file resolution: the static counterpart to `getRoute`.
8
8
  *
9
9
  * `getRoute` answers "which file backs this *route*", with extensionless URLs,
10
10
  * dynamic `[id]` segments and glob scanning. This answers the simpler question
11
11
  * "which file is at this path, and am I allowed to serve it".
12
12
  *
13
- * It exists because four handlers were each spelling that out themselves —
14
- * StaticHandler, PublicHandler, ImageHandler and NMHandler — in four slightly
13
+ * It exists because four handlers were each spelling that out themselves ( * StaticHandler, PublicHandler, ImageHandler and NMHandler) in four slightly
15
14
  * different ways, and only some of them checked everything. Once route mounts
16
15
  * arrived the drift got worse: a mount-aware StaticHandler sitting next to a
17
16
  * mount-blind PublicHandler is the kind of inconsistency that turns into a bug
@@ -19,7 +18,7 @@ import { resolveMount } from './$mounts'
19
18
  *
20
19
  * **Three of those four go through this; NMHandler does not, on purpose.** Its
21
20
  * entry point is resolved by `Bun.build`, not by the filesystem, so
22
- * `/_nm/pkg/sub` legitimately means `pkg/sub/index.js` — a path this function
21
+ * `/_nm/pkg/sub` legitimately means `pkg/sub/index.js`: a path this function
23
22
  * answers `null` for, because it is a directory. It therefore repeats the
24
23
  * containment test and calls `fs.isForbidden` itself; the reasoning, and the
25
24
  * test that pins the divergence, are in `handlers/assets/nm.ts`. It is the one
@@ -43,7 +42,7 @@ export interface StaticTarget {
43
42
  * mounted plugin cannot be traversed out of.
44
43
  *
45
44
  * Roots are tried in order, and a root that fails containment is skipped
46
- * rather than aborting — mirroring ImageHandler, which legitimately looks in
45
+ * rather than aborting: mirroring ImageHandler, which legitimately looks in
47
46
  * both the serve root and the public root.
48
47
  */
49
48
  export async function getStatic(
@@ -64,23 +63,23 @@ export async function getStatic(
64
63
  const file = fs.resolve(root, candidate.relative)
65
64
 
66
65
  // Two checks, not one: the prefix test catches a resolved path that
67
- // escaped the root, and isForbidden additionally honours `.forbidden`
68
- // markers. It does *not* apply the blocked globs — `Bakery.config.blocked`
66
+ // escaped the root, and isForbidden additionally honors `.forbidden`
67
+ // markers. It does *not* apply the blocked globs: `Bakery.config.blocked`
69
68
  // is matched against the request path in `router.ts` and again in
70
- // `handlers/assets/static.ts`, never against a resolved file path — so a
71
- // caller reaching for `getStatic` as a one-stop authorisation check is
69
+ // `handlers/assets/static.ts`, never against a resolved file path, so a
70
+ // caller reaching for `getStatic` as a one-stop authorization check is
72
71
  // getting containment and `.forbidden` only.
73
72
  //
74
- // Containment stays first — nothing stats a path that escaped the root.
73
+ // Containment stays first: nothing stats a path that escaped the root.
75
74
  // After that the order is by cost: `isForbidden` walks every directory
76
75
  // between the file and the root, so running it before the existence check
77
- // paid for a whole tree-walk on every candidate that simply is not there —
76
+ // paid for a whole tree-walk on every candidate that simply is not there,
78
77
  // which is most of them, since a miss here is how each root in the list,
79
78
  // and every extensionless probe, gets ruled out. All checks are pure and
80
79
  // all `continue`, so the candidate chosen is unchanged.
81
80
  //
82
81
  // One stat answers both questions. This used to be `fs.exists` (a stat)
83
- // then `fs.isDir` (which is `fs.exists` plus a second stat of its own) —
82
+ // then `fs.isDir` (which is `fs.exists` plus a second stat of its own):
84
83
  // up to three stats per candidate at ~28us each on Bun/Windows. A failed
85
84
  // stat means "nothing servable here" and falls through to the next
86
85
  // candidate, exactly as the exists check did.
@@ -46,19 +46,19 @@ export class ApiHandler extends DynamicHandler {
46
46
  if (result !== null && result !== undefined) return result
47
47
 
48
48
  // Two different faults used to share one bare 404, and neither channel
49
- // named the file — so "No response from handler" sent the developer
49
+ // named the file, so "No response from handler" sent the developer
50
50
  // hunting for a route that was sitting on disk the whole time.
51
51
  //
52
52
  // `null` and `undefined` are what tell them apart, and the split is exact
53
53
  // rather than incidental: `DynamicHandler.executeModule` *throws* when the
54
54
  // import fails (that is the 500 path), returns a literal `null` for
55
55
  // "module loaded, no `default`", and otherwise returns whatever the
56
- // handler produced — `undefined` when it produced nothing.
56
+ // handler produced, `undefined` when it produced nothing.
57
57
  if (result === null) {
58
58
  // A 500, not a 404: the route resolved, the file exists, and the server
59
59
  // could not answer with it. That is a server fault, and the message says
60
- // what to add. PROD still redacts it — `publicBody` replaces any 5xx
61
- // body — so naming the file here discloses nothing to a client.
60
+ // what to add. PROD still redacts it (`publicBody` replaces any 5xx
61
+ // body), so naming the file here discloses nothing to a client.
62
62
  handlerLog.API_NO_DEFAULT({ file: filePath })
63
63
  return response.error(
64
64
  `API route has no export default: ${info.path}`,
@@ -88,7 +88,7 @@ export class ApiErrorHandler extends ErrorHandler {
88
88
 
89
89
  static handle(_p: string, _r: Request, error: Handler.Error.Data) {
90
90
  // `publicBody`, not `errorBody`: in production the latter is the stack of
91
- // whatever threw. The full trace still reaches the log — `handleRequestError`
91
+ // whatever threw. The full trace still reaches the log: `handleRequestError`
92
92
  // passes the unredacted data to `config.onError` before this runs.
93
93
  return response.json.error(error.errorCode, ErrorHandler.publicBody(error))
94
94
  }
@@ -78,8 +78,8 @@ async function sharedHandler(
78
78
  // through `{{...}}` *and* through the `__PAGE_PARAMS__` script injected
79
79
  // into every page, so the raw stack was published in PROD even by a
80
80
  // template that never mentioned it. Guarded because `errorData` is
81
- // `undefined` for an ordinary page — `DEFAULT_ERROR` only exists on the
82
- // error handler — and the spread below tolerates that where the helper,
81
+ // `undefined` for an ordinary page (`DEFAULT_ERROR` only exists on the
82
+ // error handler), and the spread below tolerates that where the helper,
83
83
  // deliberately strict about the shape it redacts, does not.
84
84
  // Annotated because `beginPageRoute` hands back real `Handler.Error.Data`
85
85
  // where this used to read an untyped `(this as any).DEFAULT_ERROR`: the
@@ -63,7 +63,7 @@ export class ProxyHandler extends Handler {
63
63
 
64
64
  const resHeaders = new Headers(proxyRes.headers)
65
65
  // Bun already decompressed the body, so the upstream's content-encoding is
66
- // wrong — and so is its content-length, which described the compressed size.
66
+ // wrong, and so is its content-length, which described the compressed size.
67
67
  resHeaders.delete('content-encoding')
68
68
  resHeaders.delete('content-length')
69
69
  return new Response(proxyRes.body, {
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * WebSocket clients subscribed to the live log stream.
3
3
  *
4
- * `LiveReloadHandler` owns membership — it adds a socket on
4
+ * `LiveReloadHandler` owns membership: it adds a socket on
5
5
  * `subscribe_logger` and removes it on close. Consumers (the dashboard's
6
6
  * log broadcast, the analytics `activeLoggers` gauge) only read.
7
7
  *
8
8
  * This lives in core rather than a plugin deliberately: it used to be
9
9
  * defined in `plugins/analytics/core`, which made core import from a
10
- * plugin — a backwards edge that blocks packaging core on its own.
10
+ * plugin, a backwards edge that blocks packaging core on its own.
11
11
  */
12
12
  export const connectedLoggers = new Set<any>()
@@ -46,7 +46,7 @@ export function setLogCallback(cb: (entry: LoggerEntry) => void) {
46
46
  *
47
47
  * Three call sites used to spell this as `Promise.try(() =>
48
48
  * onLogCallback?.(…)).catch(() => {})`, which allocated two promises and
49
- * scheduled a microtask for *every* line — including every line logged while
49
+ * scheduled a microtask for *every* line, including every line logged while
50
50
  * no sink was registered at all. `Try` swallows a synchronous throw and a
51
51
  * rejected promise alike (see `utils/isomorphic/try.ts`) without allocating
52
52
  * anything on the synchronous path, which is the only path a sink typed
@@ -61,7 +61,7 @@ function emit(entry: LoggerEntry): void {
61
61
  }
62
62
 
63
63
  // Hoisted: both were rebuilt on every call, and neither depends on the input.
64
- // The colour table is a 23-property literal and the pattern a regex literal,
64
+ // The color table is a 23-property literal and the pattern a regex literal,
65
65
  // so a process logging steadily paid for both on every line.
66
66
  const COLORS: MapOf<string> = {
67
67
  r: '\x1b[31m', // Red
@@ -299,7 +299,7 @@ type Messages<T extends MapOf<string>> = {
299
299
  const RX_PARAM = /\{([^}]+)\}/g
300
300
 
301
301
  type ParsedMessage = {
302
- /** The string this was parsed from — the cache's validity check. */
302
+ /** The string this was parsed from: the cache's validity check. */
303
303
  raw: string
304
304
  level: LogLevels
305
305
  template: string
@@ -332,7 +332,7 @@ export function messageLogger<T extends MapOf<string>>(
332
332
  targetMsgs: T,
333
333
  ) {
334
334
  /**
335
- * One entry per *declared* key, so it is bounded by the message table — the
335
+ * One entry per *declared* key, so it is bounded by the message table: the
336
336
  * tables in `serve-log.ts` are literals of a dozen-odd entries each.
337
337
  * Undeclared props are deliberately left uncached: the trap fabricates a
338
338
  * message for those, and a caller reading arbitrary property names off the