chttp + React
++ Server says: {message} +
+From 6feebb5aa1a4a765625c6c46e22c5361c784bd99 Mon Sep 17 00:00:00 2001 From: Quino Terrasa <26749475+espetro@users.noreply.github.com> Date: Mon, 19 Oct 2026 07:50:08 +0000 Subject: [PATCH 1/4] fix(router): probe _headers with hasOwnProperty in ReqView.header() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bare this._headers[key] read throws "record has no key" under program-code (single-graph/vendored) scriptc compilation — the typed record slot cannot represent undefined — while packed-dist compilation tolerates the read. Any optional request header (if-none-match, accept-encoding, accept) therefore fenced compiled apps that read headers through vendored sources; packed consumers were unaffected. Guard both the _headers and _headersDistinct lookups with Object.prototype.hasOwnProperty.call, the same idiom ReqView already uses for optional fields. Verified: a vendored @chttp/router + @chttp/static graph now serves 200/304 where it previously aborted. --- packages/router/src/index.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/router/src/index.ts b/packages/router/src/index.ts index 2e0a52b..fca9f7c 100644 --- a/packages/router/src/index.ts +++ b/packages/router/src/index.ts @@ -372,8 +372,14 @@ export class ReqViewCore { const key = String(name).toLowerCase(); // Read from the materialized _headers table. The Node path copies // `raw.headers` once at construction; the app.fetch path populates - // it from the captured Web Request's Headers. - const v = this._headers[key]; + // it from the captured Web Request's Headers. Presence is probed with + // hasOwnProperty, never a bare index: under program-code compilation a + // missing typed-record key throws ("record has no key"), while packed + // dist tolerates the read — this is what fenced `If-None-Match` / + // `Accept-Encoding` lookups in single-graph (vendored) builds. + const v = Object.prototype.hasOwnProperty.call(this._headers, key) + ? this._headers[key] + : undefined; if (v !== undefined) { return String(v); } @@ -383,7 +389,9 @@ export class ReqViewCore { // Headers are single-value, joined in copyHeaders' string arm). // d[0] === v[0] for Node joins, so the first-element read is // identical to the IncomingMessage semantics. - const d = this._headersDistinct[key]; + const d = Object.prototype.hasOwnProperty.call(this._headersDistinct, key) + ? this._headersDistinct[key] + : undefined; if (d !== undefined && d.length > 0) { return String(d[0]); } From 7e43dcaff1bb7e5a62010939b33f56fadfdc53c5 Mon Sep 17 00:00:00 2001 From: Quino Terrasa <26749475+espetro@users.noreply.github.com> Date: Mon, 19 Oct 2026 07:50:08 +0000 Subject: [PATCH 2/4] =?UTF-8?q?feat(examples):=20csr-react=20=E2=80=94=20R?= =?UTF-8?q?eact=20SPA=20on=20@chttp/static=20+=20baked-assets=20recipe=20(?= =?UTF-8?q?issue=20#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds examples/integrations/csr-react: a Vite-built React SPA served by serveStatic({ spa: true }) with an /api slice on the same router, plus the JSON-baked assets recipe — scripts/bake.mjs walks dist/ and emits an EmbeddedAssets subclass (BakedAssets) for the single-binary path, and scripts/vendor.mjs stages one program-code graph (the same pattern as ws-echo) so scriptc lowers the subclass. docs/static.md gains "Baking assets into the binary" documenting the recipe and the case-6 (JSON b64) vs case-9 (FFI blob) tradeoffs from the frontend probes; docs/context.md documents c.bytes. Node tests cover the honesty gates: sha256 byte-integrity of a hashed asset, ETag -> 304 with empty body, SPA Accept rules, /api JSON 404, and the same assertions against the baked source. ELF smoke verified manually under scriptc 0.3.6: 100% static coverage, byte-identical asset, 304 revalidation, SPA fallback. --- docs/context.md | 1 + docs/examples.md | 23 ++-- docs/static.md | 59 +++++---- examples/README.md | 39 +++--- examples/integrations/csr-react/.gitignore | 5 + examples/integrations/csr-react/README.md | 25 ++++ examples/integrations/csr-react/package.json | 26 ++++ .../integrations/csr-react/scripts/bake.mjs | 108 ++++++++++++++++ .../integrations/csr-react/scripts/vendor.mjs | 49 +++++++ examples/integrations/csr-react/src/app.ts | 14 ++ .../integrations/csr-react/src/main.baked.ts | 9 ++ examples/integrations/csr-react/src/main.ts | 6 + .../integrations/csr-react/test/csr.test.ts | 122 ++++++++++++++++++ .../integrations/csr-react/vite.config.mjs | 9 ++ .../integrations/csr-react/web/index.html | 12 ++ .../integrations/csr-react/web/src/App.jsx | 21 +++ .../integrations/csr-react/web/src/main.jsx | 10 ++ .../integrations/csr-react/web/src/style.css | 16 +++ pnpm-lock.yaml | 46 +++++++ pnpm-workspace.yaml | 1 + 20 files changed, 549 insertions(+), 52 deletions(-) create mode 100644 examples/integrations/csr-react/.gitignore create mode 100644 examples/integrations/csr-react/README.md create mode 100644 examples/integrations/csr-react/package.json create mode 100644 examples/integrations/csr-react/scripts/bake.mjs create mode 100644 examples/integrations/csr-react/scripts/vendor.mjs create mode 100644 examples/integrations/csr-react/src/app.ts create mode 100644 examples/integrations/csr-react/src/main.baked.ts create mode 100644 examples/integrations/csr-react/src/main.ts create mode 100644 examples/integrations/csr-react/test/csr.test.ts create mode 100644 examples/integrations/csr-react/vite.config.mjs create mode 100644 examples/integrations/csr-react/web/index.html create mode 100644 examples/integrations/csr-react/web/src/App.jsx create mode 100644 examples/integrations/csr-react/web/src/main.jsx create mode 100644 examples/integrations/csr-react/web/src/style.css diff --git a/docs/context.md b/docs/context.md index b684fce..3a2766a 100644 --- a/docs/context.md +++ b/docs/context.md @@ -26,6 +26,7 @@ Helpers write at statement level and return `c` for chaining. After one runs, th c.json({ ok: true }); c.text("plain"); c.html("
+ Server says: {message} +
+