@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 +86 -35
- package/dist/index.d.mts +89 -18
- package/dist/index.d.ts +89 -18
- package/dist/index.js +144 -79
- package/dist/index.mjs +142 -80
- package/eslint.config.mjs +1 -1
- package/package.json +5 -5
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
|
[](https://badge.fury.io/js/@geostrategists%2Freact-router-aws)
|
|
6
2
|
[](https://packagephobia.com/result?p=@geostrategists/react-router-aws)
|
|
7
3
|
|
|
8
|
-
|
|
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
|
-
|
|
17
|
+
### Acknowledgements
|
|
11
18
|
|
|
12
|
-
-
|
|
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
|
-
|
|
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
|
-
//
|
|
46
|
+
// lambda-handler.ts
|
|
24
47
|
import * as build from "virtual:react-router/server-build";
|
|
25
|
-
import {
|
|
48
|
+
import { createAPIGatewayV2RequestHandler } from "@geostrategists/react-router-aws";
|
|
26
49
|
|
|
27
|
-
export const handler =
|
|
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
|
-
|
|
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
|
-
|
|
63
|
+
### Streaming support for Lambda Function URLs
|
|
37
64
|
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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:
|
|
19
|
-
type
|
|
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
|
-
|
|
29
|
-
|
|
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,
|
|
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:
|
|
19
|
-
type
|
|
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
|
-
|
|
29
|
-
|
|
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,
|
|
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.
|
|
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-
|
|
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-
|
|
112
|
-
function
|
|
113
|
-
const host = event.headers["x-forwarded-host"] || event.headers.
|
|
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
|
|
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.
|
|
122
|
-
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
|
|
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
|
|
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
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
151
|
-
isBase64Encoded
|
|
150
|
+
cookies
|
|
152
151
|
};
|
|
153
152
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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-
|
|
172
|
+
// src/adapters/api-gateway-v1.ts
|
|
160
173
|
var import_node2 = require("@react-router/node");
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
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
|
|
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.
|
|
170
|
-
headers:
|
|
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
|
|
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
|
|
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
|
-
|
|
206
|
-
body,
|
|
213
|
+
body: body || "",
|
|
207
214
|
isBase64Encoded
|
|
208
215
|
};
|
|
209
216
|
}
|
|
210
|
-
var
|
|
211
|
-
|
|
212
|
-
|
|
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/
|
|
267
|
-
var
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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.
|
|
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 {
|
|
13
|
+
import {
|
|
14
|
+
createRequestHandler as createReactRouterRequestHandler
|
|
15
|
+
} from "react-router";
|
|
14
16
|
|
|
15
|
-
// src/adapters/api-gateway-
|
|
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-
|
|
86
|
-
function
|
|
87
|
-
const host = event.headers["x-forwarded-host"] || event.headers.
|
|
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
|
|
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.
|
|
96
|
-
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
|
|
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
|
|
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
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
125
|
-
isBase64Encoded
|
|
121
|
+
cookies
|
|
126
122
|
};
|
|
127
123
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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-
|
|
143
|
+
// src/adapters/api-gateway-v1.ts
|
|
134
144
|
import { readableStreamToString as readableStreamToString2 } from "@react-router/node";
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
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
|
|
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.
|
|
144
|
-
headers:
|
|
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
|
|
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
|
|
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
|
-
|
|
180
|
-
body,
|
|
184
|
+
body: body || "",
|
|
181
185
|
isBase64Encoded
|
|
182
186
|
};
|
|
183
187
|
}
|
|
184
|
-
var
|
|
185
|
-
|
|
186
|
-
|
|
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/
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geostrategists/react-router-aws",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
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.
|
|
62
|
-
"typescript-eslint": "^8.
|
|
61
|
+
"typescript": "^5.9.2",
|
|
62
|
+
"typescript-eslint": "^8.41.0"
|
|
63
63
|
},
|
|
64
|
-
"packageManager": "yarn@4.
|
|
64
|
+
"packageManager": "yarn@4.9.4+sha512.7b1cb0b62abba6a537b3a2ce00811a843bea02bcf53138581a6ae5b1bf563f734872bd47de49ce32a9ca9dcaff995aa789577ffb16811da7c603dcf69e73750b",
|
|
65
65
|
"release": {
|
|
66
66
|
"plugins": [
|
|
67
67
|
"@semantic-release/commit-analyzer",
|