@teamscala/service-boot 0.23.494 → 0.23.495

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "name": "@teamscala/service-boot",
8
8
  "type": "module",
9
- "version": "0.23.494",
9
+ "version": "0.23.495",
10
10
  "sideEffects": false,
11
11
  "imports": {
12
12
  "#service-boot/*": "./src/*",
@@ -18,16 +18,13 @@
18
18
  "dependencies": {
19
19
  "@orpc/server": "*",
20
20
  "@teamscala/agreement-generation": "*",
21
- "@teamscala/api-resend": "*",
22
21
  "@teamscala/auth": "*",
23
22
  "@teamscala/cache": "*",
24
23
  "@teamscala/chat-completion": "*",
25
24
  "@teamscala/db": "*",
26
25
  "@teamscala/db-validation": "*",
27
26
  "@teamscala/document-signing": "*",
28
- "@teamscala/email": "*",
29
27
  "@teamscala/email-client": "*",
30
- "@teamscala/email-template": "*",
31
28
  "@teamscala/encryption": "*",
32
29
  "@teamscala/event-log": "*",
33
30
  "@teamscala/file-lock": "*",
@@ -38,14 +35,11 @@
38
35
  "@teamscala/http": "*",
39
36
  "@teamscala/logger": "*",
40
37
  "@teamscala/mcp-multi-session": "*",
41
- "@teamscala/notify-cache": "*",
42
38
  "@teamscala/object-storage": "*",
43
39
  "@teamscala/orpc": "*",
44
40
  "@teamscala/os": "*",
45
41
  "@teamscala/publish-registry": "*",
46
- "@teamscala/realtime-utils": "*",
47
42
  "@teamscala/resolver-engine": "*",
48
- "@teamscala/safe-fallback": "*",
49
43
  "@teamscala/timing": "*",
50
44
  "@teamscala/tool-executor": "*",
51
45
  "@teamscala/watchdog": "*",
@@ -55,7 +55,7 @@
55
55
  // be silently bypassed; a valid local-export chunk carries a
56
56
  // class/function/const/var/let/import, a valid re-export carries `from`).
57
57
 
58
- import { mkdir, readdir, rename, rm } from "node:fs/promises";
58
+ import { mkdir, readdir, rm } from "node:fs/promises";
59
59
  import { join } from "node:path";
60
60
  import { getAppLogger } from "@teamscala/logger/app-loggers";
61
61
  import { fileHash, removeStaleHashedFiles } from "./hashed-output.ts";
@@ -79,7 +79,6 @@ export async function buildHydrateBundle(
79
79
  ): Promise<HydrateBundleResult | null> {
80
80
  const entry = join(cwd, "src", "entry-client.ts");
81
81
  const outDir = join(cwd, "dist", "client");
82
- const tmpFile = join(outDir, `${HYDRATE_BASE}.js`);
83
82
 
84
83
  if (!(await Bun.file(entry).exists())) {
85
84
  log.warn(
@@ -123,7 +122,26 @@ export async function buildHydrateBundle(
123
122
  entrypoints: [entry],
124
123
  target: "browser",
125
124
  outdir: outDir,
126
- naming: `${HYDRATE_BASE}.js`,
125
+ // THE BUNDLER OWNS THE HASH, because it also writes the references.
126
+ // Under `splitting: true` + `publicPath`, Bun bakes the entry's
127
+ // build-time name into every chunk that imports it. Naming the entry
128
+ // `hydrate.js` here and RENAMING it to `hydrate-<hash>.js` afterwards
129
+ // moved the file but could not rewrite the specifiers already emitted
130
+ // into the chunks — so every chunk imported `/assets/hydrate.js`, which
131
+ // no longer existed. 404 on the module, the importing block dies, and
132
+ // the page still returns 200 with every guard green.
133
+ //
134
+ // MEASURED 2026-09-08 on quiz.scala.business: `AssessmentFlowBlock`
135
+ // failed with "Failed to fetch dynamically imported module"; crawling
136
+ // the served graph (142 level-1 + 76 level-2 assets) found exactly one
137
+ // broken URL — `/assets/hydrate.js` — imported by the entry itself and
138
+ // by most chunks. Hash applied OUTSIDE the generator is two artefacts
139
+ // that must agree with nothing making them (`one-generator-pattern`).
140
+ naming: {
141
+ entry: `${HYDRATE_BASE}-[hash].[ext]`,
142
+ chunk: "chunk-[hash].[ext]",
143
+ asset: "[name]-[hash].[ext]",
144
+ },
127
145
  minifyWhitespace: process.env.NODE_ENV === "production",
128
146
  minifySyntax: process.env.NODE_ENV === "production",
129
147
  minifyIdentifiers: false,
@@ -232,10 +250,24 @@ export async function buildHydrateBundle(
232
250
  );
233
251
  }
234
252
 
235
- const hash = await fileHash(tmpFile);
236
- const hashedName = `${HYDRATE_BASE}-${hash}.js`;
253
+ // Bun emitted the hashed entry itself, so the name it wrote into every
254
+ // chunk IS the name on disk. Read it back from the build's own output
255
+ // list rather than reconstructing it — a reconstructed name is a second
256
+ // copy of a fact the bundler already owns, which is the defect this
257
+ // change removes (`a-mutating-verb-reads-back-what-it-claims`).
258
+ const entryOutput = result.outputs.find(
259
+ (o) => o.kind === "entry-point",
260
+ );
261
+ if (!entryOutput) {
262
+ throw new Error(
263
+ "[build-client-assets] Bun.build reported success but emitted no entry-point output — refusing to ship a bundle with no entry.",
264
+ );
265
+ }
266
+ const hashedName = entryOutput.path.split("/").pop() as string;
237
267
  const hashedPath = join(outDir, hashedName);
238
- await rename(tmpFile, hashedPath);
268
+ const hash =
269
+ /^hydrate-([A-Za-z0-9]+)\.js$/.exec(hashedName)?.[1] ??
270
+ (await fileHash(hashedPath));
239
271
  removeStaleHashedFiles(outDir, HYDRATE_BASE, "js", hashedName);
240
272
 
241
273
  // Splitting emits one chunk per lazy import() alongside the entry. Keep the
@@ -10,7 +10,7 @@
10
10
  * kind:ui service's stylesheet (2026-09-01, operator-visible).
11
11
  */
12
12
  import { describe, expect, test } from "bun:test";
13
- import { mkdir, rm, writeFile } from "node:fs/promises";
13
+ import { mkdir, rm } from "node:fs/promises";
14
14
  import { join } from "node:path";
15
15
  import { tmpdir } from "node:os";
16
16
  import { scanSourceCandidates } from "./source-candidates.ts";
@@ -23,7 +23,7 @@ async function makeSourceTree(tag: string, files: Record<string, string>) {
23
23
  for (const [rel, content] of Object.entries(files)) {
24
24
  const p = join(root, rel);
25
25
  await mkdir(p.slice(0, p.lastIndexOf("/")), { recursive: true });
26
- await writeFile(p, content);
26
+ await Bun.file(p).write(content);
27
27
  }
28
28
  return root;
29
29
  }
@@ -14,7 +14,7 @@
14
14
  * boundaries the verification layer depends on.
15
15
  */
16
16
 
17
- import { mkdtemp, mkdir, readdir, rm, writeFile } from "node:fs/promises";
17
+ import { mkdtemp, mkdir, readdir, rm } from "node:fs/promises";
18
18
  import { tmpdir } from "node:os";
19
19
  import { join } from "node:path";
20
20
  import { afterAll, afterEach, describe, expect, test } from "bun:test";
@@ -36,10 +36,7 @@ async function makeDir(): Promise<string> {
36
36
  async function seedMaps(outDir: string, names: string[]): Promise<void> {
37
37
  await mkdir(outDir, { recursive: true });
38
38
  for (const name of names) {
39
- await writeFile(
40
- join(outDir, name),
41
- JSON.stringify({ version: 3, sources: ["a.ts"], mappings: "AAAA" }),
42
- );
39
+ await Bun.file(join(outDir, name)).write(JSON.stringify({ version: 3, sources: ["a.ts"], mappings: "AAAA" }));
43
40
  }
44
41
  }
45
42
 
@@ -14,7 +14,7 @@
14
14
  * explicitly, never resolved and never thrown on.
15
15
  */
16
16
  import { describe, expect, test } from "bun:test";
17
- import { mkdir, rm, writeFile } from "node:fs/promises";
17
+ import { mkdir, rm } from "node:fs/promises";
18
18
  import { join } from "node:path";
19
19
  import { tmpdir } from "node:os";
20
20
  import { bunResolveStylesheet } from "./styles-build.ts";
@@ -25,7 +25,7 @@ async function makeTree(tag: string, files: Record<string, string>) {
25
25
  for (const [rel, content] of Object.entries(files)) {
26
26
  const p = join(root, rel);
27
27
  await mkdir(p.slice(0, p.lastIndexOf("/")), { recursive: true });
28
- await writeFile(p, content);
28
+ await Bun.file(p).write(content);
29
29
  }
30
30
  return root;
31
31
  }
@@ -2,14 +2,13 @@
2
2
  * @system server-boot
3
3
  * @status handwritten
4
4
  * @edit edit directly
5
- * @indivisible-unit owner=unassigned reason=81 lines; pre-2026-08-27 exemption, unreviewed
6
5
  *
7
6
  * Install harness — sandbox-mounts each declared package and captures
8
7
  * registry.require() calls via AsyncLocalStorage. Validates all required
9
8
  * rows exist before returning. Throws HarnessInstallError with full
10
9
  * context on failure. Per Constitution Rule 2 step 3.
11
10
  *
12
- * Marked @indivisible-unit: HarnessInstallError is the error class
11
+ * Marked: HarnessInstallError is the error class
13
12
  * validatePackageDependencies (might) throw — they form one canonical
14
13
  * "install harness validation" unit (the validator + the failure-mode
15
14
  * class it produces).
@@ -2,7 +2,6 @@
2
2
  * @system server-boot
3
3
  * @status handwritten
4
4
  * @edit edit directly
5
- * @indivisible-unit owner=unassigned reason=188 lines; pre-2026-08-27 exemption, unreviewed
6
5
  *
7
6
  * One-call install of the entire logging pipeline. After installLogging()
8
7
  * runs, every error in the service — process-level, HTTP middleware, Hono
@@ -11,7 +10,7 @@
11
10
  * loga (dev) with full context. Application code never imports the logger
12
11
  * or wraps anything in try/catch for telemetry purposes.
13
12
  *
14
- * Marked @indivisible-unit: installLogging (early bootstrap, hardcoded
13
+ * Marked: installLogging (early bootstrap, hardcoded
15
14
  * defaults) and applyLoggerConfig (late-stage, DB-driven) are the two
16
15
  * phases of the same one-call install — the service boot pipeline calls
17
16
  * them in sequence with the same serviceSlug.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @system ui-service-boot
3
- * @status handwritten @indivisible-unit owner=unassigned reason=91 lines; pre-2026-08-27 exemption, unreviewed
3
+ * @status handwritten
4
4
  * @edit edit directly
5
5
  *
6
6
  * The two SWR loader-graph caches (static-shell + parameterised routes) for the
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @system ui-service-boot
3
- * @status handwritten @indivisible-unit owner=unassigned reason=82 lines; pre-2026-08-27 exemption, unreviewed
3
+ * @status handwritten
4
4
  * @edit edit directly
5
5
  *
6
6
  * Meta/title helpers for the route registrar: interpolate the route's
@@ -2,7 +2,6 @@
2
2
  * @system service-boot
3
3
  * @status handwritten
4
4
  * @edit edit directly
5
- * @indivisible-unit owner=unassigned reason=299 lines; pre-2026-08-27 exemption, unreviewed
6
5
  *
7
6
  * One cohesive unit — the branded resource-not-found / holding page: the
8
7
  * PrimaryResourceMissingError trigger, the org-logo resolver the page