Skip to content

feat(node-streams): wire primitives into render pipeline (7/8)#89860

Draft
feedthejim wants to merge 1 commit intofeedthejim/node-stream-03-primitivesfrom
feedthejim/node-stream-04-pipeline
Draft

feat(node-streams): wire primitives into render pipeline (7/8)#89860
feedthejim wants to merge 1 commit intofeedthejim/node-stream-03-primitivesfrom
feedthejim/node-stream-04-pipeline

Conversation

@feedthejim
Copy link
Copy Markdown
Contributor

@feedthejim feedthejim commented Feb 11, 2026

Summary

Integrate node stream primitives into the actual render paths, all gated behind experimental.useNodeStreams.

Core render pipeline (app-render.tsx):

  • teeDebugChannelForSsrAndBrowser(): tees debug channel for SSR + browser HMR delivery
  • debugChannelClientForBrowser(): converts Node Readable to web ReadableStream for HMR
  • useNodeStreams branching throughout: Fizz renders, flight streams, prerender paths, PPR resume
  • Type widening: AnyStream now includes Readable at the switcher boundary

Prerender support (app-render-prerender-utils.ts):

  • ReactServerResult accepts NodeReadable with AsyncLocalStorage context preservation
  • createReactServerPrerenderResultFromPrerender() dispatcher for node/web paths
  • processNodePrelude() for node stream prelude emptiness detection

Other integrations:

  • instant-validation.tsx: node stream branches for validation renders
  • render-result.ts: accept Node Readable as response body, pipeToNodeWritable() method
  • flight-render-result.ts: accept Node Readable in constructor
  • app-page.ts: PPR resume path using PassThrough + pipeToNodeWritable()

Test plan

  • pnpm --filter=next types passes
  • pnpm test-start-turbo test/e2e/app-dir/ssr-in-rsc/ passes
  • pnpm testonly test/e2e/app-dir/user-bundle-node-stream-guard/ passes
  • Flag is off by default: existing tests unaffected
  • With __NEXT_USE_NODE_STREAMS=true: app-dir e2e tests pass

@feedthejim feedthejim force-pushed the feedthejim/node-stream-04-pipeline branch from 635593b to ba0aa8d Compare February 11, 2026 21:35
@feedthejim feedthejim changed the title feat(node-streams): wire node stream primitives into render pipeline feat(node-streams): wire primitives into render pipeline Feb 11, 2026
@feedthejim feedthejim force-pushed the feedthejim/node-stream-03-primitives branch from bf6e861 to 5d447ac Compare February 11, 2026 21:41
@feedthejim feedthejim force-pushed the feedthejim/node-stream-04-pipeline branch from ba0aa8d to 4f8168c Compare February 11, 2026 21:41
@feedthejim feedthejim force-pushed the feedthejim/node-stream-03-primitives branch from 5d447ac to 99fdd5a Compare February 11, 2026 21:46
@feedthejim feedthejim force-pushed the feedthejim/node-stream-04-pipeline branch from 4f8168c to c4c989d Compare February 11, 2026 21:46
@feedthejim feedthejim force-pushed the feedthejim/node-stream-03-primitives branch from 99fdd5a to 99bd282 Compare February 11, 2026 21:50
@feedthejim feedthejim force-pushed the feedthejim/node-stream-04-pipeline branch from c4c989d to 9efcb95 Compare February 11, 2026 21:50
@feedthejim feedthejim force-pushed the feedthejim/node-stream-04-pipeline branch 2 times, most recently from c7f7e91 to f7db683 Compare February 11, 2026 22:34
@feedthejim feedthejim force-pushed the feedthejim/node-stream-03-primitives branch from 8813630 to 4199831 Compare February 11, 2026 22:45
@feedthejim feedthejim force-pushed the feedthejim/node-stream-04-pipeline branch from f7db683 to 0665da4 Compare February 11, 2026 22:45
@nextjs-bot
Copy link
Copy Markdown
Collaborator

nextjs-bot commented Feb 11, 2026

Stats from current PR

🔴 3 regressions

Metric Canary PR Change Trend
node_modules Size 472 MB 576 MB 🔴 +104 MB (+22%) ▁▁███
Webpack Build Time 13.962s 14.884s 🔴 +922ms (+7%) ▃▁▂▁▁
Webpack Build Time (cached) 14.179s 15.013s 🔴 +834ms (+6%) ▃▁▂▁▂
📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 457ms 456ms ▁▁▁▁▁
Cold (Ready in log) 451ms 452ms ▁▁▂▂▂
Cold (First Request) 926ms 931ms ▁▄▅▄▃
Warm (Listen) 456ms 456ms ▁▁▁▁▁
Warm (Ready in log) 451ms 450ms ▁▁▁▁▂
Warm (First Request) 372ms 372ms ▂▂▂▁▃
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 455ms 455ms ▁▁▁▁▁
Cold (Ready in log) 439ms 439ms ▃▃▂▃▂
Cold (First Request) 1.930s 1.958s ▄▂▃▂▃
Warm (Listen) 455ms 457ms ▁▁▁▁▁
Warm (Ready in log) 440ms 438ms ▆▅▄▄▄
Warm (First Request) 1.966s 1.946s ▄▂▂▂▃

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.309s 4.398s ▂▁▁▁▄
Cached Build 4.362s 4.430s ▂▁▁▁▄
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 13.962s 14.884s 🔴 +922ms (+7%) ▃▁▂▁▁
Cached Build 14.179s 15.013s 🔴 +834ms (+6%) ▃▁▂▁▂
node_modules Size 472 MB 576 MB 🔴 +104 MB (+22%) ▁▁███
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles: **397 kB** → **397 kB** ⚠️ +13 B

80 files with content-based hashes (individual files not comparable between builds)

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 757 B 763 B
Total 757 B 763 B ⚠️ +6 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 452 B 449 B
Total 452 B 449 B ✅ -3 B

📦 Webpack

Client

Main Bundles
Canary PR Change
5528-HASH.js gzip 5.47 kB N/A -
6280-HASH.js gzip 57.1 kB N/A -
6335.HASH.js gzip 169 B N/A -
912-HASH.js gzip 4.53 kB N/A -
e8aec2e4-HASH.js gzip 62.6 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 254 B 254 B
main-HASH.js gzip 39.1 kB 39.1 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
262-HASH.js gzip N/A 4.53 kB -
2889.HASH.js gzip N/A 169 B -
5602-HASH.js gzip N/A 5.48 kB -
6948ada0-HASH.js gzip N/A 62.6 kB -
9544-HASH.js gzip N/A 57.7 kB -
Total 231 kB 231 kB ⚠️ +642 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 194 B
_error-HASH.js gzip 183 B 180 B 🟢 3 B (-2%)
css-HASH.js gzip 331 B 330 B
dynamic-HASH.js gzip 1.81 kB 1.81 kB
edge-ssr-HASH.js gzip 256 B 256 B
head-HASH.js gzip 351 B 352 B
hooks-HASH.js gzip 384 B 383 B
image-HASH.js gzip 580 B 581 B
index-HASH.js gzip 260 B 260 B
link-HASH.js gzip 2.5 kB 2.5 kB
routerDirect..HASH.js gzip 320 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.97 kB 7.97 kB ✅ -2 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 126 kB 126 kB
page.js gzip 249 kB 251 kB 🔴 +2.41 kB (+1%)
Total 374 kB 377 kB ⚠️ +2.32 kB
Middleware
Canary PR Change
middleware-b..fest.js gzip 617 B 614 B
middleware-r..fest.js gzip 156 B 155 B
middleware.js gzip 43.4 kB 44 kB 🔴 +573 B (+1%)
edge-runtime..pack.js gzip 842 B 842 B
Total 45 kB 45.6 kB ⚠️ +569 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 715 B 718 B
Total 715 B 718 B ⚠️ +3 B
Build Cache
Canary PR Change
0.pack gzip 3.87 MB 3.92 MB 🔴 +54.7 kB (+1%)
index.pack gzip 103 kB 105 kB 🔴 +1.52 kB (+1%)
index.pack.old gzip 103 kB 103 kB
Total 4.07 MB 4.13 MB ⚠️ +56.8 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 316 kB 322 kB 🔴 +6.02 kB (+2%)
app-page-exp..prod.js gzip 167 kB 171 kB 🔴 +3.41 kB (+2%)
app-page-tur...dev.js gzip 315 kB 321 kB 🔴 +6 kB (+2%)
app-page-tur..prod.js gzip 167 kB 170 kB 🔴 +3.42 kB (+2%)
app-page-tur...dev.js gzip 312 kB 318 kB 🔴 +5.89 kB (+2%)
app-page-tur..prod.js gzip 165 kB 169 kB 🔴 +3.45 kB (+2%)
app-page.run...dev.js gzip 312 kB 318 kB 🔴 +5.93 kB (+2%)
app-page.run..prod.js gzip 165 kB 169 kB 🔴 +3.44 kB (+2%)
app-route-ex...dev.js gzip 70.5 kB 70.6 kB
app-route-ex..prod.js gzip 49 kB 49.1 kB
app-route-tu...dev.js gzip 70.5 kB 70.6 kB
app-route-tu..prod.js gzip 49 kB 49.1 kB
app-route-tu...dev.js gzip 70.1 kB 70.2 kB
app-route-tu..prod.js gzip 48.8 kB 48.9 kB
app-route.ru...dev.js gzip 70.1 kB 70.2 kB
app-route.ru..prod.js gzip 48.7 kB 48.9 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 43.2 kB 50.6 kB 🔴 +7.38 kB (+17%)
pages-api-tu..prod.js gzip 32.9 kB 37.6 kB 🔴 +4.73 kB (+14%)
pages-api.ru...dev.js gzip 43.2 kB 50.5 kB 🔴 +7.38 kB (+17%)
pages-api.ru..prod.js gzip 32.8 kB 37.6 kB 🔴 +4.73 kB (+14%)
pages-turbo....dev.js gzip 52.5 kB 59.9 kB 🔴 +7.43 kB (+14%)
pages-turbo...prod.js gzip 38.4 kB 43.2 kB 🔴 +4.78 kB (+12%)
pages.runtim...dev.js gzip 52.5 kB 59.9 kB 🔴 +7.43 kB (+14%)
pages.runtim..prod.js gzip 38.4 kB 43.2 kB 🔴 +4.78 kB (+12%)
server.runti..prod.js gzip 63.5 kB 69.2 kB 🔴 +5.65 kB (+9%)
app-page-exp...dev.js gzip N/A 325 kB -
app-page-exp..prod.js gzip N/A 173 kB -
app-page-nod...dev.js gzip N/A 321 kB -
app-page-nod..prod.js gzip N/A 171 kB -
app-page-tur...dev.js gzip N/A 324 kB -
app-page-tur..prod.js gzip N/A 173 kB -
app-page-tur...dev.js gzip N/A 321 kB -
app-page-tur..prod.js gzip N/A 171 kB -
app-route-ex...dev.js gzip N/A 77.8 kB -
app-route-ex..prod.js gzip N/A 53.6 kB -
app-route-no...dev.js gzip N/A 77.4 kB -
app-route-no..prod.js gzip N/A 53.4 kB -
app-route-tu...dev.js gzip N/A 77.8 kB -
app-route-tu..prod.js gzip N/A 53.6 kB -
app-route-tu...dev.js gzip N/A 77.4 kB -
app-route-tu..prod.js gzip N/A 53.4 kB -
dist_client_...dev.js gzip N/A 332 B -
dist_client_...dev.js gzip N/A 324 B -
dist_client_...dev.js gzip N/A 334 B -
dist_client_...dev.js gzip N/A 326 B -
Total 2.8 MB 5.39 MB ⚠️ +2.6 MB
📝 Changed Files (45 files)

Files with changes:

  • app-page-exp..ntime.dev.js
  • app-page-exp..time.prod.js
  • app-page-exp..ntime.dev.js
  • app-page-exp..time.prod.js
  • app-page-nod..ntime.dev.js
  • app-page-nod..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page.runtime.dev.js
  • app-page.runtime.prod.js
  • app-route-ex..ntime.dev.js
  • app-route-ex..time.prod.js
  • app-route-ex..ntime.dev.js
  • app-route-ex..time.prod.js
  • ... and 25 more
View diffs
app-page-exp..ntime.dev.js
failed to diff
app-page-exp..time.prod.js

Diff too large to display

app-page-exp..ntime.dev.js
failed to diff
app-page-exp..time.prod.js
failed to diff
app-page-nod..ntime.dev.js
failed to diff
app-page-nod..time.prod.js

Diff too large to display

app-page-tur..ntime.dev.js
failed to diff
app-page-tur..time.prod.js

Diff too large to display

app-page-tur..ntime.dev.js
failed to diff
app-page-tur..time.prod.js
failed to diff
app-page-tur..ntime.dev.js
failed to diff
app-page-tur..time.prod.js

Diff too large to display

app-page-tur..ntime.dev.js
failed to diff
app-page-tur..time.prod.js
failed to diff
app-page.runtime.dev.js
failed to diff
app-page.runtime.prod.js
failed to diff
app-route-ex..ntime.dev.js

Diff too large to display

app-route-ex..time.prod.js

Diff too large to display

app-route-ex..ntime.dev.js

Diff too large to display

app-route-ex..time.prod.js

Diff too large to display

app-route-no..ntime.dev.js

Diff too large to display

app-route-no..time.prod.js

Diff too large to display

app-route-tu..ntime.dev.js

Diff too large to display

app-route-tu..time.prod.js

Diff too large to display

app-route-tu..ntime.dev.js

Diff too large to display

app-route-tu..time.prod.js

Diff too large to display

app-route-tu..ntime.dev.js

Diff too large to display

app-route-tu..time.prod.js

Diff too large to display

app-route-tu..ntime.dev.js

Diff too large to display

app-route-tu..time.prod.js

Diff too large to display

app-route.runtime.dev.js

Diff too large to display

app-route.ru..time.prod.js

Diff too large to display

dist_client_..ntime.dev.js
@@ -0,0 +1,2 @@
+"use strict";exports.ids=["dist_client_dev_noop-turbopack-hmr_js"],exports.modules={"./dist/client/dev/noop-turbopack-hmr.js"(module,exports1){function connect(){}Object.defineProperty(exports1,"__esModule",{value:!0}),Object.defineProperty(exports1,"connect",{enumerable:!0,get:function(){return connect}}),("function"==typeof exports1.default||"object"==typeof exports1.default&&null!==exports1.default)&&void 0===exports1.default.__esModule&&(Object.defineProperty(exports1.default,"__esModule",{value:!0}),Object.assign(exports1.default,exports1),module.exports=exports1.default)}};
+//# sourceMappingURL=dist_client_dev_noop-turbopack-hmr_js-experimental-nodestreams.runtime.dev.js.map
\ No newline at end of file
dist_client_..ntime.dev.js
@@ -0,0 +1,2 @@
+"use strict";exports.ids=["dist_client_dev_noop-turbopack-hmr_js"],exports.modules={"./dist/client/dev/noop-turbopack-hmr.js"(module,exports1){function connect(){}Object.defineProperty(exports1,"__esModule",{value:!0}),Object.defineProperty(exports1,"connect",{enumerable:!0,get:function(){return connect}}),("function"==typeof exports1.default||"object"==typeof exports1.default&&null!==exports1.default)&&void 0===exports1.default.__esModule&&(Object.defineProperty(exports1.default,"__esModule",{value:!0}),Object.assign(exports1.default,exports1),module.exports=exports1.default)}};
+//# sourceMappingURL=dist_client_dev_noop-turbopack-hmr_js-nodestreams.runtime.dev.js.map
\ No newline at end of file
dist_client_..ntime.dev.js
@@ -0,0 +1,2 @@
+"use strict";exports.ids=["dist_client_dev_noop-turbopack-hmr_js"],exports.modules={"./dist/client/dev/noop-turbopack-hmr.js"(module,exports1){function connect(){}Object.defineProperty(exports1,"__esModule",{value:!0}),Object.defineProperty(exports1,"connect",{enumerable:!0,get:function(){return connect}}),("function"==typeof exports1.default||"object"==typeof exports1.default&&null!==exports1.default)&&void 0===exports1.default.__esModule&&(Object.defineProperty(exports1.default,"__esModule",{value:!0}),Object.assign(exports1.default,exports1),module.exports=exports1.default)}};
+//# sourceMappingURL=dist_client_dev_noop-turbopack-hmr_js-turbo-experimental-nodestreams.runtime.dev.js.map
\ No newline at end of file
dist_client_..ntime.dev.js
@@ -0,0 +1,2 @@
+"use strict";exports.ids=["dist_client_dev_noop-turbopack-hmr_js"],exports.modules={"./dist/client/dev/noop-turbopack-hmr.js"(module,exports1){function connect(){}Object.defineProperty(exports1,"__esModule",{value:!0}),Object.defineProperty(exports1,"connect",{enumerable:!0,get:function(){return connect}}),("function"==typeof exports1.default||"object"==typeof exports1.default&&null!==exports1.default)&&void 0===exports1.default.__esModule&&(Object.defineProperty(exports1.default,"__esModule",{value:!0}),Object.assign(exports1.default,exports1),module.exports=exports1.default)}};
+//# sourceMappingURL=dist_client_dev_noop-turbopack-hmr_js-turbo-nodestreams.runtime.dev.js.map
\ No newline at end of file
pages-api-tu..ntime.dev.js

Diff too large to display

pages-api-tu..time.prod.js

Diff too large to display

pages-api.runtime.dev.js

Diff too large to display

pages-api.ru..time.prod.js

Diff too large to display

pages-turbo...ntime.dev.js

Diff too large to display

pages-turbo...time.prod.js

Diff too large to display

pages.runtime.dev.js

Diff too large to display

pages.runtime.prod.js

Diff too large to display

server.runtime.prod.js

Diff too large to display

@feedthejim feedthejim force-pushed the feedthejim/node-stream-03-primitives branch from 4199831 to 932773c Compare February 12, 2026 00:59
@feedthejim feedthejim force-pushed the feedthejim/node-stream-04-pipeline branch from 0665da4 to 43acaf9 Compare February 12, 2026 00:59
@feedthejim feedthejim changed the title feat(node-streams): wire primitives into render pipeline (7/8) feat(node-streams): wire primitives into render pipeline Feb 12, 2026
@feedthejim feedthejim changed the title (7/8) feat(node-streams): wire primitives into render pipeline feat(node-streams): wire primitives into render pipeline (7/8) Feb 12, 2026
const bridge = new PassThrough()
streams.push(bridge)

const combined = chainNodeStreams(...streams)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of spreading maybe we can just pass the array here. Since we control both functions.

@timneutkens timneutkens changed the base branch from feedthejim/node-stream-03-primitives to graphite-base/89860 February 13, 2026 13:44
@timneutkens timneutkens force-pushed the feedthejim/node-stream-04-pipeline branch from 43acaf9 to 6fa3940 Compare February 16, 2026 14:59
@timneutkens timneutkens changed the base branch from graphite-base/89860 to feedthejim/node-stream-03-primitives February 16, 2026 14:59
@timneutkens timneutkens force-pushed the feedthejim/node-stream-03-primitives branch from 3579d00 to 4ccf817 Compare February 16, 2026 16:50
@timneutkens timneutkens force-pushed the feedthejim/node-stream-04-pipeline branch from 6fa3940 to 8628e0b Compare February 16, 2026 16:50
@timneutkens timneutkens force-pushed the feedthejim/node-stream-03-primitives branch from 4ccf817 to 6a196c8 Compare February 19, 2026 10:20
@timneutkens timneutkens force-pushed the feedthejim/node-stream-04-pipeline branch 2 times, most recently from 117dd1d to 954314f Compare February 19, 2026 11:07
@timneutkens timneutkens force-pushed the feedthejim/node-stream-03-primitives branch 2 times, most recently from 36ff037 to 35c5f12 Compare February 19, 2026 12:12
@timneutkens timneutkens force-pushed the feedthejim/node-stream-04-pipeline branch from 954314f to fa666f7 Compare February 19, 2026 12:12
@timneutkens timneutkens force-pushed the feedthejim/node-stream-03-primitives branch from 35c5f12 to f043de0 Compare February 19, 2026 12:41
@timneutkens timneutkens force-pushed the feedthejim/node-stream-04-pipeline branch 2 times, most recently from b4003ab to 33a702e Compare February 19, 2026 13:49
@timneutkens timneutkens force-pushed the feedthejim/node-stream-03-primitives branch from f043de0 to 05531f7 Compare February 19, 2026 13:49
@timneutkens timneutkens force-pushed the feedthejim/node-stream-04-pipeline branch from 33a702e to ad081d7 Compare February 23, 2026 10:02
@timneutkens timneutkens force-pushed the feedthejim/node-stream-03-primitives branch from 05531f7 to 79b103c Compare February 23, 2026 10:02
Integrate the node stream building blocks (from prior PR) into the
actual render paths, all gated behind experimental.useNodeStreams:

- app-render.tsx: add useNodeStreams branching throughout render paths,
  teeDebugChannelForSsrAndBrowser and debugChannelClientForBrowser helpers
- app-render-prerender-utils.ts: ReactServerResult accepts NodeReadable,
  createReactServerPrerenderResultFromPrerender dispatcher
- instant-validation.tsx: node stream branches for validation renders
- render-result.ts: accept Node Readable as response body with piping
- flight-render-result.ts: accept Node Readable in constructor
- app-page.ts: PPR resume path for node streams
- stream-ops.ts/debug-channel-server.ts: widen types to include Readable
@timneutkens timneutkens force-pushed the feedthejim/node-stream-04-pipeline branch from ad081d7 to 6257529 Compare February 24, 2026 12:46
@timneutkens timneutkens force-pushed the feedthejim/node-stream-03-primitives branch from 79b103c to 558f424 Compare February 24, 2026 12:46
benfavre added a commit to benfavre/next.js that referenced this pull request Mar 18, 2026
Add `node-stream-helpers.ts` with Node.js native stream utilities that
parallel the WhatWG stream helpers in `node-web-streams-helper.ts`.
These are the foundational building blocks needed for the node-streams
rendering effort (PRs vercel#89566, vercel#89859, vercel#89860, vercel#90500).

Key functions:
- `chainNodeStreams()` - chains multiple Readable streams sequentially
- `createBufferedTransformNode()` - batches small chunks before flushing
- `createInlinedDataNodeStream()` - inlines flight data into HTML stream
- `pipeNodeReadableToResponse()` - pipes Readable directly to ServerResponse
- `nodeStreamToBuffer()` / `nodeStreamToString()` - collection utilities

ALS context propagation uses `bindSnapshot()` from the existing
`async-local-storage.ts` module, which wraps `AsyncLocalStorage.bind()`.
This addresses the review feedback from @lubieowoce on PR vercel#89859 where
ALS context was incorrectly propagated by wrapping callback return values
instead of binding the callbacks themselves.

This PR adds only the helper utilities as new files. No existing files
are modified. Wiring into the render pipeline is a separate step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants