Skip to content

feat(router): lite Node fetch bridge for the fetch dispatch arm (#89) - #94

Merged
devin-ai-integration[bot] merged 2 commits into
mainfrom
devin/1792397061-lite-fetch-bridge
Oct 19, 2026
Merged

devin-ai-integration[bot] merged 2 commits into
mainfrom
devin/1792397061-lite-fetch-bridge

Conversation

@espetro

@espetro espetro commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes #89.

Phase 1 — premise check: node:inspector CPU profile of chttp-fetch-node over ~1.46M bench requests put undici frames at 13.8% of total CPU plus 6.0% of web-streams machinery they drive (~27% of non-idle CPU) vs 4.4% for dispatch. The undici Request/Headers/Response churn is the dominant removable cost — full attribution in bench/scriptc-evidence/fetch-node-undici-cpuprof-2026-10-19.md.

Phase 2 — implementation (packages/router/src/lite.ts):

  • LiteHeaders — WebHeadersView over IncomingMessage.headers, zero materialization (WebReqView.headers narrowed from Headers to the structural WebHeadersView; real Headers still satisfy it).
  • LiteRequest — WebReqView over the IncomingMessage: absolute url, lazy text()/arrayBuffer()/body. Body-carrying methods drain to raw Buffer[] at the edge — node dumps unconsumed request bytes on res.end, so true laziness would hang late c.req.raw readers; decode stays lazy.
  • LiteResponse extends FetchResponseSink — writes straight to ServerResponse: status/headers/chunks on the wire as produced, writeHead()-armed streaming flushes per chunk (_ctl → res.write/res.end), adopt() mirrors status+headers and the bridge pumps the adopted body stream.
  • app.fetchBridge() / app.serveFetch({port, host?}) — opt-in Node entry; app.fetch is unchanged for WinterCG hosts.
  • lite.request() lazily materializes a real undici Request for fetch(c.req.raw)-style consumers (documented edge).

Known limit: LiteResponse compiles under scriptc (ws-echo vendors the whole router source) and scriptc lowers neither res.setHeader(string[]) nor res.appendHeader — appended set-cookie collapses to last-write-wins, NodeResponseSink's documented behavior. app.fetch keeps true multi-cookie.

Bench (fresh same-run cells, appended to bench/RESULTS-v2.md):

compare json user echo64 echo1k deep files missing mixed
fetch-node / node 50% 56% 51% 51% 52% 50% 53% 52%
lite-node / node 95% 101% 94% 86% 93% 91% 95% 95%
lite-node / fetch-node 188% 182% 183% 168% 179% 182% 178% 185%

RSS after 100k reqs: 146 MB lite vs 189 MB undici bridge. bench/apps/chttp-fetch-lite.ts joins the SUBJECTS table and the scriptc gate (100% static, all 11 gates green); 8 new tests in test/lite-bridge.test.js, 119/119 router tests pass; examples/ws-echo ELF build verified locally.

Link to Devin session: https://app.devin.ai/sessions/e5050c4a69cb44609235ea0956702552
Open in Devin Desktop: https://app.devin.ai/desktop/session/e5050c4a69cb44609235ea0956702552?variant=devin
Requested by: @espetro

app.fetchBridge()/app.serveFetch() run _genFetch/_dispatchPrim over
LiteRequest (a WebReqView on IncomingMessage — zero-copy LiteHeaders,
lazy Buffer-drain bodies, lazy request() materialization) and
LiteResponse (a FetchResponseSink that writes straight to
ServerResponse incl. writeHead()-armed streaming and adopted-Response
body pumping). WebReqView.headers narrows to the structural
WebHeadersView so the lite map assigns cleanly.

cpu-prof assessment: undici + web-streams frames were ~20% of total
CPU (~27% of non-idle) on chttp-fetch-node vs 4.4% dispatch — the
issue premise held, so the lite path was built. Fresh cell:
chttp-fetch-lite-node lands at 86-101% of chttp-node (mean ~94%) vs
the undici bridge's 50-56%, RSS 146 MB vs 189 MB (RESULTS-v2.md).

bench/apps/chttp-fetch-lite.ts joins the bench SUBJECTS table and the
scriptc gate (100% static). New test file: lite-bridge.test.js (8
tests — wire behavior, lazy bodies, multi-cookie, adoption, 405).
@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Deploying chttpjs with  Cloudflare Pages  Cloudflare Pages

Latest commit: f1eb852
Status: ✅  Deploy successful!
Preview URL: https://5e8a0992.chttpjs.pages.dev
Branch Preview URL: https://devin-1792397061-lite-fetch.chttpjs.pages.dev

View logs

…89)

ws-echo vendors the whole router source into its ELF build; scriptc
lowers neither res.setHeader(string[]) nor res.appendHeader, so
LiteResponse.appendHeader collapses repeated headers to last-write-
wins — NodeResponseSink's documented limit. The append-only hdrList
still keeps the full list for bookkeeping; app.fetch keeps true
multi-cookie. Lite bridge test now asserts the collapse.
@devin-ai-integration
devin-ai-integration Bot merged commit 4ff7645 into main Oct 19, 2026
3 checks passed
@devin-ai-integration
devin-ai-integration Bot deleted the devin/1792397061-lite-fetch-bridge branch October 19, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: Node-side fetch bridge bypassing undici Request/Response churn

1 participant