@bakery-framework/cli 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.
package/README.md CHANGED
@@ -32,15 +32,15 @@ There is no `--port` flag. The port resolves as `PORT` → `port` in
32
32
  fallback: `PORT=3000x` exits 1 instead of binding somewhere random.
33
33
 
34
34
  For a standalone schema sync in a deploy step, drive `SyncService` from
35
- `@bakery-framework/orm/sync` directly — `--sync` starts a server afterwards.
35
+ `@bakery-framework/orm/sync` directly: `--sync` starts a server afterwards.
36
36
 
37
37
  ## License
38
38
 
39
- MIT with the Commons Clause v1.0 — see [LICENSE](./LICENSE).
39
+ MIT with the Commons Clause v1.0. See [LICENSE](./LICENSE).
40
40
 
41
- **Not an OSI-approved licence.** The Commons Clause removes the right to *sell*
42
- the software — meaning to charge for a product or service whose value derives
41
+ **Not an OSI-approved license.** The Commons Clause removes the right to *sell*
42
+ the software: meaning to charge for a product or service whose value derives
43
43
  substantially from it, hosting and support included. Everything else the MIT
44
- licence grants is unchanged: use it, modify it, ship it inside your own product.
45
- If your organisation only permits OSI-approved dependencies, this will not pass
44
+ license grants is unchanged: use it, modify it, ship it inside your own product.
45
+ If your organization only permits OSI-approved dependencies, this will not pass
46
46
  that check.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bakery-framework/cli",
3
- "version": "2.0.0-alpha.9",
3
+ "version": "2.0.0-rc.2",
4
4
  "description": "Bakery entry point and process supervision: mode dispatch, dev watcher, cluster.",
5
5
  "keywords": [
6
6
  "bakery",
@@ -36,13 +36,13 @@
36
36
  "!src/tests"
37
37
  ],
38
38
  "engines": {
39
- "bun": ">=1.3.14"
39
+ "bun": ">=1.4.0"
40
40
  },
41
41
  "dependencies": {
42
- "@bakery-framework/core": "^2.0.0-alpha.9"
42
+ "@bakery-framework/core": "^2.0.0-rc.2"
43
43
  },
44
44
  "peerDependencies": {
45
- "@bakery-framework/orm": "^2.0.0-alpha.9"
45
+ "@bakery-framework/orm": "^2.0.0-rc.2"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@bakery-framework/orm": {
package/src/args.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Split out of `index.ts` so it can be tested: that file is the mode
5
5
  * dispatcher, and importing it boots a server. Nothing here reads
6
- * `process.argv` — the caller passes the slice, which is also what lets a test
6
+ * `process.argv`: the caller passes the slice, which is also what lets a test
7
7
  * state the argv it means.
8
8
  */
9
9
 
@@ -35,7 +35,7 @@ export function autoThreadCount(): number {
35
35
  * `handleThreadsMaster` with one worker.
36
36
  *
37
37
  * Both spellings of each flag are accepted (`--threads 4`, `--threads=4`, and
38
- * the `-t` forms). A flag with a non-numeric or absent value is not an error —
38
+ * the `-t` forms). A flag with a non-numeric or absent value is not an error:
39
39
  * it falls back to `autoThreadCount()`, so `--threads --dev` asks for the
40
40
  * default rather than rejecting. `Math.max(1, …)` is what keeps `--threads 0`
41
41
  * from requesting a cluster with no workers in it.
package/src/dev.ts CHANGED
@@ -22,7 +22,7 @@ try {
22
22
 
23
23
  config = await initConfig()
24
24
  // Still before initImportMap(): a plugin's setup() may contribute entries.
25
- // setupPlugins() memoises, so setupServer()'s later call is a no-op here.
25
+ // setupPlugins() memoizes, so setupServer()'s later call is a no-op here.
26
26
  await setupPlugins()
27
27
  await initImportMap()
28
28
  initHostImportMaps()
@@ -74,7 +74,7 @@ if (hasORM()) {
74
74
  serveLog.SCHEMA_SYNC_SKIP()
75
75
  // Only on the skip path. When a sync runs it reports drift itself, from
76
76
  // the plan it just built; here nothing else would ever look. Measured at
77
- // 2.7ms median against apps/example (4 tables) — it is one introspection
77
+ // 2.7ms median against apps/example (4 tables): it is one introspection
78
78
  // pass, so it grows with table count, which is why it is not on the path
79
79
  // that is about to introspect anyway.
80
80
  const { initDB, connection } = await import(
package/src/index.ts CHANGED
@@ -14,15 +14,63 @@ const isThreadWorker = import.meta.env.THREAD_WORKER
14
14
 
15
15
  const threadsOption = parseThreadsOption(process.argv.slice(2))
16
16
 
17
+ /**
18
+ * `--help` before anything else, because everything else starts a server.
19
+ *
20
+ * There was no help branch at all: `bakery --help` fell through to `./prod`,
21
+ * booted a production server, bound the configured port and sat there. On a
22
+ * machine where that port is already taken it fails with a bind error, and on
23
+ * one where it is free it silently *takes* it: either way the user asked what
24
+ * the flags were and got a running service.
25
+ *
26
+ * Printed with `console.log` and not the structured logger, for the reason
27
+ * CLAUDE.md records as one of the two standing exceptions: usage text is
28
+ * program output, not a log line, and it goes to stdout so it can be piped.
29
+ */
30
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
31
+ console.log(`
32
+ Usage: bakery [--dev] [--sync] [--threads N] [--port N]
33
+
34
+ Runs the application in the current directory: the one whose
35
+ \`server.config.ts\` sits beside it.
36
+
37
+ Flags:
38
+ --dev Development: a supervisor that watches files, compiles on
39
+ demand and reloads the browser. Without it, production.
40
+ --sync, -s Run the schema sync before starting. Requires
41
+ @bakery-framework/orm; it is an error rather than a skip
42
+ when that is missing, because asking for a sync and
43
+ silently not doing one looks like it worked.
44
+ --threads N, -t N Fork a cluster of N workers. Production only: ignored
45
+ under --dev. THREAD_ID 0 owns the startup banner.
46
+ --port N, -p N Listening port. Overrides \`port\` in server.config.ts and
47
+ the PORT environment variable.
48
+ --help, -h This.
49
+
50
+ Schema commands have their own help: \`bun run db:sync --help\`.
51
+ `)
52
+ process.exit(0)
53
+ }
54
+
17
55
  // Before any mode takes over, and before the config is read: `applyPortFlag`
18
56
  // writes `process.env.PORT`, which is what the worker, the startup banner and
19
- // the dev master's advertised URL all resolve from — and what the spawned dev
57
+ // the dev master's advertised URL all resolve from, and what the spawned dev
20
58
  // worker and the cluster Workers inherit. Doing it here means none of them
21
59
  // needed changing.
22
60
  applyPortFlag()
23
61
 
24
62
  if (
25
63
  (process.argv.includes('--sync') || process.argv.includes('-s')) &&
64
+ // **Not in development, where `dev.ts` owns the decision.** Under `--dev`
65
+ // this file is the watcher master, which then spawns a `--dev-worker` that
66
+ // reaches `dev.ts`, and that path reads `--sync` as `force`, hashes the
67
+ // schema sources, decides skip-or-run against the recorded hash and writes
68
+ // the new one. Running here as well meant `--dev --sync` synced twice per
69
+ // boot: once blindly in the master and once properly in the worker, against
70
+ // the same database, with the master's pass doing work the worker was about
71
+ // to redo. Production keeps this branch, because `prod.ts` does not sync at
72
+ // all and this is the only thing that answers `--sync` there.
73
+ !isDev &&
26
74
  !isDevWorker &&
27
75
  !isThreadWorker
28
76
  ) {
package/src/orm.ts CHANGED
@@ -3,15 +3,15 @@
3
3
  *
4
4
  * The CLI declares it as an **optional peer**, so an app scaffolded with
5
5
  * `--no-orm` never downloads it. Every use of it in this package is already an
6
- * `await import()`, so its absence is not a module-graph problem — it is a
6
+ * `await import()`, so its absence is not a module-graph problem: it is a
7
7
  * question this file answers once, before anything tries.
8
8
  *
9
9
  * **Absence and breakage must not be conflated, and telling them apart is the
10
10
  * only reason this file exists.** The obvious implementation is to wrap the
11
11
  * `await import()` in a try and treat a throw as "not installed", and it is
12
12
  * wrong in the direction that costs data: a database that is configured but
13
- * unreachable, a `DB_URL` with a typo, a schema module that throws at import —
14
- * all of those would be swallowed as "no ORM here" and the server would boot
13
+ * unreachable, a `DB_URL` with a typo, a schema module that throws at import.
14
+ * All of those would be swallowed as "no ORM here" and the server would boot
15
15
  * happily with no database and no complaint. `initDB` failing has always been
16
16
  * fatal and stays fatal. Only *resolution* failing is soft.
17
17
  *
@@ -25,10 +25,10 @@ let resolved: boolean | null = null
25
25
  /**
26
26
  * True when `@bakery-framework/orm` can be resolved from this package.
27
27
  *
28
- * Memoised: it is asked on the boot path and once more per shutdown, the answer
28
+ * Memoized: it is asked on the boot path and once more per shutdown, the answer
29
29
  * cannot change within a process, and the filesystem walk is not free.
30
30
  *
31
- * `import.meta.dir` is the resolution base on purpose — it is what a real
31
+ * `import.meta.dir` is the resolution base on purpose: it is what a real
32
32
  * `import` from this module would use, so the answer matches what the `await
33
33
  * import()` calls below it will actually do. Resolving from `process.cwd()`
34
34
  * would ask a different question (does the *app* see it) and could disagree,
@@ -43,7 +43,7 @@ export function hasORM(): boolean {
43
43
  resolved = true
44
44
  } catch {
45
45
  // Not installed. The only expected outcome of this catch, and the reason
46
- // it is allowed to be silent — see the note above about what is *not*
46
+ // it is allowed to be silent. See the note above about what is *not*
47
47
  // routed through here.
48
48
  resolved = false
49
49
  }
package/src/pipeline.ts CHANGED
@@ -9,7 +9,7 @@ import { retryAfterSeconds } from './rate-limit'
9
9
  * They were inline in the `Bun.serve` callback, which is unreachable from a
10
10
  * test: `worker.ts` calls `Bun.serve` at module scope, so importing it binds a
11
11
  * port. Nothing about them is server-specific, so they live here and the
12
- * callback calls them — the serve options are otherwise unchanged.
12
+ * callback calls them: the serve options are otherwise unchanged.
13
13
  */
14
14
 
15
15
  /** The `rateLimit` config with the `false` (disabled) arm removed. */
@@ -24,7 +24,7 @@ export type RateLimitConfig = Exclude<ProcessedAppConfig['rateLimit'], false>
24
24
  *
25
25
  * The `is.object` guard is load-bearing twice over. `'errorCode' in res` is a
26
26
  * `TypeError` on a primitive, and `Try.return`'s failure sentinel is a
27
- * `symbol` — so the guard is what keeps the rejection path from throwing
27
+ * `symbol`, so the guard is what keeps the rejection path from throwing
28
28
  * inside the code that exists to handle throws. Note `is.object([])` is
29
29
  * deliberately `true` (see CLAUDE.md); an array simply has no `errorCode`.
30
30
  *
@@ -41,9 +41,9 @@ export function isErrorResult(res: unknown): boolean {
41
41
  * Which token bucket this request spends from.
42
42
  *
43
43
  * The `|| hostname` is not a tidy-up. An empty key is a *valid* string that
44
- * hashes to one fixed slot, so every client whose IP could not be determined —
44
+ * hashes to one fixed slot, so every client whose IP could not be determined,
45
45
  * which is all of them when `Bakery.server` is not yet assigned, and any of
46
- * them behind a proxy with `trustProxy` off — would share a single bucket and
46
+ * them behind a proxy with `trustProxy` off: would share a single bucket and
47
47
  * 429 each other. Falling back to the hostname keeps the collision at
48
48
  * per-host, which is the coarsest grouping that is still meaningful.
49
49
  */
@@ -58,7 +58,7 @@ export function rateLimitKey(
58
58
  /**
59
59
  * The 429 a rejected request receives.
60
60
  *
61
- * `Retry-After` is whole seconds per RFC 9110 and never 0 — see
61
+ * `Retry-After` is whole seconds per RFC 9110 and never 0. See
62
62
  * `retryAfterSeconds`.
63
63
  */
64
64
  export function tooManyRequests(refill: number): Response {
package/src/prod.ts CHANGED
@@ -16,7 +16,7 @@ try {
16
16
  )
17
17
 
18
18
  await initConfig()
19
- // Before initImportMap() so plugin-contributed entries land in it; memoised,
19
+ // Before initImportMap() so plugin-contributed entries land in it; memoized,
20
20
  // so the later setupServer() call inside worker.ts does not repeat it.
21
21
  await setupPlugins()
22
22
  await initImportMap()
package/src/rate-limit.ts CHANGED
@@ -20,7 +20,7 @@ export const RATE_LIMIT_SLOTS = 1024
20
20
  * left 100 reachable buckets out of 1024 with **83% of all keys in bucket 0**.
21
21
  * Rate limiting is on by default at `{max: 100, refill: 10}`, so in practice
22
22
  * most clients shared one bucket with a sustained ceiling of 10 requests per
23
- * second — one busy client 429'd everyone else, and an ordinary page load
23
+ * second, one busy client 429'd everyone else, and an ordinary page load
24
24
  * (many requests inside a single refill tick) tripped it on its own.
25
25
  *
26
26
  * Taking the modulo in bigint space and converting the small result keeps the
@@ -55,13 +55,13 @@ const logState = new LRUCache<string, { last: number; suppressed: number }>(
55
55
  *
56
56
  * The constraint is availability under flood: stdout writes are effectively
57
57
  * synchronous on Windows, so one log line per rejected request hands the flood
58
- * the limiter just absorbed straight to the logger — the 429 path becomes as
58
+ * the limiter just absorbed straight to the logger, the 429 path becomes as
59
59
  * expensive as the work it was refusing. At most one line per key per
60
60
  * `RATE_LIMIT_LOG_WINDOW_MS` instead.
61
61
  *
62
62
  * Returns `null` when the line must be suppressed, otherwise the number of
63
63
  * rejections suppressed since the key's previous line (0 for a first
64
- * offender). Bounded per convention 6 — the key derives from client-controlled
64
+ * offender). Bounded per convention 6: the key derives from client-controlled
65
65
  * data (IP, or `rateLimit.keyBy`), so the state lives in an LRU and an evicted
66
66
  * key simply logs again as if new. Over-logging is the safe direction to be
67
67
  * wrong in.
@@ -88,7 +88,7 @@ export function __resetRateLimitLogState(): void {
88
88
 
89
89
  /**
90
90
  * Seconds until the bucket holds a token again, for the 429's `Retry-After`
91
- * header. Whole seconds per RFC 9110, and never less than 1 — "0" would tell
91
+ * header. Whole seconds per RFC 9110, and never less than 1: "0" would tell
92
92
  * the client to retry immediately, which is the opposite of the point.
93
93
  */
94
94
  export function retryAfterSeconds(refill: number): number {
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * Hash everything the boot-time schema sync reads: the schema source files
3
- * (resolved with the same probe order as orm/sync/load.ts — configured path,
3
+ * (resolved with the same probe order as orm/sync/load.ts, configured path,
4
4
  * then the `orm/` folder layout, then a root `schema.ts`) plus the DB target,
5
5
  * since switching `DB_URL` changes what "synced" means.
6
6
  *
7
- * Returns `null` for any indeterminate state — a configured path that does not
8
- * exist, an unreadable file — so `classifySchemaSync` fails closed into
7
+ * Returns `null` for any indeterminate state (a configured path that does not
8
+ * exist, an unreadable file), so `classifySchemaSync` fails closed into
9
9
  * re-syncing. Total absence of a schema is *not* indeterminate (it is a
10
10
  * supported state for the defaults) and hashes to a stable value.
11
11
  *
12
12
  * `base` defaults to the app's cwd, which is what `dev.ts` wants and the only
13
13
  * value it passes. It is a parameter at all because `fs.cwd` is a module-level
14
14
  * constant evaluated at import time, so a test cannot reach this function's
15
- * probe any other way without `process.chdir` before the import — process-global
15
+ * probe any other way without `process.chdir` before the import: process-global
16
16
  * state of exactly the kind convention 9 exists to keep out of the suite.
17
17
  *
18
18
  * The `@bakery-framework/core/utils` import stays dynamic, as it was inline in
package/src/shutdown.ts CHANGED
@@ -11,7 +11,7 @@ let running: Promise<void> | null = null
11
11
  *
12
12
  * The same bound the cluster master applies to a worker's pre-terminate flush,
13
13
  * for the same reason it states: *a wedged worker must delay shutdown, not
14
- * prevent it*. The standalone path did not honour that — `runShutdownSequence()`
14
+ * prevent it*. The standalone path did not honor that: `runShutdownSequence()`
15
15
  * gated `process.exit(0)` with no deadline, so one hook that never settled meant
16
16
  * SIGINT never terminated the process and Ctrl-C appeared to do nothing.
17
17
  */
@@ -20,7 +20,7 @@ export const SHUTDOWN_TIMEOUT_MS = FLUSH_TIMEOUT_MS
20
20
  /**
21
21
  * The two teardown steps that close process-wide resources. Behind a seam
22
22
  * because a test that ran the real ones would close the cache database and the
23
- * ORM connection for every test file scheduled after it — see `__setTestDb` in
23
+ * ORM connection for every test file scheduled after it. See `__setTestDb` in
24
24
  * `orm/connection.ts` for the same pattern and the same reason.
25
25
  */
26
26
  export interface ShutdownTeardown {
@@ -43,7 +43,7 @@ const defaultTeardown: ShutdownTeardown = {
43
43
 
44
44
  let testTeardown: ShutdownTeardown | null = null
45
45
 
46
- /** Test seam — see `ShutdownTeardown`. */
46
+ /** Test seam. See `ShutdownTeardown`. */
47
47
  export function __setTestTeardown(teardown: ShutdownTeardown): void {
48
48
  testTeardown = teardown
49
49
  }
@@ -55,28 +55,28 @@ export function __resetTestTeardown(): void {
55
55
  /**
56
56
  * Run every teardown step this process owns, once.
57
57
  *
58
- * Memoised rather than guarded by a boolean: a cluster worker can be asked to
58
+ * Memoized rather than guarded by a boolean: a cluster worker can be asked to
59
59
  * flush by the master *and* receive a signal, and the second caller has to wait
60
60
  * for the first run to finish rather than race it or skip it.
61
61
  *
62
62
  * ## Order
63
63
  *
64
- * 1. `Bakery.config.onShutdown()` — the application's own hook.
65
- * 2. `Bakery.shutdownHooks` — framework internals (tiered-cache flush, session
64
+ * 1. `Bakery.config.onShutdown()`: the application's own hook.
65
+ * 2. `Bakery.shutdownHooks`: framework internals (tiered-cache flush, session
66
66
  * prune timer).
67
- * 3. `PluginHooks.onShutdown()` — plugins.
68
- * 4. Resource close — the shared cache database, then the ORM connection.
67
+ * 3. `PluginHooks.onShutdown()`, plugins.
68
+ * 4. Resource close: the shared cache database, then the ORM connection.
69
69
  *
70
70
  * The app goes first because it is the only participant that can still need the
71
71
  * framework intact: step 2 flushes the session/cache tier, so an app hook that
72
72
  * wants to write a last session value has to run before it, not after. That is
73
73
  * also the exact reverse of startup, where `runStartupBanner()` calls
74
- * `PluginHooks.onStart()` and then `config.onStart()` last — last up, first down.
74
+ * `PluginHooks.onStart()` and then `config.onStart()` last: last up, first down.
75
75
  *
76
- * Step 4 exists because steps 1–3 all still write. `cache/tiered.ts` used to
76
+ * Step 4 exists because steps 1 to 3 all still write. `cache/tiered.ts` used to
77
77
  * close the shared cache database inside its own step-2 hook, which is
78
78
  * registered at module-evaluation time and so runs *first* of all the framework
79
- * hooks — before every plugin. The analytics plugin's shutdown flush binds that
79
+ * hooks: before every plugin. The analytics plugin's shutdown flush binds that
80
80
  * same handle, so its statements threw and its page-hit and history deltas were
81
81
  * dropped on every clean stop. The ORM connection was closed by nothing at all:
82
82
  * `closeDB()` had exactly one caller, the `db:sync` CLI, so a SIGINT abandoned
@@ -84,7 +84,7 @@ export function __resetTestTeardown(): void {
84
84
  *
85
85
  * Every step is isolated: a throwing hook is reported and the rest still run.
86
86
  * A shutdown that aborts halfway loses precisely the data this sequence exists
87
- * to save. The whole sequence is bounded by `timeoutMs` for the same reason —
87
+ * to save. The whole sequence is bounded by `timeoutMs` for the same reason:
88
88
  * losing the tail of a flush beats never exiting.
89
89
  */
90
90
  export function runShutdownSequence(
@@ -94,7 +94,7 @@ export function runShutdownSequence(
94
94
  return running
95
95
  }
96
96
 
97
- /** Test seam — see `__setTestConfig`. Clears the once-per-process memo. */
97
+ /** Test seam. See `__setTestConfig`. Clears the once-per-process memo. */
98
98
  export function __resetShutdownSequence(): void {
99
99
  running = null
100
100
  }
@@ -139,7 +139,7 @@ async function shutdown(): Promise<void> {
139
139
  }
140
140
 
141
141
  // Inside the step, not above it: an `await import()` that fails throws, and
142
- // out here that rejection escaped the sequence entirely — taking the resource
142
+ // out here that rejection escaped the sequence entirely, taking the resource
143
143
  // close below with it and, in `worker.ts`, the `process.exit(0)` that follows.
144
144
  await step('plugin onShutdown', async () => {
145
145
  const { PluginHooks } = await import('@bakery-framework/core/core/plugins')
package/src/threads.ts CHANGED
@@ -10,7 +10,7 @@ export const FLUSH_TIMEOUT_MS = 5000
10
10
 
11
11
  /**
12
12
  * The part of `Worker` this module needs. Narrow enough that a test can supply
13
- * a plain object, which is the only practical way to exercise the timeout — a
13
+ * a plain object, which is the only practical way to exercise the timeout: a
14
14
  * real Worker that never acknowledges means spawning a real server.
15
15
  */
16
16
  export interface FlushTarget {
@@ -20,7 +20,7 @@ export interface FlushTarget {
20
20
  }
21
21
 
22
22
  /**
23
- * Ask every worker to flush, and resolve when they all have — or when
23
+ * Ask every worker to flush, and resolve when they all have, or when
24
24
  * `timeoutMs` elapses, whichever comes first.
25
25
  *
26
26
  * `worker.terminate()` is immediate: a worker's shutdown hooks never run, so
@@ -87,7 +87,7 @@ export const RESPAWN_RESET_AFTER_MS = 60_000
87
87
  * backoff, `RESPAWN_BASE_DELAY_MS` doubling per consecutive failure up to
88
88
  * `RESPAWN_MAX_DELAY_MS`. A fixed 100ms meant a worker that died during boot
89
89
  * (bad DB URL, port conflict) re-ran initDB/setupServer ~10 times a second
90
- * indefinitely. Pure — delay = f(consecutiveFailures); the caller owns the
90
+ * indefinitely. Pure: delay = f(consecutiveFailures); the caller owns the
91
91
  * count and resets it after `RESPAWN_RESET_AFTER_MS` of survival.
92
92
  */
93
93
  export function respawnDelayMs(consecutiveFailures: number): number {
@@ -108,7 +108,7 @@ export async function handleThreadsMaster(threadCount: number) {
108
108
  if (process.platform !== 'linux' && threadCount > 1) {
109
109
  // Not just Windows: the multi-worker model needs kernel-level SO_REUSEPORT
110
110
  // load balancing, which only Linux provides. On macOS N sockets either
111
- // fail to bind or never receive balanced traffic — and a bind failure
111
+ // fail to bind or never receive balanced traffic, and a bind failure
112
112
  // feeds the respawn loop below.
113
113
  serveLog.CLUSTER_CLAMPED({
114
114
  platform: PLATFORM_NAMES[process.platform] ?? process.platform,
@@ -138,9 +138,9 @@ export async function handleThreadsMaster(threadCount: number) {
138
138
  // getter-only the assignment threw, and a Try() swallow hid it).
139
139
  //
140
140
  // THREAD_WORKER is deliberately NOT set. It is the flag that scales caches
141
- // *down* for N-way memory sharing — HandlerCache 500→50, HandlerMap
141
+ // *down* for N-way memory sharing: HandlerCache 500→50, HandlerMap
142
142
  // routeCache 5000→500, the tiered cache's memory tier ÷4, the SQLite page
143
- // caches ~10x smaller — and a single worker that owns the whole process
143
+ // caches ~10x smaller, and a single worker that owns the whole process
144
144
  // would get all of that for zero benefit. Setting it here made
145
145
  // `--threads 1` strictly worse than plain `bun run serve`; leaving it
146
146
  // unset makes this path identical to plain prod.
@@ -161,7 +161,7 @@ export async function handleThreadsMaster(threadCount: number) {
161
161
  const spawnWorker = (id: number) => {
162
162
  workerState.set(id, {
163
163
  isTerminated: false,
164
- // The streak survives the respawn — that is what makes it a streak.
164
+ // The streak survives the respawn: that is what makes it a streak.
165
165
  consecutiveFailures: workerState.get(id)?.consecutiveFailures ?? 0,
166
166
  spawnedAt: Date.now(),
167
167
  })
package/src/worker.ts CHANGED
@@ -42,7 +42,7 @@ const sharedPoolBound = new Promise<void>(resolve => {
42
42
  if (typeof self !== 'undefined' && 'addEventListener' in self) {
43
43
  self.addEventListener('message', (e: any) => {
44
44
  if (e.data?.type === 'INIT_SHARED_POOL' && e.data.buffer) {
45
- // Rebinds on every send on purpose — a late or repeated handover from
45
+ // Rebinds on every send on purpose: a late or repeated handover from
46
46
  // the master must still land; resolving the promise twice is a no-op.
47
47
  Bakery.sharedPool.bind(e.data.buffer)
48
48
  signalSharedPoolBound()
@@ -52,7 +52,7 @@ if (typeof self !== 'undefined' && 'addEventListener' in self) {
52
52
  // The cluster master asking for a flush before it calls terminate().
53
53
  // Deliberately no process.exit() here: inside a Worker thread that would
54
54
  // take the whole cluster down, master included. We flush, we acknowledge,
55
- // and the master terminates us — or gives up waiting and does it anyway.
55
+ // and the master terminates us, or gives up waiting and does it anyway.
56
56
  void (async () => {
57
57
  Bakery.server?.stop(true)
58
58
  await runShutdownSequence()
@@ -63,9 +63,9 @@ if (typeof self !== 'undefined' && 'addEventListener' in self) {
63
63
  }
64
64
 
65
65
  try {
66
- // Memoised no-op on the dev/prod entry paths, which already ran it (and
66
+ // Memoized no-op on the dev/prod entry paths, which already ran it (and
67
67
  // exited there if it threw). A cluster worker is spawned straight into this
68
- // file and passes through neither entry, so this is its first call — and in
68
+ // file and passes through neither entry, so this is its first call, and in
69
69
  // PROD a present-but-broken server.config.ts must fail the boot here rather
70
70
  // than serve the built-in defaults.
71
71
  await initConfig()
@@ -74,9 +74,9 @@ try {
74
74
  process.exit(1)
75
75
  }
76
76
 
77
- // Skipped entirely when the ORM is not installed — the app has no database and
77
+ // Skipped entirely when the ORM is not installed. The app has no database and
78
78
  // asked for none. Note what is *inside* the guard rather than outside it: once
79
- // the ORM is present, a failure to initialise is still fatal, because at that
79
+ // the ORM is present, a failure to initialize is still fatal, because at that
80
80
  // point the app does have a database and it does not work.
81
81
  if (hasORM()) {
82
82
  try {
@@ -100,7 +100,7 @@ try {
100
100
  if (import.meta.env.THREAD_WORKER) {
101
101
  // The master posts INIT_SHARED_POOL immediately after constructing this
102
102
  // Worker, but the message lands on a later event-loop turn than an immediate
103
- // Bun.serve — early requests would hit a worker-local SharedMemoryPool whose
103
+ // Bun.serve. Early requests would hit a worker-local SharedMemoryPool whose
104
104
  // counters and rate-limit state bind() then silently discards. So in
105
105
  // thread-worker mode only, wait for the handover before serving. Plain
106
106
  // prod/dev never set THREAD_WORKER and skip this entirely.
@@ -120,7 +120,7 @@ if (import.meta.env.THREAD_WORKER) {
120
120
  // Same resolver `startup.ts`'s banner and the dev master's URL use, so what we
121
121
  // bind and what they advertise cannot disagree. It throws on a malformed
122
122
  // `PORT` rather than handing `Bun.serve` a `NaN` it silently turns into a
123
- // random ephemeral port — which is how `PORT=3000x` used to produce a server
123
+ // random ephemeral port, which is how `PORT=3000x` used to produce a server
124
124
  // on 51570 under a banner reading `http://localhost:3000/`.
125
125
  let PORT: number
126
126
  try {
@@ -146,7 +146,7 @@ try {
146
146
  const hostname = getHostname(req)
147
147
  const hostConfig = resolveHostConfig(hostname)
148
148
 
149
- return hostStore.run({ config: hostConfig, hostname }, async () => {
149
+ return hostStore.run({ config: hostConfig, hostname, req }, async () => {
150
150
  const path = url.pathname
151
151
  req.startNs = Bun.nanoseconds()
152
152
  req.__hostname = hostname
@@ -157,7 +157,7 @@ try {
157
157
  const key = rateLimitKey(rl, req, hostname)
158
158
  const slot = rateLimitSlot(key)
159
159
  if (!Bakery.sharedPool.consumeToken(slot, rl.max, rl.refill)) {
160
- // Sampled — availability under flood: stdout is effectively
160
+ // Sampled: availability under flood: stdout is effectively
161
161
  // synchronous on Windows, so a line per rejection replays the
162
162
  // flood the limiter just absorbed as a logging flood.
163
163
  const suppressed = sampleRateLimitLog(key)
@@ -208,7 +208,7 @@ try {
208
208
  serveLog.UNHANDLED_ERR({ error: errorMsg(error) })
209
209
  const hostname = req ? getHostname(req) : ''
210
210
  const hostConfig = resolveHostConfig(hostname)
211
- return hostStore.run({ config: hostConfig, hostname }, async () => {
211
+ return hostStore.run({ config: hostConfig, hostname, req }, async () => {
212
212
  return await handleRequestError('/', req, error)
213
213
  })
214
214
  },
@@ -221,7 +221,7 @@ try {
221
221
  if (isDevWorker) {
222
222
  // One `.catch` on the whole chain, not one nested inside the `.then`. The
223
223
  // nested form covered `startCompileService` rejecting but left the dynamic
224
- // `import()` itself unhandled — so a compiler module that failed to load
224
+ // `import()` itself unhandled, so a compiler module that failed to load
225
225
  // produced an unhandled rejection rather than the WATCHER_ERR line that
226
226
  // exists to report exactly that.
227
227
  import('@bakery-framework/core/compiler')
@@ -241,7 +241,7 @@ async function handleShutdown(signal: string) {
241
241
 
242
242
  Bakery.server?.stop(true)
243
243
 
244
- // config.onShutdown, framework hooks, plugins, then resource close — see
244
+ // config.onShutdown, framework hooks, plugins, then resource close. See
245
245
  // shutdown.ts for why that order. It used to run only the middle two, so an
246
246
  // application's `onShutdown` was declared, defaulted to a no-op, and never
247
247
  // called. The sequence carries its own deadline.
@@ -252,7 +252,7 @@ async function handleShutdown(signal: string) {
252
252
  * `process.exit(0)` lives here, in a `finally`, because `handleShutdown` is
253
253
  * async and the signal handler cannot await it: registered directly, its
254
254
  * promise was neither awaited nor caught, so a rejection anywhere in teardown
255
- * became an unhandled rejection *and* skipped the exit — the process kept
255
+ * became an unhandled rejection *and* skipped the exit, the process kept
256
256
  * running with its listener stopped, answering nothing.
257
257
  */
258
258
  function onSignal(signal: string): void {