Skip to content

[Next.js/dylib] Preserve returned NextResponse headers, cookies, and stream across module boundaries #8038

Description

@proggeramlug

What happened?

The current Perry/Perch Next.js adapter can execute NextResponse.json(...) inside an imported route, but it cannot consume the returned Response reliably. It therefore discards the real return value and fabricates deterministic response bytes in the caller. That makes the RSS/CPU result a lower bound rather than an apples-to-apples Next.js result.

Closed work such as #237 and #1650 established the Web Streams / Response.body surface. This issue is the remaining composed case: a NextResponse returned through an imported async App Route boundary, then inspected and drained by the app-library entry point.

Reproduction app (provided)

Use the exact pinned app and verifier attached to #8034. Do not replace the streamed response with NextResponse.json() or a hard-coded Buffer.

The supplied route returns:

  • status 207;
  • content-type: application/json; charset=utf-8;
  • x-perry-repro: <request id>;
  • Set-Cookie: perry_ctx=<request id>; ... via NextResponse.cookies;
  • a ReadableStream<Uint8Array> that emits the JSON in two chunks, with the second chunk queued in a microtask.

Focused oracle:

BASE_URL=http://127.0.0.1:3100 npm run verify

Required implementation behavior

For both Response and the NextResponse subclass:

  • preserve the native response identity/brand across a cross-module return and an async Promise<Response> return;
  • preserve status, status text, headers, and cookie mutations;
  • preserve body: ReadableStream | null, including the queued second chunk and close signal;
  • keep the response, Headers backing, stream handle, controller, queued chunks, and reader rooted for the full drain;
  • make repeated property reads and body reads resolve the same backing record;
  • surface a thrown/rejected stream error instead of hanging or returning an empty body.

The host ABI can serialize the final response after Perry has exposed these fields; that serialization is not a reason to fabricate a different response inside the test.

Regression coverage

Add a two-module minimal fixture in which module A returns a streamed Response both synchronously and asynchronously and module B validates status/headers/cookies and drains body.getReader(). Then keep #8034 as the production Next subclass/composition test.

Run both tests in executable mode and app-only dylib mode against the shared runtime/stdlib providers; this catches native-handle registries accidentally duplicated or hidden across images.

Acceptance criteria

  • [Next.js/dylib] Add a pinned production App Route parity fixture and CI gate #8034's verifier receives exact status, response header, cookie, and JSON for all 21 requests.
  • Both stream chunks are consumed and EOF is observed; an empty, truncated, buffered substitute does not pass the minimal stream test.
  • Sync and async cross-module returns pass with normal and forced/verified GC.
  • App-only dylib mode has the same result as a classic Perry executable.
  • The fixture contains no hard-coded status/body/headers and no ignored GET(...) return value.

Baseline / references

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regressionparityCompatibility gap with Node.js, ECMAScript, or the supported ecosystem

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions