@geostrategists/react-router-aws 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,52 +1,115 @@
1
- # @geostrategists/react-router-aws
2
-
3
- ## AWS adapters for React Router v7 (successor to Remix)
4
-
5
1
  [![npm version](https://badge.fury.io/js/@geostrategists%2Freact-router-aws.svg)](https://badge.fury.io/js/@geostrategists%2Freact-router-aws)
6
2
  [![install size](https://packagephobia.com/badge?p=@geostrategists/react-router-aws)](https://packagephobia.com/result?p=@geostrategists/react-router-aws)
7
3
 
8
- Forked from [remix-aws](https://github.com/wingleung/remix-aws) to support React Router v7, which Remix was merged into.
4
+ # AWS Lambda adapters for React Router v7
5
+
6
+ This project provides adapters for running React Router Framework applications on AWS Lambda
7
+ behind a number of different HTTP gateways.
8
+
9
+ ## 🚀 Supported gateways
10
+
11
+ - Lambda function URL (streaming) _✨(recommended)_
12
+ - Lambda function URL (buffered)
13
+ - API Gateway v2
14
+ - API Gateway v1
15
+ - Application Load Balancer
9
16
 
10
- ## 🚀 support
17
+ ### Acknowledgements
11
18
 
12
- - API gateway v1
13
- - API gateway v2
14
- - Application load balancer
19
+ This project was forked from [remix-aws](https://github.com/wingleung/remix-aws) to support React Router v7, which Remix was merged into.
15
20
 
16
21
  ## Getting started
17
22
 
23
+ ### Installation
24
+
25
+ ```shell
26
+ npm add @geostrategists/react-router-aws
27
+ ```
28
+
18
29
  ```shell
19
- npm install --save @geostrategists/react-router-aws
30
+ yarn add @geostrategists/react-router-aws
20
31
  ```
21
32
 
33
+ ### Gateway-specific handlers
34
+
35
+ Next, choose the handler that matches your AWS integration:
36
+
37
+ - `createALBRequestHandler` for Application Load Balancer
38
+ - `createAPIGatewayV1RequestHandler` for API Gateway v1
39
+ - `createAPIGatewayV2RequestHandler` for API Gateway v2
40
+ - `createFunctionURLRequestHandler` for Lambda Function URLs (Buffered)
41
+ - `createFunctionURLStreamingRequestHandler` for Lambda Function URLs (Streaming)
42
+
43
+ Example for API Gateway v2:
44
+
22
45
  ```javascript
23
- // server.js
46
+ // lambda-handler.ts
24
47
  import * as build from "virtual:react-router/server-build";
25
- import { AWSProxy, createRequestHandler } from "@geostrategists/react-router-aws";
48
+ import { createAPIGatewayV2RequestHandler } from "@geostrategists/react-router-aws";
26
49
 
27
- export const handler = createRequestHandler({
50
+ export const handler = createAPIGatewayV2RequestHandler({
28
51
  build,
29
52
  mode: process.env.NODE_ENV,
30
- awsProxy: AWSProxy.APIGatewayV2,
31
53
  });
32
54
  ```
33
55
 
34
- ### `awsProxy`
56
+ > [!NOTE]
57
+ >
58
+ > **DEPRECATION NOTICE**: The previous `createRequestHandler` method still exists, but is kept only for
59
+ > backwards-compatibility reasons and will be removed in the next major release.
60
+ > It does not allow tree-shaking and will include all gateway adapters in your bundle.
61
+ > For optimal bundle size, always use the method specific to your gateway:
35
62
 
36
- By default, the `awsProxy` is set to `AWSProxy.APIGatewayV2`.
63
+ ### Streaming support for Lambda Function URLs
37
64
 
38
- #### Options
65
+ React Router and React allow you to stream responses from the server to the client, reducing the TTFB (time to first byte)
66
+ and improving the user experience. See [Streaming with Suspense](https://reactrouter.com/how-to/suspense) for details.
39
67
 
40
- - `AWSProxy.APIGatewayV1`
41
- - `AWSProxy.APIGatewayV2`
42
- - `AWSProxy.ALB`
43
- - `AWSProxy.FunctionURL`
68
+ For this to work, the response from the Lambda must also be streamed. This is currently only possible with
69
+ Lambda Function URLs, which is why we recommend this setup.
70
+
71
+ For streaming responses from React Router on AWS Lambda Function URLs, use `createFunctionURLStreamingRequestHandler`:
72
+
73
+ ```typescript
74
+ // lambda-handler.ts
75
+ import * as build from "virtual:react-router/server-build";
76
+ import { createFunctionURLStreamingRequestHandler } from "@geostrategists/react-router-aws";
77
+
78
+ export const handler = createFunctionURLStreamingRequestHandler({
79
+ build,
80
+ mode: process.env.NODE_ENV,
81
+ });
82
+ ```
83
+
84
+ The Function URL must be configured to use streaming responses.
85
+
86
+ For example, in CDK:
87
+
88
+ ```typescript
89
+ // frontend-stack.ts
90
+ declare const fn: lambda.Function;
91
+
92
+ fn.addFunctionUrl({
93
+ authType: lambda.FunctionUrlAuthType.NONE,
94
+ invokeMode: lambda.InvokeMode.RESPONSE_STREAM,
95
+ });
96
+ ```
97
+
98
+ > [!TIP]
99
+ > It is strongly recommended to include the `@geostrategists/react-router-aws` dependency in your Lambda handler bundle.
100
+ > Otherwise (if it is externalized and perhaps put in a Lambda layer), AWS Lambda may not detect the handler as a
101
+ > streaming handler.
102
+ >
103
+ > If you encounter responses that show a `{ statusCode, headers, body }` JSON object instead of just the body,
104
+ > this might be the reason.
105
+
106
+ More on response streaming: https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html
44
107
 
45
108
  ## Deployment recommendation
46
109
 
47
110
  Since Vite already bundles the project into a single entry point, there is no need to further
48
111
  bundle the lambda code.
49
- For example, when using AWS CDK, we recommend using lambda.Function directly instead of lambda.NodeJsFunction.
112
+ For example, when using AWS CDK, we recommend using [lambda.Function](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.Function.html) directly instead of lambda.NodeJsFunction.
50
113
 
51
114
  Dependencies can be provided using a layer, for example.
52
115
 
@@ -61,16 +124,4 @@ There are two primary methods to achieve this:
61
124
  - Use the .mjs extension:
62
125
  Alternatively, you can change the file extension to `.mjs`. For example, you can configure the React Router `serverBuildFile` setting to output `index.mjs`.
63
126
 
64
- more info: [AWS docs on ES module support in AWS lambdas](https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html#designate-es-module)
65
-
66
- ## Notes
67
-
68
- ### split from @remix/architect
69
-
70
- As mentioned in [#3173](https://github.com/remix-run/remix/pull/3173) the goal would be to provide an AWS adapter for
71
- the community by the community.
72
- In doing so the focus will be on AWS integrations and less on Architect. I do think it's added value to provide examples
73
- for Architect, AWS SAM, AWS CDK, Serverless,...
74
-
75
- **info:** [ALB types](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/trigger/alb.d.ts#L29-L48)
76
- vs [API gateway v1 types](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/trigger/api-gateway-proxy.d.ts#L116-L145)
127
+ See [AWS docs on ES module support in AWS lambdas](https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html#designate-es-module) for more information.
package/dist/index.d.mts CHANGED
@@ -1,12 +1,7 @@
1
- import { APIGatewayProxyEventV2, APIGatewayProxyEvent, ALBEvent, APIGatewayProxyHandlerV2, APIGatewayProxyHandler, ALBHandler } from 'aws-lambda';
2
1
  import { UNSAFE_MiddlewareEnabled, unstable_InitialContext, AppLoadContext, ServerBuild } from 'react-router';
2
+ import { ALBEvent, ALBHandler, APIGatewayProxyEvent, APIGatewayProxyHandler, APIGatewayProxyEventV2, APIGatewayProxyHandlerV2, LambdaFunctionURLEvent, LambdaFunctionURLHandler, Handler, APIGatewayProxyResult, APIGatewayProxyStructuredResultV2, ALBResult } from 'aws-lambda';
3
+ import { StreamifyHandler } from 'aws-lambda/handler';
3
4
 
4
- declare enum AWSProxy {
5
- APIGatewayV1 = "APIGatewayV1",
6
- APIGatewayV2 = "APIGatewayV2",
7
- ALB = "ALB",
8
- FunctionURL = "FunctionURL"
9
- }
10
5
  type MaybePromise<T> = T | Promise<T>;
11
6
  /**
12
7
  * A function that returns the value to use as `context` in route `loader` and
@@ -15,17 +10,93 @@ type MaybePromise<T> = T | Promise<T>;
15
10
  * You can think of this as an escape hatch that allows you to pass
16
11
  * environment/platform-specific values through to your loader/action.
17
12
  */
18
- type GetLoadContextFunction = (event: APIGatewayProxyEventV2 | APIGatewayProxyEvent | ALBEvent) => UNSAFE_MiddlewareEnabled extends true ? MaybePromise<unstable_InitialContext> : MaybePromise<AppLoadContext>;
19
- type RequestHandler = APIGatewayProxyHandlerV2 | APIGatewayProxyHandler | ALBHandler;
20
- /**
21
- * Returns a request handler for AWS that serves the response using
22
- * React Router.
23
- */
24
- declare function createRequestHandler({ build, getLoadContext, mode, awsProxy, }: {
13
+ type GetLoadContextFunction<E> = (event: E) => UNSAFE_MiddlewareEnabled extends true ? MaybePromise<unstable_InitialContext> : MaybePromise<AppLoadContext>;
14
+ type CreateRequestHandlerArgs<T> = {
25
15
  build: ServerBuild;
26
- getLoadContext?: GetLoadContextFunction;
16
+ getLoadContext?: GetLoadContextFunction<T>;
27
17
  mode?: string;
28
- awsProxy?: AWSProxy;
29
- }): RequestHandler;
18
+ };
19
+ /**
20
+ * Returns a request handler for AWS API Gateway V1
21
+ *
22
+ */
23
+ /**
24
+ * Returns a request handler for AWS API Gateway V1 events.
25
+ *
26
+ * @param options - The handler options, including the React Router server build,
27
+ * optional getLoadContext function, and mode string.
28
+ * @returns An AWS API Gateway V1 handler compatible with APIGatewayProxyHandler.
29
+ */
30
+ declare function createAPIGatewayV1RequestHandler(options: CreateRequestHandlerArgs<APIGatewayProxyEvent>): APIGatewayProxyHandler;
31
+ /**
32
+ * Returns a request handler for AWS API Gateway V2 events.
33
+ *
34
+ * @param options - The handler options, including the React Router server build,
35
+ * optional getLoadContext function, and mode string.
36
+ * @returns An AWS API Gateway V2 handler compatible with APIGatewayProxyHandlerV2.
37
+ */
38
+ declare function createAPIGatewayV2RequestHandler(options: CreateRequestHandlerArgs<APIGatewayProxyEventV2>): APIGatewayProxyHandlerV2;
39
+ /**
40
+ * Returns a request handler for AWS Application Load Balancer events.
41
+ *
42
+ * @param options - The handler options, including the React Router server build,
43
+ * optional getLoadContext function, and mode string.
44
+ * @returns An AWS ALB handler compatible with ALBHandler.
45
+ */
46
+ declare function createALBRequestHandler(options: CreateRequestHandlerArgs<ALBEvent>): ALBHandler;
47
+ /**
48
+ * Returns a request handler for AWS Lambda Function URL events (invoke mode BUFFERED).
49
+ *
50
+ * @param options - The handler options, including the React Router server build,
51
+ * optional getLoadContext function, and mode string.
52
+ * @returns An AWS Lambda Function URL handler compatible with Lambda Function URLs with InvokeMode BUFFERED.
53
+ */
54
+ declare function createFunctionURLRequestHandler(options: CreateRequestHandlerArgs<LambdaFunctionURLEvent>): LambdaFunctionURLHandler;
55
+ /**
56
+ * Returns a request handler for AWS Lambda Function URL events (invoke mode RESPONSE_STREAM).
57
+ *
58
+ * @param options - The handler options, including the React Router server build,
59
+ * optional getLoadContext function, and mode string.
60
+ * @returns A streaming AWS Lambda Function URL handler compatible with Lambda Function URLs with InvokeMode RESPONSE_STREAM.
61
+ */
62
+ declare function createFunctionURLStreamingRequestHandler(options: CreateRequestHandlerArgs<LambdaFunctionURLEvent>): StreamifyHandler<LambdaFunctionURLEvent, void>;
63
+
64
+ interface ReactRouterAdapter<E, Ret, Res = void, H = Handler<E, Ret>> {
65
+ wrapHandler: (handler: (event: E, res: Res) => Promise<Ret>) => H;
66
+ createReactRouterRequest: (event: E) => Request;
67
+ sendReactRouterResponse: (nodeResponse: Response, response: Res) => Promise<Ret>;
68
+ }
69
+
70
+ type ApiGatewayV1Adapter = ReactRouterAdapter<APIGatewayProxyEvent, APIGatewayProxyResult>;
71
+
72
+ type ApiGatewayV2Adapter = ReactRouterAdapter<APIGatewayProxyEventV2, APIGatewayProxyStructuredResultV2>;
73
+
74
+ type ApplicationLoadBalancerAdapter = ReactRouterAdapter<ALBEvent, ALBResult>;
75
+
76
+ type FunctionUrlStreamingAdapter = ReactRouterAdapter<LambdaFunctionURLEvent, void, awslambda.HttpResponseStream, StreamifyHandler<LambdaFunctionURLEvent, void>>;
77
+
78
+ declare enum AWSProxy {
79
+ APIGatewayV1 = "APIGatewayV1",
80
+ APIGatewayV2 = "APIGatewayV2",
81
+ ALB = "ALB",
82
+ FunctionURL = "FunctionURL",
83
+ FunctionURLStreaming = "FunctionURLStreaming"
84
+ }
85
+ type InferAdapter<T extends AWSProxy> = T extends AWSProxy.APIGatewayV1 ? ApiGatewayV1Adapter : T extends AWSProxy.APIGatewayV2 | AWSProxy.FunctionURL ? ApiGatewayV2Adapter : T extends AWSProxy.ALB ? ApplicationLoadBalancerAdapter : T extends AWSProxy.FunctionURLStreaming ? FunctionUrlStreamingAdapter : never;
86
+ type InferEventType<T extends AWSProxy> = InferAdapter<T> extends ReactRouterAdapter<infer E, any, any, any> ? E : never;
87
+ type InferHandlerType<T extends AWSProxy> = InferAdapter<T> extends ReactRouterAdapter<any, any, any, infer H> ? H : never;
88
+ /**
89
+ * Returns a request handler for AWS that serves the response using React Router.
90
+ *
91
+ * @deprecated Use one of the gateway-specific create*RequestHandler methods instead for better tree-shaking.
92
+ * - `createAPIGatewayV1RequestHandler`
93
+ * - `createAPIGatewayV2RequestHandler`
94
+ * - `createALBRequestHandler`
95
+ * - `createFunctionURLRequestHandler`
96
+ * - `createFunctionURLStreamingRequestHandler`
97
+ */
98
+ declare function createRequestHandler<T extends AWSProxy>(options: CreateRequestHandlerArgs<InferEventType<T>> & {
99
+ awsProxy?: T;
100
+ }): InferHandlerType<T>;
30
101
 
31
- export { AWSProxy, type GetLoadContextFunction, type RequestHandler, createRequestHandler };
102
+ export { AWSProxy, type GetLoadContextFunction, createALBRequestHandler, createAPIGatewayV1RequestHandler, createAPIGatewayV2RequestHandler, createFunctionURLRequestHandler, createFunctionURLStreamingRequestHandler, createRequestHandler };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,7 @@
1
- import { APIGatewayProxyEventV2, APIGatewayProxyEvent, ALBEvent, APIGatewayProxyHandlerV2, APIGatewayProxyHandler, ALBHandler } from 'aws-lambda';
2
1
  import { UNSAFE_MiddlewareEnabled, unstable_InitialContext, AppLoadContext, ServerBuild } from 'react-router';
2
+ import { ALBEvent, ALBHandler, APIGatewayProxyEvent, APIGatewayProxyHandler, APIGatewayProxyEventV2, APIGatewayProxyHandlerV2, LambdaFunctionURLEvent, LambdaFunctionURLHandler, Handler, APIGatewayProxyResult, APIGatewayProxyStructuredResultV2, ALBResult } from 'aws-lambda';
3
+ import { StreamifyHandler } from 'aws-lambda/handler';
3
4
 
4
- declare enum AWSProxy {
5
- APIGatewayV1 = "APIGatewayV1",
6
- APIGatewayV2 = "APIGatewayV2",
7
- ALB = "ALB",
8
- FunctionURL = "FunctionURL"
9
- }
10
5
  type MaybePromise<T> = T | Promise<T>;
11
6
  /**
12
7
  * A function that returns the value to use as `context` in route `loader` and
@@ -15,17 +10,93 @@ type MaybePromise<T> = T | Promise<T>;
15
10
  * You can think of this as an escape hatch that allows you to pass
16
11
  * environment/platform-specific values through to your loader/action.
17
12
  */
18
- type GetLoadContextFunction = (event: APIGatewayProxyEventV2 | APIGatewayProxyEvent | ALBEvent) => UNSAFE_MiddlewareEnabled extends true ? MaybePromise<unstable_InitialContext> : MaybePromise<AppLoadContext>;
19
- type RequestHandler = APIGatewayProxyHandlerV2 | APIGatewayProxyHandler | ALBHandler;
20
- /**
21
- * Returns a request handler for AWS that serves the response using
22
- * React Router.
23
- */
24
- declare function createRequestHandler({ build, getLoadContext, mode, awsProxy, }: {
13
+ type GetLoadContextFunction<E> = (event: E) => UNSAFE_MiddlewareEnabled extends true ? MaybePromise<unstable_InitialContext> : MaybePromise<AppLoadContext>;
14
+ type CreateRequestHandlerArgs<T> = {
25
15
  build: ServerBuild;
26
- getLoadContext?: GetLoadContextFunction;
16
+ getLoadContext?: GetLoadContextFunction<T>;
27
17
  mode?: string;
28
- awsProxy?: AWSProxy;
29
- }): RequestHandler;
18
+ };
19
+ /**
20
+ * Returns a request handler for AWS API Gateway V1
21
+ *
22
+ */
23
+ /**
24
+ * Returns a request handler for AWS API Gateway V1 events.
25
+ *
26
+ * @param options - The handler options, including the React Router server build,
27
+ * optional getLoadContext function, and mode string.
28
+ * @returns An AWS API Gateway V1 handler compatible with APIGatewayProxyHandler.
29
+ */
30
+ declare function createAPIGatewayV1RequestHandler(options: CreateRequestHandlerArgs<APIGatewayProxyEvent>): APIGatewayProxyHandler;
31
+ /**
32
+ * Returns a request handler for AWS API Gateway V2 events.
33
+ *
34
+ * @param options - The handler options, including the React Router server build,
35
+ * optional getLoadContext function, and mode string.
36
+ * @returns An AWS API Gateway V2 handler compatible with APIGatewayProxyHandlerV2.
37
+ */
38
+ declare function createAPIGatewayV2RequestHandler(options: CreateRequestHandlerArgs<APIGatewayProxyEventV2>): APIGatewayProxyHandlerV2;
39
+ /**
40
+ * Returns a request handler for AWS Application Load Balancer events.
41
+ *
42
+ * @param options - The handler options, including the React Router server build,
43
+ * optional getLoadContext function, and mode string.
44
+ * @returns An AWS ALB handler compatible with ALBHandler.
45
+ */
46
+ declare function createALBRequestHandler(options: CreateRequestHandlerArgs<ALBEvent>): ALBHandler;
47
+ /**
48
+ * Returns a request handler for AWS Lambda Function URL events (invoke mode BUFFERED).
49
+ *
50
+ * @param options - The handler options, including the React Router server build,
51
+ * optional getLoadContext function, and mode string.
52
+ * @returns An AWS Lambda Function URL handler compatible with Lambda Function URLs with InvokeMode BUFFERED.
53
+ */
54
+ declare function createFunctionURLRequestHandler(options: CreateRequestHandlerArgs<LambdaFunctionURLEvent>): LambdaFunctionURLHandler;
55
+ /**
56
+ * Returns a request handler for AWS Lambda Function URL events (invoke mode RESPONSE_STREAM).
57
+ *
58
+ * @param options - The handler options, including the React Router server build,
59
+ * optional getLoadContext function, and mode string.
60
+ * @returns A streaming AWS Lambda Function URL handler compatible with Lambda Function URLs with InvokeMode RESPONSE_STREAM.
61
+ */
62
+ declare function createFunctionURLStreamingRequestHandler(options: CreateRequestHandlerArgs<LambdaFunctionURLEvent>): StreamifyHandler<LambdaFunctionURLEvent, void>;
63
+
64
+ interface ReactRouterAdapter<E, Ret, Res = void, H = Handler<E, Ret>> {
65
+ wrapHandler: (handler: (event: E, res: Res) => Promise<Ret>) => H;
66
+ createReactRouterRequest: (event: E) => Request;
67
+ sendReactRouterResponse: (nodeResponse: Response, response: Res) => Promise<Ret>;
68
+ }
69
+
70
+ type ApiGatewayV1Adapter = ReactRouterAdapter<APIGatewayProxyEvent, APIGatewayProxyResult>;
71
+
72
+ type ApiGatewayV2Adapter = ReactRouterAdapter<APIGatewayProxyEventV2, APIGatewayProxyStructuredResultV2>;
73
+
74
+ type ApplicationLoadBalancerAdapter = ReactRouterAdapter<ALBEvent, ALBResult>;
75
+
76
+ type FunctionUrlStreamingAdapter = ReactRouterAdapter<LambdaFunctionURLEvent, void, awslambda.HttpResponseStream, StreamifyHandler<LambdaFunctionURLEvent, void>>;
77
+
78
+ declare enum AWSProxy {
79
+ APIGatewayV1 = "APIGatewayV1",
80
+ APIGatewayV2 = "APIGatewayV2",
81
+ ALB = "ALB",
82
+ FunctionURL = "FunctionURL",
83
+ FunctionURLStreaming = "FunctionURLStreaming"
84
+ }
85
+ type InferAdapter<T extends AWSProxy> = T extends AWSProxy.APIGatewayV1 ? ApiGatewayV1Adapter : T extends AWSProxy.APIGatewayV2 | AWSProxy.FunctionURL ? ApiGatewayV2Adapter : T extends AWSProxy.ALB ? ApplicationLoadBalancerAdapter : T extends AWSProxy.FunctionURLStreaming ? FunctionUrlStreamingAdapter : never;
86
+ type InferEventType<T extends AWSProxy> = InferAdapter<T> extends ReactRouterAdapter<infer E, any, any, any> ? E : never;
87
+ type InferHandlerType<T extends AWSProxy> = InferAdapter<T> extends ReactRouterAdapter<any, any, any, infer H> ? H : never;
88
+ /**
89
+ * Returns a request handler for AWS that serves the response using React Router.
90
+ *
91
+ * @deprecated Use one of the gateway-specific create*RequestHandler methods instead for better tree-shaking.
92
+ * - `createAPIGatewayV1RequestHandler`
93
+ * - `createAPIGatewayV2RequestHandler`
94
+ * - `createALBRequestHandler`
95
+ * - `createFunctionURLRequestHandler`
96
+ * - `createFunctionURLStreamingRequestHandler`
97
+ */
98
+ declare function createRequestHandler<T extends AWSProxy>(options: CreateRequestHandlerArgs<InferEventType<T>> & {
99
+ awsProxy?: T;
100
+ }): InferHandlerType<T>;
30
101
 
31
- export { AWSProxy, type GetLoadContextFunction, type RequestHandler, createRequestHandler };
102
+ export { AWSProxy, type GetLoadContextFunction, createALBRequestHandler, createAPIGatewayV1RequestHandler, createAPIGatewayV2RequestHandler, createFunctionURLRequestHandler, createFunctionURLStreamingRequestHandler, createRequestHandler };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @geostrategists/react-router-aws v2.0.0
2
+ * @geostrategists/react-router-aws v2.1.1
3
3
  *
4
4
  * Copyright (c) Geostrategists Consulting GmbH
5
5
  *
@@ -31,6 +31,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AWSProxy: () => AWSProxy,
34
+ createALBRequestHandler: () => createALBRequestHandler,
35
+ createAPIGatewayV1RequestHandler: () => createAPIGatewayV1RequestHandler,
36
+ createAPIGatewayV2RequestHandler: () => createAPIGatewayV2RequestHandler,
37
+ createFunctionURLRequestHandler: () => createFunctionURLRequestHandler,
38
+ createFunctionURLStreamingRequestHandler: () => createFunctionURLStreamingRequestHandler,
34
39
  createRequestHandler: () => createRequestHandler
35
40
  });
36
41
  module.exports = __toCommonJS(index_exports);
@@ -38,9 +43,8 @@ module.exports = __toCommonJS(index_exports);
38
43
  // src/server.ts
39
44
  var import_react_router = require("react-router");
40
45
 
41
- // src/adapters/api-gateway-v1.ts
46
+ // src/adapters/api-gateway-v2.ts
42
47
  var import_node = require("@react-router/node");
43
- var import_url = require("url");
44
48
 
45
49
  // src/binaryTypes.ts
46
50
  var binaryTypes = [
@@ -108,87 +112,91 @@ function isBinaryType(contentType) {
108
112
  return binaryTypes.includes(test);
109
113
  }
110
114
 
111
- // src/adapters/api-gateway-v1.ts
112
- function createReactRouterRequestAPIGatewayV1(event) {
113
- const host = event.headers["x-forwarded-host"] || event.headers.Host;
115
+ // src/adapters/api-gateway-v2.ts
116
+ function createReactRouterRequestAPIGateywayV2(event) {
117
+ const host = event.headers["x-forwarded-host"] || event.headers.host;
118
+ const search = event.rawQueryString.length ? `?${event.rawQueryString}` : "";
114
119
  const scheme = event.headers["x-forwarded-proto"] || "http";
115
- const rawQueryString = new import_url.URLSearchParams(event.queryStringParameters).toString();
116
- const search = rawQueryString.length > 0 ? `?${rawQueryString}` : "";
117
- const url = new URL(event.path + search, `${scheme}://${host}`);
120
+ const url = new URL(event.rawPath + search, `${scheme}://${host}`);
118
121
  const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
119
122
  const controller = new AbortController();
120
123
  return new Request(url.href, {
121
- method: event.requestContext.httpMethod,
122
- headers: createReactRouterHeadersAPIGatewayV1(event.headers),
124
+ method: event.requestContext.http.method,
125
+ headers: createReactRouterHeadersAPIGatewayV2(event.headers, event.cookies),
123
126
  signal: controller.signal,
124
- body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body || void 0
127
+ body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
125
128
  });
126
129
  }
127
- function createReactRouterHeadersAPIGatewayV1(requestHeaders) {
130
+ function createReactRouterHeadersAPIGatewayV2(requestHeaders, requestCookies) {
128
131
  const headers = new Headers();
129
132
  for (const [header, value] of Object.entries(requestHeaders)) {
130
133
  if (value) {
131
134
  headers.append(header, value);
132
135
  }
133
136
  }
137
+ if (requestCookies) {
138
+ headers.append("Cookie", requestCookies.join("; "));
139
+ }
134
140
  return headers;
135
141
  }
136
- async function sendReactRouterResponseAPIGatewayV1(nodeResponse) {
137
- const contentType = nodeResponse.headers.get("Content-Type");
138
- const isBase64Encoded = isBinaryType(contentType);
139
- let body;
140
- if (nodeResponse.body) {
141
- if (isBase64Encoded) {
142
- body = await (0, import_node.readableStreamToString)(nodeResponse.body, "base64");
143
- } else {
144
- body = await nodeResponse.text();
145
- }
142
+ function extractAPIGatewayV2ResponseMetadata(nodeResponse) {
143
+ const cookies = nodeResponse.headers.getSetCookie();
144
+ if (cookies.length) {
145
+ nodeResponse.headers.delete("Set-Cookie");
146
146
  }
147
147
  return {
148
148
  statusCode: nodeResponse.status,
149
149
  headers: Object.fromEntries(nodeResponse.headers.entries()),
150
- body: body || "",
151
- isBase64Encoded
150
+ cookies
152
151
  };
153
152
  }
154
- var apiGatewayV1Adapter = {
155
- createReactRouterRequest: createReactRouterRequestAPIGatewayV1,
156
- sendReactRouterResponse: sendReactRouterResponseAPIGatewayV1
153
+ async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
154
+ const result = extractAPIGatewayV2ResponseMetadata(nodeResponse);
155
+ const contentType = nodeResponse.headers.get("Content-Type");
156
+ result.isBase64Encoded = isBinaryType(contentType);
157
+ if (nodeResponse.body) {
158
+ if (result.isBase64Encoded) {
159
+ result.body = await (0, import_node.readableStreamToString)(nodeResponse.body, "base64");
160
+ } else {
161
+ result.body = await nodeResponse.text();
162
+ }
163
+ }
164
+ return result;
165
+ }
166
+ var apiGatewayV2Adapter = {
167
+ wrapHandler: (handler) => (e) => handler(e),
168
+ createReactRouterRequest: createReactRouterRequestAPIGateywayV2,
169
+ sendReactRouterResponse: sendReactRouterResponseAPIGatewayV2
157
170
  };
158
171
 
159
- // src/adapters/api-gateway-v2.ts
172
+ // src/adapters/api-gateway-v1.ts
160
173
  var import_node2 = require("@react-router/node");
161
- function createReactRouterRequestAPIGateywayV2(event) {
162
- const host = event.headers["x-forwarded-host"] || event.headers.host;
163
- const search = event.rawQueryString.length ? `?${event.rawQueryString}` : "";
174
+ var import_url = require("url");
175
+ function createReactRouterRequestAPIGatewayV1(event) {
176
+ const host = event.headers["x-forwarded-host"] || event.headers.Host;
164
177
  const scheme = event.headers["x-forwarded-proto"] || "http";
165
- const url = new URL(event.rawPath + search, `${scheme}://${host}`);
178
+ const rawQueryString = new import_url.URLSearchParams(event.queryStringParameters).toString();
179
+ const search = rawQueryString.length > 0 ? `?${rawQueryString}` : "";
180
+ const url = new URL(event.path + search, `${scheme}://${host}`);
166
181
  const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
167
182
  const controller = new AbortController();
168
183
  return new Request(url.href, {
169
- method: event.requestContext.http.method,
170
- headers: createReactRouterHeadersAPIGatewayV2(event.headers, event.cookies),
184
+ method: event.requestContext.httpMethod,
185
+ headers: createReactRouterHeadersAPIGatewayV1(event.headers),
171
186
  signal: controller.signal,
172
- body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
187
+ body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body || void 0
173
188
  });
174
189
  }
175
- function createReactRouterHeadersAPIGatewayV2(requestHeaders, requestCookies) {
190
+ function createReactRouterHeadersAPIGatewayV1(requestHeaders) {
176
191
  const headers = new Headers();
177
192
  for (const [header, value] of Object.entries(requestHeaders)) {
178
193
  if (value) {
179
194
  headers.append(header, value);
180
195
  }
181
196
  }
182
- if (requestCookies) {
183
- headers.append("Cookie", requestCookies.join("; "));
184
- }
185
197
  return headers;
186
198
  }
187
- async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
188
- const cookies = nodeResponse.headers.getSetCookie();
189
- if (cookies.length) {
190
- nodeResponse.headers.delete("Set-Cookie");
191
- }
199
+ async function sendReactRouterResponseAPIGatewayV1(nodeResponse) {
192
200
  const contentType = nodeResponse.headers.get("Content-Type");
193
201
  const isBase64Encoded = isBinaryType(contentType);
194
202
  let body;
@@ -202,14 +210,14 @@ async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
202
210
  return {
203
211
  statusCode: nodeResponse.status,
204
212
  headers: Object.fromEntries(nodeResponse.headers.entries()),
205
- cookies,
206
- body,
213
+ body: body || "",
207
214
  isBase64Encoded
208
215
  };
209
216
  }
210
- var apiGatewayV2Adapter = {
211
- createReactRouterRequest: createReactRouterRequestAPIGateywayV2,
212
- sendReactRouterResponse: sendReactRouterResponseAPIGatewayV2
217
+ var apiGatewayV1Adapter = {
218
+ wrapHandler: (handler) => (e) => handler(e),
219
+ createReactRouterRequest: createReactRouterRequestAPIGatewayV1,
220
+ sendReactRouterResponse: sendReactRouterResponseAPIGatewayV1
213
221
  };
214
222
 
215
223
  // src/adapters/application-load-balancer.ts
@@ -259,55 +267,112 @@ async function sendReactRouterResponseALB(nodeResponse) {
259
267
  };
260
268
  }
261
269
  var applicationLoadBalancerAdapter = {
270
+ wrapHandler: (handler) => (e) => handler(e),
262
271
  createReactRouterRequest: createReactRouterRequestALB,
263
272
  sendReactRouterResponse: sendReactRouterResponseALB
264
273
  };
265
274
 
266
- // src/adapters/index.ts
267
- var createReactRouterAdapter = (awsProxy) => {
268
- switch (awsProxy) {
269
- case "APIGatewayV1" /* APIGatewayV1 */:
270
- return apiGatewayV1Adapter;
271
- case "APIGatewayV2" /* APIGatewayV2 */:
272
- case "FunctionURL" /* FunctionURL */:
273
- return apiGatewayV2Adapter;
274
- case "ALB" /* ALB */:
275
- return applicationLoadBalancerAdapter;
275
+ // src/adapters/function-url-streaming.ts
276
+ var import_node4 = require("@react-router/node");
277
+ var emptyStream = () => new ReadableStream({
278
+ start(controller) {
279
+ controller.enqueue("");
280
+ controller.close();
281
+ }
282
+ });
283
+ var sendReactRouterResponseFunctionUrlStreaming = async (response, responseStream) => {
284
+ const metadata = extractAPIGatewayV2ResponseMetadata(response);
285
+ let body = response.body;
286
+ if (!body) {
287
+ body = emptyStream();
276
288
  }
289
+ const httpResponseStream = awslambda.HttpResponseStream.from(responseStream, metadata);
290
+ await (0, import_node4.writeReadableStreamToWritable)(body, httpResponseStream);
291
+ };
292
+ var functionUrlStreamingAdapter = {
293
+ wrapHandler: awslambda.streamifyResponse,
294
+ createReactRouterRequest: createReactRouterRequestAPIGateywayV2,
295
+ sendReactRouterResponse: sendReactRouterResponseFunctionUrlStreaming
277
296
  };
278
297
 
279
298
  // src/server.ts
280
- var AWSProxy = /* @__PURE__ */ ((AWSProxy2) => {
281
- AWSProxy2["APIGatewayV1"] = "APIGatewayV1";
282
- AWSProxy2["APIGatewayV2"] = "APIGatewayV2";
283
- AWSProxy2["ALB"] = "ALB";
284
- AWSProxy2["FunctionURL"] = "FunctionURL";
285
- return AWSProxy2;
286
- })(AWSProxy || {});
287
- function createRequestHandler({
288
- build,
289
- getLoadContext,
290
- mode = process.env.NODE_ENV,
291
- awsProxy = "APIGatewayV2" /* APIGatewayV2 */
292
- }) {
299
+ function createAPIGatewayV1RequestHandler(options) {
300
+ return createRequestHandlerForAdapter(apiGatewayV1Adapter, options);
301
+ }
302
+ function createAPIGatewayV2RequestHandler(options) {
303
+ return createRequestHandlerForAdapter(apiGatewayV2Adapter, options);
304
+ }
305
+ function createALBRequestHandler(options) {
306
+ return createRequestHandlerForAdapter(applicationLoadBalancerAdapter, options);
307
+ }
308
+ function createFunctionURLRequestHandler(options) {
309
+ return createRequestHandlerForAdapter(apiGatewayV2Adapter, options);
310
+ }
311
+ function createFunctionURLStreamingRequestHandler(options) {
312
+ return createRequestHandlerForAdapter(functionUrlStreamingAdapter, options);
313
+ }
314
+ function createRequestHandlerForAdapter(awsAdapter, { build, getLoadContext, mode = process.env.NODE_ENV }) {
293
315
  const handleRequest = (0, import_react_router.createRequestHandler)(build, mode);
294
- return async (event) => {
295
- const awsAdapter = createReactRouterAdapter(awsProxy);
316
+ return awsAdapter.wrapHandler(async (event, res) => {
296
317
  let request;
297
318
  try {
298
319
  request = awsAdapter.createReactRouterRequest(event);
299
320
  } catch (e) {
300
- return awsAdapter.sendReactRouterResponse(
301
- new Response(`Bad Request: ${e instanceof Error ? e.message : e}`, { status: 400 })
321
+ return await awsAdapter.sendReactRouterResponse(
322
+ new Response(`Bad Request: ${e instanceof Error ? e.message : e}`, { status: 400 }),
323
+ res
302
324
  );
303
325
  }
304
326
  const loadContext = await getLoadContext?.(event);
305
327
  const response = await handleRequest(request, loadContext);
306
- return awsAdapter.sendReactRouterResponse(response);
307
- };
328
+ return await awsAdapter.sendReactRouterResponse(response, res);
329
+ });
330
+ }
331
+
332
+ // src/legacy.ts
333
+ var AWSProxy = /* @__PURE__ */ ((AWSProxy2) => {
334
+ AWSProxy2["APIGatewayV1"] = "APIGatewayV1";
335
+ AWSProxy2["APIGatewayV2"] = "APIGatewayV2";
336
+ AWSProxy2["ALB"] = "ALB";
337
+ AWSProxy2["FunctionURL"] = "FunctionURL";
338
+ AWSProxy2["FunctionURLStreaming"] = "FunctionURLStreaming";
339
+ return AWSProxy2;
340
+ })(AWSProxy || {});
341
+ function createRequestHandler(options) {
342
+ const { awsProxy = "APIGatewayV2" /* APIGatewayV2 */, ...opts } = options;
343
+ switch (awsProxy) {
344
+ case "APIGatewayV1" /* APIGatewayV1 */:
345
+ return createAPIGatewayV1RequestHandler(
346
+ opts
347
+ );
348
+ case "APIGatewayV2" /* APIGatewayV2 */:
349
+ return createAPIGatewayV2RequestHandler(
350
+ opts
351
+ );
352
+ case "ALB" /* ALB */:
353
+ return createALBRequestHandler(opts);
354
+ case "FunctionURL" /* FunctionURL */:
355
+ return createFunctionURLRequestHandler(
356
+ opts
357
+ );
358
+ case "FunctionURLStreaming" /* FunctionURLStreaming */:
359
+ return createFunctionURLStreamingRequestHandler(
360
+ opts
361
+ );
362
+ default:
363
+ return assertNever(awsProxy, `Unsupported buffered AWS Proxy type: ${awsProxy}`);
364
+ }
365
+ }
366
+ function assertNever(x, message) {
367
+ throw new Error(message);
308
368
  }
309
369
  // Annotate the CommonJS export names for ESM import in node:
310
370
  0 && (module.exports = {
311
371
  AWSProxy,
372
+ createALBRequestHandler,
373
+ createAPIGatewayV1RequestHandler,
374
+ createAPIGatewayV2RequestHandler,
375
+ createFunctionURLRequestHandler,
376
+ createFunctionURLStreamingRequestHandler,
312
377
  createRequestHandler
313
378
  });
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @geostrategists/react-router-aws v2.0.0
2
+ * @geostrategists/react-router-aws v2.1.1
3
3
  *
4
4
  * Copyright (c) Geostrategists Consulting GmbH
5
5
  *
@@ -10,11 +10,12 @@
10
10
  */
11
11
 
12
12
  // src/server.ts
13
- import { createRequestHandler as createReactRouterRequestHandler } from "react-router";
13
+ import {
14
+ createRequestHandler as createReactRouterRequestHandler
15
+ } from "react-router";
14
16
 
15
- // src/adapters/api-gateway-v1.ts
17
+ // src/adapters/api-gateway-v2.ts
16
18
  import { readableStreamToString } from "@react-router/node";
17
- import { URLSearchParams } from "url";
18
19
 
19
20
  // src/binaryTypes.ts
20
21
  var binaryTypes = [
@@ -82,87 +83,91 @@ function isBinaryType(contentType) {
82
83
  return binaryTypes.includes(test);
83
84
  }
84
85
 
85
- // src/adapters/api-gateway-v1.ts
86
- function createReactRouterRequestAPIGatewayV1(event) {
87
- const host = event.headers["x-forwarded-host"] || event.headers.Host;
86
+ // src/adapters/api-gateway-v2.ts
87
+ function createReactRouterRequestAPIGateywayV2(event) {
88
+ const host = event.headers["x-forwarded-host"] || event.headers.host;
89
+ const search = event.rawQueryString.length ? `?${event.rawQueryString}` : "";
88
90
  const scheme = event.headers["x-forwarded-proto"] || "http";
89
- const rawQueryString = new URLSearchParams(event.queryStringParameters).toString();
90
- const search = rawQueryString.length > 0 ? `?${rawQueryString}` : "";
91
- const url = new URL(event.path + search, `${scheme}://${host}`);
91
+ const url = new URL(event.rawPath + search, `${scheme}://${host}`);
92
92
  const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
93
93
  const controller = new AbortController();
94
94
  return new Request(url.href, {
95
- method: event.requestContext.httpMethod,
96
- headers: createReactRouterHeadersAPIGatewayV1(event.headers),
95
+ method: event.requestContext.http.method,
96
+ headers: createReactRouterHeadersAPIGatewayV2(event.headers, event.cookies),
97
97
  signal: controller.signal,
98
- body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body || void 0
98
+ body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
99
99
  });
100
100
  }
101
- function createReactRouterHeadersAPIGatewayV1(requestHeaders) {
101
+ function createReactRouterHeadersAPIGatewayV2(requestHeaders, requestCookies) {
102
102
  const headers = new Headers();
103
103
  for (const [header, value] of Object.entries(requestHeaders)) {
104
104
  if (value) {
105
105
  headers.append(header, value);
106
106
  }
107
107
  }
108
+ if (requestCookies) {
109
+ headers.append("Cookie", requestCookies.join("; "));
110
+ }
108
111
  return headers;
109
112
  }
110
- async function sendReactRouterResponseAPIGatewayV1(nodeResponse) {
111
- const contentType = nodeResponse.headers.get("Content-Type");
112
- const isBase64Encoded = isBinaryType(contentType);
113
- let body;
114
- if (nodeResponse.body) {
115
- if (isBase64Encoded) {
116
- body = await readableStreamToString(nodeResponse.body, "base64");
117
- } else {
118
- body = await nodeResponse.text();
119
- }
113
+ function extractAPIGatewayV2ResponseMetadata(nodeResponse) {
114
+ const cookies = nodeResponse.headers.getSetCookie();
115
+ if (cookies.length) {
116
+ nodeResponse.headers.delete("Set-Cookie");
120
117
  }
121
118
  return {
122
119
  statusCode: nodeResponse.status,
123
120
  headers: Object.fromEntries(nodeResponse.headers.entries()),
124
- body: body || "",
125
- isBase64Encoded
121
+ cookies
126
122
  };
127
123
  }
128
- var apiGatewayV1Adapter = {
129
- createReactRouterRequest: createReactRouterRequestAPIGatewayV1,
130
- sendReactRouterResponse: sendReactRouterResponseAPIGatewayV1
124
+ async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
125
+ const result = extractAPIGatewayV2ResponseMetadata(nodeResponse);
126
+ const contentType = nodeResponse.headers.get("Content-Type");
127
+ result.isBase64Encoded = isBinaryType(contentType);
128
+ if (nodeResponse.body) {
129
+ if (result.isBase64Encoded) {
130
+ result.body = await readableStreamToString(nodeResponse.body, "base64");
131
+ } else {
132
+ result.body = await nodeResponse.text();
133
+ }
134
+ }
135
+ return result;
136
+ }
137
+ var apiGatewayV2Adapter = {
138
+ wrapHandler: (handler) => (e) => handler(e),
139
+ createReactRouterRequest: createReactRouterRequestAPIGateywayV2,
140
+ sendReactRouterResponse: sendReactRouterResponseAPIGatewayV2
131
141
  };
132
142
 
133
- // src/adapters/api-gateway-v2.ts
143
+ // src/adapters/api-gateway-v1.ts
134
144
  import { readableStreamToString as readableStreamToString2 } from "@react-router/node";
135
- function createReactRouterRequestAPIGateywayV2(event) {
136
- const host = event.headers["x-forwarded-host"] || event.headers.host;
137
- const search = event.rawQueryString.length ? `?${event.rawQueryString}` : "";
145
+ import { URLSearchParams } from "url";
146
+ function createReactRouterRequestAPIGatewayV1(event) {
147
+ const host = event.headers["x-forwarded-host"] || event.headers.Host;
138
148
  const scheme = event.headers["x-forwarded-proto"] || "http";
139
- const url = new URL(event.rawPath + search, `${scheme}://${host}`);
149
+ const rawQueryString = new URLSearchParams(event.queryStringParameters).toString();
150
+ const search = rawQueryString.length > 0 ? `?${rawQueryString}` : "";
151
+ const url = new URL(event.path + search, `${scheme}://${host}`);
140
152
  const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
141
153
  const controller = new AbortController();
142
154
  return new Request(url.href, {
143
- method: event.requestContext.http.method,
144
- headers: createReactRouterHeadersAPIGatewayV2(event.headers, event.cookies),
155
+ method: event.requestContext.httpMethod,
156
+ headers: createReactRouterHeadersAPIGatewayV1(event.headers),
145
157
  signal: controller.signal,
146
- body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
158
+ body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body || void 0
147
159
  });
148
160
  }
149
- function createReactRouterHeadersAPIGatewayV2(requestHeaders, requestCookies) {
161
+ function createReactRouterHeadersAPIGatewayV1(requestHeaders) {
150
162
  const headers = new Headers();
151
163
  for (const [header, value] of Object.entries(requestHeaders)) {
152
164
  if (value) {
153
165
  headers.append(header, value);
154
166
  }
155
167
  }
156
- if (requestCookies) {
157
- headers.append("Cookie", requestCookies.join("; "));
158
- }
159
168
  return headers;
160
169
  }
161
- async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
162
- const cookies = nodeResponse.headers.getSetCookie();
163
- if (cookies.length) {
164
- nodeResponse.headers.delete("Set-Cookie");
165
- }
170
+ async function sendReactRouterResponseAPIGatewayV1(nodeResponse) {
166
171
  const contentType = nodeResponse.headers.get("Content-Type");
167
172
  const isBase64Encoded = isBinaryType(contentType);
168
173
  let body;
@@ -176,14 +181,14 @@ async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
176
181
  return {
177
182
  statusCode: nodeResponse.status,
178
183
  headers: Object.fromEntries(nodeResponse.headers.entries()),
179
- cookies,
180
- body,
184
+ body: body || "",
181
185
  isBase64Encoded
182
186
  };
183
187
  }
184
- var apiGatewayV2Adapter = {
185
- createReactRouterRequest: createReactRouterRequestAPIGateywayV2,
186
- sendReactRouterResponse: sendReactRouterResponseAPIGatewayV2
188
+ var apiGatewayV1Adapter = {
189
+ wrapHandler: (handler) => (e) => handler(e),
190
+ createReactRouterRequest: createReactRouterRequestAPIGatewayV1,
191
+ sendReactRouterResponse: sendReactRouterResponseAPIGatewayV1
187
192
  };
188
193
 
189
194
  // src/adapters/application-load-balancer.ts
@@ -233,54 +238,111 @@ async function sendReactRouterResponseALB(nodeResponse) {
233
238
  };
234
239
  }
235
240
  var applicationLoadBalancerAdapter = {
241
+ wrapHandler: (handler) => (e) => handler(e),
236
242
  createReactRouterRequest: createReactRouterRequestALB,
237
243
  sendReactRouterResponse: sendReactRouterResponseALB
238
244
  };
239
245
 
240
- // src/adapters/index.ts
241
- var createReactRouterAdapter = (awsProxy) => {
242
- switch (awsProxy) {
243
- case "APIGatewayV1" /* APIGatewayV1 */:
244
- return apiGatewayV1Adapter;
245
- case "APIGatewayV2" /* APIGatewayV2 */:
246
- case "FunctionURL" /* FunctionURL */:
247
- return apiGatewayV2Adapter;
248
- case "ALB" /* ALB */:
249
- return applicationLoadBalancerAdapter;
246
+ // src/adapters/function-url-streaming.ts
247
+ import { writeReadableStreamToWritable } from "@react-router/node";
248
+ var emptyStream = () => new ReadableStream({
249
+ start(controller) {
250
+ controller.enqueue("");
251
+ controller.close();
252
+ }
253
+ });
254
+ var sendReactRouterResponseFunctionUrlStreaming = async (response, responseStream) => {
255
+ const metadata = extractAPIGatewayV2ResponseMetadata(response);
256
+ let body = response.body;
257
+ if (!body) {
258
+ body = emptyStream();
250
259
  }
260
+ const httpResponseStream = awslambda.HttpResponseStream.from(responseStream, metadata);
261
+ await writeReadableStreamToWritable(body, httpResponseStream);
262
+ };
263
+ var functionUrlStreamingAdapter = {
264
+ wrapHandler: awslambda.streamifyResponse,
265
+ createReactRouterRequest: createReactRouterRequestAPIGateywayV2,
266
+ sendReactRouterResponse: sendReactRouterResponseFunctionUrlStreaming
251
267
  };
252
268
 
253
269
  // src/server.ts
254
- var AWSProxy = /* @__PURE__ */ ((AWSProxy2) => {
255
- AWSProxy2["APIGatewayV1"] = "APIGatewayV1";
256
- AWSProxy2["APIGatewayV2"] = "APIGatewayV2";
257
- AWSProxy2["ALB"] = "ALB";
258
- AWSProxy2["FunctionURL"] = "FunctionURL";
259
- return AWSProxy2;
260
- })(AWSProxy || {});
261
- function createRequestHandler({
262
- build,
263
- getLoadContext,
264
- mode = process.env.NODE_ENV,
265
- awsProxy = "APIGatewayV2" /* APIGatewayV2 */
266
- }) {
270
+ function createAPIGatewayV1RequestHandler(options) {
271
+ return createRequestHandlerForAdapter(apiGatewayV1Adapter, options);
272
+ }
273
+ function createAPIGatewayV2RequestHandler(options) {
274
+ return createRequestHandlerForAdapter(apiGatewayV2Adapter, options);
275
+ }
276
+ function createALBRequestHandler(options) {
277
+ return createRequestHandlerForAdapter(applicationLoadBalancerAdapter, options);
278
+ }
279
+ function createFunctionURLRequestHandler(options) {
280
+ return createRequestHandlerForAdapter(apiGatewayV2Adapter, options);
281
+ }
282
+ function createFunctionURLStreamingRequestHandler(options) {
283
+ return createRequestHandlerForAdapter(functionUrlStreamingAdapter, options);
284
+ }
285
+ function createRequestHandlerForAdapter(awsAdapter, { build, getLoadContext, mode = process.env.NODE_ENV }) {
267
286
  const handleRequest = createReactRouterRequestHandler(build, mode);
268
- return async (event) => {
269
- const awsAdapter = createReactRouterAdapter(awsProxy);
287
+ return awsAdapter.wrapHandler(async (event, res) => {
270
288
  let request;
271
289
  try {
272
290
  request = awsAdapter.createReactRouterRequest(event);
273
291
  } catch (e) {
274
- return awsAdapter.sendReactRouterResponse(
275
- new Response(`Bad Request: ${e instanceof Error ? e.message : e}`, { status: 400 })
292
+ return await awsAdapter.sendReactRouterResponse(
293
+ new Response(`Bad Request: ${e instanceof Error ? e.message : e}`, { status: 400 }),
294
+ res
276
295
  );
277
296
  }
278
297
  const loadContext = await getLoadContext?.(event);
279
298
  const response = await handleRequest(request, loadContext);
280
- return awsAdapter.sendReactRouterResponse(response);
281
- };
299
+ return await awsAdapter.sendReactRouterResponse(response, res);
300
+ });
301
+ }
302
+
303
+ // src/legacy.ts
304
+ var AWSProxy = /* @__PURE__ */ ((AWSProxy2) => {
305
+ AWSProxy2["APIGatewayV1"] = "APIGatewayV1";
306
+ AWSProxy2["APIGatewayV2"] = "APIGatewayV2";
307
+ AWSProxy2["ALB"] = "ALB";
308
+ AWSProxy2["FunctionURL"] = "FunctionURL";
309
+ AWSProxy2["FunctionURLStreaming"] = "FunctionURLStreaming";
310
+ return AWSProxy2;
311
+ })(AWSProxy || {});
312
+ function createRequestHandler(options) {
313
+ const { awsProxy = "APIGatewayV2" /* APIGatewayV2 */, ...opts } = options;
314
+ switch (awsProxy) {
315
+ case "APIGatewayV1" /* APIGatewayV1 */:
316
+ return createAPIGatewayV1RequestHandler(
317
+ opts
318
+ );
319
+ case "APIGatewayV2" /* APIGatewayV2 */:
320
+ return createAPIGatewayV2RequestHandler(
321
+ opts
322
+ );
323
+ case "ALB" /* ALB */:
324
+ return createALBRequestHandler(opts);
325
+ case "FunctionURL" /* FunctionURL */:
326
+ return createFunctionURLRequestHandler(
327
+ opts
328
+ );
329
+ case "FunctionURLStreaming" /* FunctionURLStreaming */:
330
+ return createFunctionURLStreamingRequestHandler(
331
+ opts
332
+ );
333
+ default:
334
+ return assertNever(awsProxy, `Unsupported buffered AWS Proxy type: ${awsProxy}`);
335
+ }
336
+ }
337
+ function assertNever(x, message) {
338
+ throw new Error(message);
282
339
  }
283
340
  export {
284
341
  AWSProxy,
342
+ createALBRequestHandler,
343
+ createAPIGatewayV1RequestHandler,
344
+ createAPIGatewayV2RequestHandler,
345
+ createFunctionURLRequestHandler,
346
+ createFunctionURLStreamingRequestHandler,
285
347
  createRequestHandler
286
348
  };
package/eslint.config.mjs CHANGED
@@ -5,7 +5,7 @@ import tseslint from "typescript-eslint";
5
5
 
6
6
  export default tseslint.config(
7
7
  {
8
- ignores: ["dist"],
8
+ ignores: ["dist", "lib"],
9
9
  },
10
10
  eslint.configs.recommended,
11
11
  tseslint.configs.recommended,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geostrategists/react-router-aws",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "AWS adapter for React Router v7",
5
5
  "license": "MIT",
6
6
  "bugs": {
@@ -48,7 +48,7 @@
48
48
  "@react-router/dev": "^7.3.0",
49
49
  "@react-router/node": "^7.3.0",
50
50
  "@semantic-release/exec": "^7.0.3",
51
- "@types/aws-lambda": "^8.10.125",
51
+ "@types/aws-lambda": "^8.10.152",
52
52
  "@types/node": "^20",
53
53
  "eslint": "^9.22.0",
54
54
  "husky": "^9.1.7",
@@ -58,10 +58,10 @@
58
58
  "react-router": "^7.3.0",
59
59
  "semantic-release": "^24.2.3",
60
60
  "tsup": "^8.4.0",
61
- "typescript": "^5.8.2",
62
- "typescript-eslint": "^8.26.1"
61
+ "typescript": "^5.9.2",
62
+ "typescript-eslint": "^8.41.0"
63
63
  },
64
- "packageManager": "yarn@4.7.0+sha256.293632d8a095d8ea4786eb2c5798c83c37544abed17ed31186a3ec4549a07c06",
64
+ "packageManager": "yarn@4.9.4+sha512.7b1cb0b62abba6a537b3a2ce00811a843bea02bcf53138581a6ae5b1bf563f734872bd47de49ce32a9ca9dcaff995aa789577ffb16811da7c603dcf69e73750b",
65
65
  "release": {
66
66
  "plugins": [
67
67
  "@semantic-release/commit-analyzer",