Skip to content

Refactor AWS Lambda streaming support and enhance converters - #37

Open
conico974 wants to merge 5 commits into
mainfrom
conico/mandatory-stream
Open

Refactor AWS Lambda streaming support and enhance converters#37
conico974 wants to merge 5 commits into
mainfrom
conico/mandatory-stream

Conversation

@conico974

Copy link
Copy Markdown
Contributor

Refactor AWS Lambda streaming support by introducing a new converter structure and enhancing existing converters for better handling of direct and streaming output types. Improve memory management by removing the retainChunks option and updating response handling. Add tests for the new aws-streaming converter and ensure compatibility with existing functionality.

Copilot AI review requested due to automatic review settings July 15, 2026 15:27
@pkg-pr-new

pkg-pr-new Bot commented Jul 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/opennextjs/adapters-api/@opennextjs/aws@cd0116f
npm i https://pkg.pr.new/opennextjs/adapters-api/@opennextjs/cloudflare@cd0116f
npm i https://pkg.pr.new/opennextjs/adapters-api/@opennextjs/core@cd0116f

commit: cd0116f

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the converter/wrapper contract to better support AWS Lambda streaming by introducing a ConverterOutput structure (direct vs stream), removing chunk-retention behavior, and updating wrappers/converters to either stream responses or buffer streams into platform-specific return values. It also adds/updates unit tests to cover the new streaming converter and the updated converter behaviors.

Changes:

  • Introduces ConverterOutput and updates the Converter.convertTo(event, context) API to return either a stream creator (optionally with a deferred platform output) or a direct finalizer.
  • Refactors wrappers (AWS Lambda, AWS Lambda streaming/compressed, Node, Cloudflare) to initialize converter output before invoking the handler and to handle direct vs streamed results consistently.
  • Adds a buffered stream creator utility for AWS API Gateway/CloudFront converters and adds/updates unit tests for the new converter behaviors (including the new aws-streaming converter).

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/tests-unit/tests/core/routing/util.test.ts Updates routing util tests to reflect removal of buffered body exposure.
packages/tests-unit/tests/converters/sqs-revalidate.test.ts Adds unit coverage for direct-output behavior of the SQS revalidate converter.
packages/tests-unit/tests/converters/aws-streaming.test.ts Adds coverage for Lambda streaming prelude + streamed body behavior.
packages/tests-unit/tests/converters/aws-cloudfront.test.ts Refactors tests to validate buffered streaming output path for CloudFront converter.
packages/tests-unit/tests/converters/aws-apigw-v2.test.ts Refactors tests to validate buffered streaming output path for API Gateway v2 converter.
packages/tests-unit/tests/converters/aws-apigw-v1.test.ts Refactors tests to validate buffered streaming output path for API Gateway v1 converter.
packages/core/src/types/overrides.ts Adds ConverterOutput and updates Converter.convertTo signature/semantics.
packages/core/src/types/open-next.ts Makes InternalResult.body optional, removes retainChunks, adds aws-streaming converter option.
packages/core/src/plugins/resolve.ts Adds defaulting behavior to select aws-streaming converter for aws-lambda-streaming wrapper.
packages/core/src/overrides/wrappers/node.ts Updates node wrapper to use new converter output model.
packages/core/src/overrides/wrappers/express-dev.ts Updates dev wrapper to use new converter output model for image/all routes.
packages/core/src/overrides/wrappers/dummy.ts Updates dummy wrapper to use new converter output model.
packages/core/src/overrides/wrappers/cloudflare-node.ts Refactors Cloudflare-node wrapper to rely on converter stream output and return output.
packages/core/src/overrides/wrappers/cloudflare-edge.ts Refactors Cloudflare-edge wrapper to initialize converter output early and support direct exceptional results.
packages/core/src/overrides/converters/node.ts Moves Node converter to provide a StreamCreator via convertTo instead of returning an InternalResult mapping.
packages/core/src/overrides/converters/edge.ts Refactors Edge converter to return streaming output + direct exceptional handler for middleware results.
packages/core/src/overrides/converters/dummy.ts Updates dummy converter to return direct ConverterOutput.
packages/core/src/http/openNextResponse.ts Removes chunk retention and related helpers from OpenNextNodeResponse.
packages/core/src/core/routing/util.ts Updates convertRes to stop synthesizing a body from retained chunks.
packages/core/src/core/requestHandler.ts Adjusts request handler to handle optional bodies and updated convertRes output.
packages/core/src/build/validateConfig.ts Updates wrapper/converter compatibility matrix and defaults for Lambda streaming.
packages/core/src/build/generateOutput.ts Updates default converter selection when wrapper is aws-lambda-streaming.
packages/aws/src/overrides/wrappers/aws-lambda.ts Refactors AWS Lambda wrapper to use new converter output model (direct vs stream).
packages/aws/src/overrides/wrappers/aws-lambda-streaming.ts Refactors Lambda streaming wrapper to delegate streaming response construction to converter.
packages/aws/src/overrides/wrappers/aws-lambda-compressed.ts Refactors compressed wrapper to apply compression via StreamCreator composition.
packages/aws/src/overrides/converters/sqs-revalidate.ts Updates SQS revalidate converter to return direct output with data finalizer.
packages/aws/src/overrides/converters/response-stream.ts Introduces a buffered StreamCreator helper to build platform outputs from streamed writes.
packages/aws/src/overrides/converters/aws-streaming.ts Adds new aws-streaming converter for Lambda HTTP integration streaming prelude + body streaming.
packages/aws/src/overrides/converters/aws-cloudfront.ts Refactors CloudFront converter to buffered streaming output with a middleware direct handler.
packages/aws/src/overrides/converters/aws-apigw-v2.ts Refactors API Gateway v2 converter to buffered streaming output.
packages/aws/src/overrides/converters/aws-apigw-v1.ts Refactors API Gateway v1 converter to buffered streaming output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/aws/src/overrides/converters/response-stream.ts
Comment thread packages/core/src/overrides/wrappers/dummy.ts
- Updated aws-lambda-streaming wrapper to utilize new converter structure.
- Introduced aws-streaming converter for handling streaming responses in AWS Lambda.
- Enhanced existing converters to support direct and streaming output types.
- Modified core types to accommodate new converter output structure.
- Updated validation logic to ensure compatibility with new streaming converter.
- Added tests for aws-streaming converter and SQS revalidate functionality.
- Refactored express-dev and cloudflare wrappers to align with new converter outputs.
- Improved response handling in node and cloudflare-node wrappers.
@conico974
conico974 force-pushed the conico/mandatory-stream branch from 113b5a0 to 2e7d4c3 Compare August 2, 2026 10:50
vicb added 2 commits September 1, 2026 15:52
# Conflicts:
#	packages/aws/src/overrides/converters/aws-apigw-v1.ts
#	packages/aws/src/overrides/converters/aws-apigw-v2.ts
#	packages/aws/src/overrides/converters/aws-cloudfront.ts
#	packages/core/src/build/validateConfig.ts
#	packages/core/src/overrides/converters/edge.ts
#	packages/core/src/overrides/converters/node.ts
#	packages/core/src/plugins/resolve.ts
@vicb

vicb commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

The current main branch was merge into here and AI helped with the last commit.

Below is a report of the changes:

AWS Production Code

  • packages/aws/src/adapter.ts: changed only the server converter default from aws-apigw-v2 to aws-streaming so it matches the existing aws-lambda-streaming wrapper, and passes the adapter defaults into core validation so incompatible explicit overrides fail during configuration validation.
  • packages/aws/src/overrides/converters/aws-cloudfront.ts: contains an import-group formatting adjustment only; runtime behavior is unchanged.
  • packages/aws/src/overrides/converters/aws-streaming.ts: moved negotiated gzip, Brotli, and deflate compression into the converter; avoids recompressing encoded responses and compressing bodyless statuses; removes stale content lengths; maintains Vary: Accept-Encoding; emits identity encoding when appropriate; and exposes stream completion. This centralizes response transformation and gives the Lambda wrapper a reliable completion signal.
  • packages/aws/src/overrides/converters/response-stream.ts: honors explicit isBase64Encoded metadata and rejects the buffered output promise when the stream errors or is destroyed before finalization. This preserves binary intent and prevents callers from waiting forever on prematurely terminated streams.
  • packages/aws/src/overrides/wrappers/aws-lambda-compressed.ts: uses shared encoding negotiation, streams bodies returned by handlers that did not write directly, removes invalid content lengths, marks compressed buffered output as base64, skips compression for bodyless statuses, and uses pipeline for error propagation. This fixes compressed and edge-style returned responses in buffered Lambda mode.
  • packages/aws/src/overrides/wrappers/aws-lambda-streaming.ts: delegates compression to the converter, waits for warmer and response-stream completion, and forwards edge-runtime returned bodies through the shared response helper. This avoids duplicate compression and prevents Lambda invocations from completing before their streams settle.
  • packages/aws/src/overrides/wrappers/aws-lambda.ts: adds the shared streamResponse path for status, normalized headers, cookies, binary metadata, and returned web-stream bodies, while treating only undefined as the absence of a direct result. This lets buffered converters correctly handle handlers that return responses instead of writing through streamCreator.
  • packages/aws/src/overrides/wrappers/compression.ts: adds shared quality-aware Accept-Encoding negotiation and idempotent Vary: Accept-Encoding handling. This replaces duplicated and incomplete compression selection in the Lambda wrappers.

Core Build And Resolution

  • packages/core/src/build/adapter.ts: passes adapter bundle defaults into output generation. This ensures deployment metadata is based on the same defaults used to build the adapter.
  • packages/core/src/build/createServerBundle.ts: selects global defaults for globally placed bundles, with runtime-appropriate edge or server fallbacks. This ensures global functions use their intended CloudFront/global wrapper and converter pair.
  • packages/core/src/build/generateOutput.ts: applies adapter defaults to server, edge, global, middleware, image, queue, cache, and resolver metadata; normalizes full override paths; pairs partial overrides; detects streaming defaults; and awaits lazy global metadata. This makes emitted metadata describe the code that is actually bundled instead of relying on hard-coded AWS assumptions.
  • packages/core/src/build/validateConfig.ts: validates effective adapter and bundle defaults, pairs partial wrapper/converter overrides, handles middleware-specific defaults, permits the valid dummy/edge pairing, and gives compatibility errors precedence over warnings. This prevents invalid effective configurations from passing because only explicit user values were checked.
  • packages/core/src/overrides/compatibility.ts: centralizes the built-in wrapper-to-converter and converter-to-wrapper compatibility mappings. Output generation, validation, and plugin resolution use this common source so partial override behavior remains consistent.
  • packages/core/src/plugins/resolve.ts: adds global bundle resolution, normalizes override names, resolves compatible peers for partial overrides, handles package-qualified AWS defaults and ambiguous edge converters, and creates redirects from the effective pair. This ensures the bundler includes compatible modules from the correct package.

Core Streaming Lifecycle

  • packages/core/src/core/requestHandler.ts: propagates binary metadata into stream preludes, forwards routed response bodies without an unused tee() branch, honors write backpressure, and clears consumed bodies. This avoids retaining entire responses and safely handles large routed streams.
  • packages/core/src/http/openNextResponse.ts: infers isBase64Encoded from content type or content encoding. Buffered platform converters need this metadata to encode binary and compressed responses correctly.
  • packages/core/src/overrides/converters/edge.ts: replaces manual controller forwarding with a backpressure-aware TransformStream and exposes an abort hook through StreamCreator. This allows headers to become available early while still propagating later handler failures to response consumers.
  • packages/core/src/overrides/wrappers/cloudflare-edge.ts: passes request cancellation into the converter, races output availability against handler completion, continues post-header work through waitUntil, and aborts output on failure. This preserves low-latency streaming without hiding failures after headers have been returned.
  • packages/core/src/overrides/wrappers/cloudflare-node.ts: applies the same output/handler race and failure-abort behavior in the Node wrapper, uses the request signal as a cancellation fallback, and provides waitUntil to direct handlers. This aligns Cloudflare Node and Edge lifecycle semantics.
  • packages/core/src/overrides/wrappers/dummy.ts: applies exceptional direct mappings and otherwise returns converter output after the handler finishes. This supports buffered converters instead of incorrectly returning the raw handler result.
  • packages/core/src/overrides/wrappers/express-dev.ts: applies converter direct mappings and awaits converter output for image responses. Development behavior now follows the updated stream-converter completion contract.
  • packages/core/src/types/open-next.ts: adds optional binary metadata to stream preludes and an asynchronous abort hook to stream creators. These types support buffered binary output and post-header failure propagation.

Core Tests

  • packages/core/src/build/adapter.spec.ts: updates the output-generation call expectation for the new defaults argument. This protects the adapter-to-output contract.
  • packages/core/src/build/generateOutput.spec.ts: adds coverage for adapter metadata defaults, full-path streaming detection, awaited lazy global overrides, runtime-specific defaults, global fallbacks, and middleware defaults/resolvers. These tests ensure output metadata continues to match effective bundle configuration.
  • packages/core/src/build/validateConfig.spec.ts: covers streaming-wrapper pairing, incompatible explicit converters, adapter-default interactions, ambiguous edge converters, middleware defaults, and error precedence. These cases protect the expanded compatibility validation.
  • packages/core/src/plugins/resolve.spec.ts: updates the expected AWS server converter to aws-streaming and tests partial AWS override pairing and preservation of compatible adapter wrappers. This verifies effective override resolution and bundler redirects.

Converter And Wrapper Tests

  • packages/tests-unit/tests/converters/aws-streaming.test.ts: covers converter-owned completion, identity encoding, pre-encoded responses, negotiated compression, stale content-length removal, and bodyless statuses. This protects the redesigned streaming converter's header and lifecycle behavior.
  • packages/tests-unit/tests/converters/response-stream.test.ts: verifies rejection on stream errors and premature destruction, plus preservation of explicit binary metadata. This prevents buffered response promises from hanging or losing encoding intent.
  • packages/tests-unit/tests/wrappers/aws-lambda-compressed.test.ts: verifies base64 gzip output, content-length removal, Vary, and bodyless finalization. This protects buffered compressed Lambda responses.
  • packages/tests-unit/tests/wrappers/aws-lambda-streaming.test.ts: verifies that the wrapper waits for finalization and finalizes edge-runtime bodyless responses. This prevents premature streaming invocation completion.
  • packages/tests-unit/tests/wrappers/aws-lambda.test.ts: verifies returned edge-handler body streaming, cookie propagation, and bodyless finalization. This covers the new shared streamResponse fallback.
  • packages/tests-unit/tests/wrappers/cloudflare.test.ts: runs shared Edge and Node cases for early header return, failures before and after headers, body abortion, and direct-handler waitUntil. This protects Cloudflare's asynchronous response lifecycle.
  • packages/tests-unit/tests/wrappers/compression.test.ts: covers quality-weighted selection, disabled encodings, wildcard handling, and idempotent Vary. This validates the shared compression helpers independently of Lambda wrappers.
  • packages/tests-unit/tests/wrappers/dummy.test.ts: covers buffered output, exceptional direct mappings, and preservation of null as a valid direct result. This protects the corrected output-selection semantics.

Review Comments

  • response-stream.ts destruction comment: valid and previously unresolved. Fixed by rejecting the deferred platform output on error or premature clean destruction, with tests for both cases.
  • dummy.ts stream output comment: valid and previously unresolved. Fixed by applying exceptional direct mappings and returning buffered platform output, with tests for both paths.

@conico974 conico974 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think I didn't make the point of this PR clear enough, it's basically this https://github.com/orgs/opennextjs/projects/3?pane=issue&itemId=149553272

Basically every function that returns an InternalResult should stream, and the rest should depend on the converter and wrapper. I think we might be missing something here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This file should probably go somewhere else than in wrappers since it is also used by some converters

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's probably ok for this PR, but we need to redo this file entirely, generateOutput should either be in aws or written in a completely overridable way, it doesn't make sense to have aws constants in core

}
}
routingResult.body = bodyToReturn;
routingResult.body = undefined;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We are never returning routingResult body right ? If that's the case it should not be a property outside of here.

waitUntil: ctx.waitUntil.bind(ctx),
})
);
const handlerPromise = handler(internalEvent, {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's probably a typing issue here, and for all the other wrappers, but the idea is that if output.type is not direct it means we need to stream.

The whole point is that everything should stream except for some very specific one (like the queue handler, in which case you need to use the appropriate converter)

| "server"
| "middleware"
| "edge"
| "global"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What is this global thing here ? Placement global was only ever used to signify that it was supposed to use lambda@edge.
My gut would be to move this in aws not in core, aws can have it's own open-next.config.ts types or helper like cloudflare has

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.

3 participants