@geostrategists/react-router-aws 2.0.0 → 2.1.0
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 +140 -69
- package/dist/index.mjs +128 -70
- 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.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Geostrategists Consulting GmbH
|
|
5
5
|
*
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
11
|
"use strict";
|
|
12
|
+
var __create = Object.create;
|
|
12
13
|
var __defProp = Object.defineProperty;
|
|
13
14
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
14
15
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
16
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
17
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
18
|
var __export = (target, all) => {
|
|
17
19
|
for (var name in all)
|
|
@@ -25,12 +27,25 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
27
|
}
|
|
26
28
|
return to;
|
|
27
29
|
};
|
|
30
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
31
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
32
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
33
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
34
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
35
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
36
|
+
mod
|
|
37
|
+
));
|
|
28
38
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
39
|
|
|
30
40
|
// src/index.ts
|
|
31
41
|
var index_exports = {};
|
|
32
42
|
__export(index_exports, {
|
|
33
43
|
AWSProxy: () => AWSProxy,
|
|
44
|
+
createALBRequestHandler: () => createALBRequestHandler,
|
|
45
|
+
createAPIGatewayV1RequestHandler: () => createAPIGatewayV1RequestHandler,
|
|
46
|
+
createAPIGatewayV2RequestHandler: () => createAPIGatewayV2RequestHandler,
|
|
47
|
+
createFunctionURLRequestHandler: () => createFunctionURLRequestHandler,
|
|
48
|
+
createFunctionURLStreamingRequestHandler: () => createFunctionURLStreamingRequestHandler,
|
|
34
49
|
createRequestHandler: () => createRequestHandler
|
|
35
50
|
});
|
|
36
51
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -38,9 +53,8 @@ module.exports = __toCommonJS(index_exports);
|
|
|
38
53
|
// src/server.ts
|
|
39
54
|
var import_react_router = require("react-router");
|
|
40
55
|
|
|
41
|
-
// src/adapters/api-gateway-
|
|
56
|
+
// src/adapters/api-gateway-v2.ts
|
|
42
57
|
var import_node = require("@react-router/node");
|
|
43
|
-
var import_url = require("url");
|
|
44
58
|
|
|
45
59
|
// src/binaryTypes.ts
|
|
46
60
|
var binaryTypes = [
|
|
@@ -108,32 +122,38 @@ function isBinaryType(contentType) {
|
|
|
108
122
|
return binaryTypes.includes(test);
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
// src/adapters/api-gateway-
|
|
112
|
-
function
|
|
113
|
-
const host = event.headers["x-forwarded-host"] || event.headers.
|
|
125
|
+
// src/adapters/api-gateway-v2.ts
|
|
126
|
+
function createReactRouterRequestAPIGateywayV2(event) {
|
|
127
|
+
const host = event.headers["x-forwarded-host"] || event.headers.host;
|
|
128
|
+
const search = event.rawQueryString.length ? `?${event.rawQueryString}` : "";
|
|
114
129
|
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}`);
|
|
130
|
+
const url = new URL(event.rawPath + search, `${scheme}://${host}`);
|
|
118
131
|
const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
|
|
119
132
|
const controller = new AbortController();
|
|
120
133
|
return new Request(url.href, {
|
|
121
|
-
method: event.requestContext.
|
|
122
|
-
headers:
|
|
134
|
+
method: event.requestContext.http.method,
|
|
135
|
+
headers: createReactRouterHeadersAPIGatewayV2(event.headers, event.cookies),
|
|
123
136
|
signal: controller.signal,
|
|
124
|
-
body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
|
|
137
|
+
body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
|
|
125
138
|
});
|
|
126
139
|
}
|
|
127
|
-
function
|
|
140
|
+
function createReactRouterHeadersAPIGatewayV2(requestHeaders, requestCookies) {
|
|
128
141
|
const headers = new Headers();
|
|
129
142
|
for (const [header, value] of Object.entries(requestHeaders)) {
|
|
130
143
|
if (value) {
|
|
131
144
|
headers.append(header, value);
|
|
132
145
|
}
|
|
133
146
|
}
|
|
147
|
+
if (requestCookies) {
|
|
148
|
+
headers.append("Cookie", requestCookies.join("; "));
|
|
149
|
+
}
|
|
134
150
|
return headers;
|
|
135
151
|
}
|
|
136
|
-
async function
|
|
152
|
+
async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
|
|
153
|
+
const cookies = nodeResponse.headers.getSetCookie();
|
|
154
|
+
if (cookies.length) {
|
|
155
|
+
nodeResponse.headers.delete("Set-Cookie");
|
|
156
|
+
}
|
|
137
157
|
const contentType = nodeResponse.headers.get("Content-Type");
|
|
138
158
|
const isBase64Encoded = isBinaryType(contentType);
|
|
139
159
|
let body;
|
|
@@ -147,48 +167,45 @@ async function sendReactRouterResponseAPIGatewayV1(nodeResponse) {
|
|
|
147
167
|
return {
|
|
148
168
|
statusCode: nodeResponse.status,
|
|
149
169
|
headers: Object.fromEntries(nodeResponse.headers.entries()),
|
|
150
|
-
|
|
170
|
+
cookies,
|
|
171
|
+
body,
|
|
151
172
|
isBase64Encoded
|
|
152
173
|
};
|
|
153
174
|
}
|
|
154
|
-
var
|
|
155
|
-
|
|
156
|
-
|
|
175
|
+
var apiGatewayV2Adapter = {
|
|
176
|
+
wrapHandler: (handler) => (e) => handler(e),
|
|
177
|
+
createReactRouterRequest: createReactRouterRequestAPIGateywayV2,
|
|
178
|
+
sendReactRouterResponse: sendReactRouterResponseAPIGatewayV2
|
|
157
179
|
};
|
|
158
180
|
|
|
159
|
-
// src/adapters/api-gateway-
|
|
181
|
+
// src/adapters/api-gateway-v1.ts
|
|
160
182
|
var import_node2 = require("@react-router/node");
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
183
|
+
var import_url = require("url");
|
|
184
|
+
function createReactRouterRequestAPIGatewayV1(event) {
|
|
185
|
+
const host = event.headers["x-forwarded-host"] || event.headers.Host;
|
|
164
186
|
const scheme = event.headers["x-forwarded-proto"] || "http";
|
|
165
|
-
const
|
|
187
|
+
const rawQueryString = new import_url.URLSearchParams(event.queryStringParameters).toString();
|
|
188
|
+
const search = rawQueryString.length > 0 ? `?${rawQueryString}` : "";
|
|
189
|
+
const url = new URL(event.path + search, `${scheme}://${host}`);
|
|
166
190
|
const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
|
|
167
191
|
const controller = new AbortController();
|
|
168
192
|
return new Request(url.href, {
|
|
169
|
-
method: event.requestContext.
|
|
170
|
-
headers:
|
|
193
|
+
method: event.requestContext.httpMethod,
|
|
194
|
+
headers: createReactRouterHeadersAPIGatewayV1(event.headers),
|
|
171
195
|
signal: controller.signal,
|
|
172
|
-
body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
|
|
196
|
+
body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body || void 0
|
|
173
197
|
});
|
|
174
198
|
}
|
|
175
|
-
function
|
|
199
|
+
function createReactRouterHeadersAPIGatewayV1(requestHeaders) {
|
|
176
200
|
const headers = new Headers();
|
|
177
201
|
for (const [header, value] of Object.entries(requestHeaders)) {
|
|
178
202
|
if (value) {
|
|
179
203
|
headers.append(header, value);
|
|
180
204
|
}
|
|
181
205
|
}
|
|
182
|
-
if (requestCookies) {
|
|
183
|
-
headers.append("Cookie", requestCookies.join("; "));
|
|
184
|
-
}
|
|
185
206
|
return headers;
|
|
186
207
|
}
|
|
187
|
-
async function
|
|
188
|
-
const cookies = nodeResponse.headers.getSetCookie();
|
|
189
|
-
if (cookies.length) {
|
|
190
|
-
nodeResponse.headers.delete("Set-Cookie");
|
|
191
|
-
}
|
|
208
|
+
async function sendReactRouterResponseAPIGatewayV1(nodeResponse) {
|
|
192
209
|
const contentType = nodeResponse.headers.get("Content-Type");
|
|
193
210
|
const isBase64Encoded = isBinaryType(contentType);
|
|
194
211
|
let body;
|
|
@@ -202,14 +219,14 @@ async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
|
|
|
202
219
|
return {
|
|
203
220
|
statusCode: nodeResponse.status,
|
|
204
221
|
headers: Object.fromEntries(nodeResponse.headers.entries()),
|
|
205
|
-
|
|
206
|
-
body,
|
|
222
|
+
body: body || "",
|
|
207
223
|
isBase64Encoded
|
|
208
224
|
};
|
|
209
225
|
}
|
|
210
|
-
var
|
|
211
|
-
|
|
212
|
-
|
|
226
|
+
var apiGatewayV1Adapter = {
|
|
227
|
+
wrapHandler: (handler) => (e) => handler(e),
|
|
228
|
+
createReactRouterRequest: createReactRouterRequestAPIGatewayV1,
|
|
229
|
+
sendReactRouterResponse: sendReactRouterResponseAPIGatewayV1
|
|
213
230
|
};
|
|
214
231
|
|
|
215
232
|
// src/adapters/application-load-balancer.ts
|
|
@@ -259,55 +276,109 @@ async function sendReactRouterResponseALB(nodeResponse) {
|
|
|
259
276
|
};
|
|
260
277
|
}
|
|
261
278
|
var applicationLoadBalancerAdapter = {
|
|
279
|
+
wrapHandler: (handler) => (e) => handler(e),
|
|
262
280
|
createReactRouterRequest: createReactRouterRequestALB,
|
|
263
281
|
sendReactRouterResponse: sendReactRouterResponseALB
|
|
264
282
|
};
|
|
265
283
|
|
|
266
|
-
// src/adapters/
|
|
267
|
-
var
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
284
|
+
// src/adapters/function-url-streaming.ts
|
|
285
|
+
var import_node4 = require("@react-router/node");
|
|
286
|
+
var import_promises = __toESM(require("stream/promises"));
|
|
287
|
+
var sendReactRouterResponseFunctionUrlStreaming = async (response, responseStream) => {
|
|
288
|
+
const httpResponseStream = awslambda.HttpResponseStream.from(responseStream, {
|
|
289
|
+
statusCode: response.status,
|
|
290
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
291
|
+
});
|
|
292
|
+
if (response.body) {
|
|
293
|
+
await (0, import_node4.writeReadableStreamToWritable)(response.body, httpResponseStream);
|
|
294
|
+
} else {
|
|
295
|
+
await import_promises.default.finished(httpResponseStream);
|
|
276
296
|
}
|
|
277
297
|
};
|
|
298
|
+
var functionUrlStreamingAdapter = {
|
|
299
|
+
wrapHandler: awslambda.streamifyResponse,
|
|
300
|
+
createReactRouterRequest: apiGatewayV2Adapter.createReactRouterRequest,
|
|
301
|
+
sendReactRouterResponse: sendReactRouterResponseFunctionUrlStreaming
|
|
302
|
+
};
|
|
278
303
|
|
|
279
304
|
// src/server.ts
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
305
|
+
function createAPIGatewayV1RequestHandler(options) {
|
|
306
|
+
return createRequestHandlerForAdapter(apiGatewayV1Adapter, options);
|
|
307
|
+
}
|
|
308
|
+
function createAPIGatewayV2RequestHandler(options) {
|
|
309
|
+
return createRequestHandlerForAdapter(apiGatewayV2Adapter, options);
|
|
310
|
+
}
|
|
311
|
+
function createALBRequestHandler(options) {
|
|
312
|
+
return createRequestHandlerForAdapter(applicationLoadBalancerAdapter, options);
|
|
313
|
+
}
|
|
314
|
+
function createFunctionURLRequestHandler(options) {
|
|
315
|
+
return createRequestHandlerForAdapter(apiGatewayV2Adapter, options);
|
|
316
|
+
}
|
|
317
|
+
function createFunctionURLStreamingRequestHandler(options) {
|
|
318
|
+
return createRequestHandlerForAdapter(functionUrlStreamingAdapter, options);
|
|
319
|
+
}
|
|
320
|
+
function createRequestHandlerForAdapter(awsAdapter, { build, getLoadContext, mode = process.env.NODE_ENV }) {
|
|
293
321
|
const handleRequest = (0, import_react_router.createRequestHandler)(build, mode);
|
|
294
|
-
return async (event) => {
|
|
295
|
-
const awsAdapter = createReactRouterAdapter(awsProxy);
|
|
322
|
+
return awsAdapter.wrapHandler(async (event, res) => {
|
|
296
323
|
let request;
|
|
297
324
|
try {
|
|
298
325
|
request = awsAdapter.createReactRouterRequest(event);
|
|
299
326
|
} catch (e) {
|
|
300
|
-
return awsAdapter.sendReactRouterResponse(
|
|
301
|
-
new Response(`Bad Request: ${e instanceof Error ? e.message : e}`, { status: 400 })
|
|
327
|
+
return await awsAdapter.sendReactRouterResponse(
|
|
328
|
+
new Response(`Bad Request: ${e instanceof Error ? e.message : e}`, { status: 400 }),
|
|
329
|
+
res
|
|
302
330
|
);
|
|
303
331
|
}
|
|
304
332
|
const loadContext = await getLoadContext?.(event);
|
|
305
333
|
const response = await handleRequest(request, loadContext);
|
|
306
|
-
return awsAdapter.sendReactRouterResponse(response);
|
|
307
|
-
};
|
|
334
|
+
return await awsAdapter.sendReactRouterResponse(response, res);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/legacy.ts
|
|
339
|
+
var AWSProxy = /* @__PURE__ */ ((AWSProxy2) => {
|
|
340
|
+
AWSProxy2["APIGatewayV1"] = "APIGatewayV1";
|
|
341
|
+
AWSProxy2["APIGatewayV2"] = "APIGatewayV2";
|
|
342
|
+
AWSProxy2["ALB"] = "ALB";
|
|
343
|
+
AWSProxy2["FunctionURL"] = "FunctionURL";
|
|
344
|
+
AWSProxy2["FunctionURLStreaming"] = "FunctionURLStreaming";
|
|
345
|
+
return AWSProxy2;
|
|
346
|
+
})(AWSProxy || {});
|
|
347
|
+
function createRequestHandler(options) {
|
|
348
|
+
const { awsProxy = "APIGatewayV2" /* APIGatewayV2 */, ...opts } = options;
|
|
349
|
+
switch (awsProxy) {
|
|
350
|
+
case "APIGatewayV1" /* APIGatewayV1 */:
|
|
351
|
+
return createAPIGatewayV1RequestHandler(
|
|
352
|
+
opts
|
|
353
|
+
);
|
|
354
|
+
case "APIGatewayV2" /* APIGatewayV2 */:
|
|
355
|
+
return createAPIGatewayV2RequestHandler(
|
|
356
|
+
opts
|
|
357
|
+
);
|
|
358
|
+
case "ALB" /* ALB */:
|
|
359
|
+
return createALBRequestHandler(opts);
|
|
360
|
+
case "FunctionURL" /* FunctionURL */:
|
|
361
|
+
return createFunctionURLRequestHandler(
|
|
362
|
+
opts
|
|
363
|
+
);
|
|
364
|
+
case "FunctionURLStreaming" /* FunctionURLStreaming */:
|
|
365
|
+
return createFunctionURLStreamingRequestHandler(
|
|
366
|
+
opts
|
|
367
|
+
);
|
|
368
|
+
default:
|
|
369
|
+
return assertNever(awsProxy, `Unsupported buffered AWS Proxy type: ${awsProxy}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function assertNever(x, message) {
|
|
373
|
+
throw new Error(message);
|
|
308
374
|
}
|
|
309
375
|
// Annotate the CommonJS export names for ESM import in node:
|
|
310
376
|
0 && (module.exports = {
|
|
311
377
|
AWSProxy,
|
|
378
|
+
createALBRequestHandler,
|
|
379
|
+
createAPIGatewayV1RequestHandler,
|
|
380
|
+
createAPIGatewayV2RequestHandler,
|
|
381
|
+
createFunctionURLRequestHandler,
|
|
382
|
+
createFunctionURLStreamingRequestHandler,
|
|
312
383
|
createRequestHandler
|
|
313
384
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @geostrategists/react-router-aws v2.
|
|
2
|
+
* @geostrategists/react-router-aws v2.1.0
|
|
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,32 +83,38 @@ 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
|
-
async function
|
|
113
|
+
async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
|
|
114
|
+
const cookies = nodeResponse.headers.getSetCookie();
|
|
115
|
+
if (cookies.length) {
|
|
116
|
+
nodeResponse.headers.delete("Set-Cookie");
|
|
117
|
+
}
|
|
111
118
|
const contentType = nodeResponse.headers.get("Content-Type");
|
|
112
119
|
const isBase64Encoded = isBinaryType(contentType);
|
|
113
120
|
let body;
|
|
@@ -121,48 +128,45 @@ async function sendReactRouterResponseAPIGatewayV1(nodeResponse) {
|
|
|
121
128
|
return {
|
|
122
129
|
statusCode: nodeResponse.status,
|
|
123
130
|
headers: Object.fromEntries(nodeResponse.headers.entries()),
|
|
124
|
-
|
|
131
|
+
cookies,
|
|
132
|
+
body,
|
|
125
133
|
isBase64Encoded
|
|
126
134
|
};
|
|
127
135
|
}
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
var apiGatewayV2Adapter = {
|
|
137
|
+
wrapHandler: (handler) => (e) => handler(e),
|
|
138
|
+
createReactRouterRequest: createReactRouterRequestAPIGateywayV2,
|
|
139
|
+
sendReactRouterResponse: sendReactRouterResponseAPIGatewayV2
|
|
131
140
|
};
|
|
132
141
|
|
|
133
|
-
// src/adapters/api-gateway-
|
|
142
|
+
// src/adapters/api-gateway-v1.ts
|
|
134
143
|
import { readableStreamToString as readableStreamToString2 } from "@react-router/node";
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
144
|
+
import { URLSearchParams } from "url";
|
|
145
|
+
function createReactRouterRequestAPIGatewayV1(event) {
|
|
146
|
+
const host = event.headers["x-forwarded-host"] || event.headers.Host;
|
|
138
147
|
const scheme = event.headers["x-forwarded-proto"] || "http";
|
|
139
|
-
const
|
|
148
|
+
const rawQueryString = new URLSearchParams(event.queryStringParameters).toString();
|
|
149
|
+
const search = rawQueryString.length > 0 ? `?${rawQueryString}` : "";
|
|
150
|
+
const url = new URL(event.path + search, `${scheme}://${host}`);
|
|
140
151
|
const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
|
|
141
152
|
const controller = new AbortController();
|
|
142
153
|
return new Request(url.href, {
|
|
143
|
-
method: event.requestContext.
|
|
144
|
-
headers:
|
|
154
|
+
method: event.requestContext.httpMethod,
|
|
155
|
+
headers: createReactRouterHeadersAPIGatewayV1(event.headers),
|
|
145
156
|
signal: controller.signal,
|
|
146
|
-
body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
|
|
157
|
+
body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body || void 0
|
|
147
158
|
});
|
|
148
159
|
}
|
|
149
|
-
function
|
|
160
|
+
function createReactRouterHeadersAPIGatewayV1(requestHeaders) {
|
|
150
161
|
const headers = new Headers();
|
|
151
162
|
for (const [header, value] of Object.entries(requestHeaders)) {
|
|
152
163
|
if (value) {
|
|
153
164
|
headers.append(header, value);
|
|
154
165
|
}
|
|
155
166
|
}
|
|
156
|
-
if (requestCookies) {
|
|
157
|
-
headers.append("Cookie", requestCookies.join("; "));
|
|
158
|
-
}
|
|
159
167
|
return headers;
|
|
160
168
|
}
|
|
161
|
-
async function
|
|
162
|
-
const cookies = nodeResponse.headers.getSetCookie();
|
|
163
|
-
if (cookies.length) {
|
|
164
|
-
nodeResponse.headers.delete("Set-Cookie");
|
|
165
|
-
}
|
|
169
|
+
async function sendReactRouterResponseAPIGatewayV1(nodeResponse) {
|
|
166
170
|
const contentType = nodeResponse.headers.get("Content-Type");
|
|
167
171
|
const isBase64Encoded = isBinaryType(contentType);
|
|
168
172
|
let body;
|
|
@@ -176,14 +180,14 @@ async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
|
|
|
176
180
|
return {
|
|
177
181
|
statusCode: nodeResponse.status,
|
|
178
182
|
headers: Object.fromEntries(nodeResponse.headers.entries()),
|
|
179
|
-
|
|
180
|
-
body,
|
|
183
|
+
body: body || "",
|
|
181
184
|
isBase64Encoded
|
|
182
185
|
};
|
|
183
186
|
}
|
|
184
|
-
var
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
var apiGatewayV1Adapter = {
|
|
188
|
+
wrapHandler: (handler) => (e) => handler(e),
|
|
189
|
+
createReactRouterRequest: createReactRouterRequestAPIGatewayV1,
|
|
190
|
+
sendReactRouterResponse: sendReactRouterResponseAPIGatewayV1
|
|
187
191
|
};
|
|
188
192
|
|
|
189
193
|
// src/adapters/application-load-balancer.ts
|
|
@@ -233,54 +237,108 @@ async function sendReactRouterResponseALB(nodeResponse) {
|
|
|
233
237
|
};
|
|
234
238
|
}
|
|
235
239
|
var applicationLoadBalancerAdapter = {
|
|
240
|
+
wrapHandler: (handler) => (e) => handler(e),
|
|
236
241
|
createReactRouterRequest: createReactRouterRequestALB,
|
|
237
242
|
sendReactRouterResponse: sendReactRouterResponseALB
|
|
238
243
|
};
|
|
239
244
|
|
|
240
|
-
// src/adapters/
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
245
|
+
// src/adapters/function-url-streaming.ts
|
|
246
|
+
import { writeReadableStreamToWritable } from "@react-router/node";
|
|
247
|
+
import stream from "node:stream/promises";
|
|
248
|
+
var sendReactRouterResponseFunctionUrlStreaming = async (response, responseStream) => {
|
|
249
|
+
const httpResponseStream = awslambda.HttpResponseStream.from(responseStream, {
|
|
250
|
+
statusCode: response.status,
|
|
251
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
252
|
+
});
|
|
253
|
+
if (response.body) {
|
|
254
|
+
await writeReadableStreamToWritable(response.body, httpResponseStream);
|
|
255
|
+
} else {
|
|
256
|
+
await stream.finished(httpResponseStream);
|
|
250
257
|
}
|
|
251
258
|
};
|
|
259
|
+
var functionUrlStreamingAdapter = {
|
|
260
|
+
wrapHandler: awslambda.streamifyResponse,
|
|
261
|
+
createReactRouterRequest: apiGatewayV2Adapter.createReactRouterRequest,
|
|
262
|
+
sendReactRouterResponse: sendReactRouterResponseFunctionUrlStreaming
|
|
263
|
+
};
|
|
252
264
|
|
|
253
265
|
// src/server.ts
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
266
|
+
function createAPIGatewayV1RequestHandler(options) {
|
|
267
|
+
return createRequestHandlerForAdapter(apiGatewayV1Adapter, options);
|
|
268
|
+
}
|
|
269
|
+
function createAPIGatewayV2RequestHandler(options) {
|
|
270
|
+
return createRequestHandlerForAdapter(apiGatewayV2Adapter, options);
|
|
271
|
+
}
|
|
272
|
+
function createALBRequestHandler(options) {
|
|
273
|
+
return createRequestHandlerForAdapter(applicationLoadBalancerAdapter, options);
|
|
274
|
+
}
|
|
275
|
+
function createFunctionURLRequestHandler(options) {
|
|
276
|
+
return createRequestHandlerForAdapter(apiGatewayV2Adapter, options);
|
|
277
|
+
}
|
|
278
|
+
function createFunctionURLStreamingRequestHandler(options) {
|
|
279
|
+
return createRequestHandlerForAdapter(functionUrlStreamingAdapter, options);
|
|
280
|
+
}
|
|
281
|
+
function createRequestHandlerForAdapter(awsAdapter, { build, getLoadContext, mode = process.env.NODE_ENV }) {
|
|
267
282
|
const handleRequest = createReactRouterRequestHandler(build, mode);
|
|
268
|
-
return async (event) => {
|
|
269
|
-
const awsAdapter = createReactRouterAdapter(awsProxy);
|
|
283
|
+
return awsAdapter.wrapHandler(async (event, res) => {
|
|
270
284
|
let request;
|
|
271
285
|
try {
|
|
272
286
|
request = awsAdapter.createReactRouterRequest(event);
|
|
273
287
|
} catch (e) {
|
|
274
|
-
return awsAdapter.sendReactRouterResponse(
|
|
275
|
-
new Response(`Bad Request: ${e instanceof Error ? e.message : e}`, { status: 400 })
|
|
288
|
+
return await awsAdapter.sendReactRouterResponse(
|
|
289
|
+
new Response(`Bad Request: ${e instanceof Error ? e.message : e}`, { status: 400 }),
|
|
290
|
+
res
|
|
276
291
|
);
|
|
277
292
|
}
|
|
278
293
|
const loadContext = await getLoadContext?.(event);
|
|
279
294
|
const response = await handleRequest(request, loadContext);
|
|
280
|
-
return awsAdapter.sendReactRouterResponse(response);
|
|
281
|
-
};
|
|
295
|
+
return await awsAdapter.sendReactRouterResponse(response, res);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/legacy.ts
|
|
300
|
+
var AWSProxy = /* @__PURE__ */ ((AWSProxy2) => {
|
|
301
|
+
AWSProxy2["APIGatewayV1"] = "APIGatewayV1";
|
|
302
|
+
AWSProxy2["APIGatewayV2"] = "APIGatewayV2";
|
|
303
|
+
AWSProxy2["ALB"] = "ALB";
|
|
304
|
+
AWSProxy2["FunctionURL"] = "FunctionURL";
|
|
305
|
+
AWSProxy2["FunctionURLStreaming"] = "FunctionURLStreaming";
|
|
306
|
+
return AWSProxy2;
|
|
307
|
+
})(AWSProxy || {});
|
|
308
|
+
function createRequestHandler(options) {
|
|
309
|
+
const { awsProxy = "APIGatewayV2" /* APIGatewayV2 */, ...opts } = options;
|
|
310
|
+
switch (awsProxy) {
|
|
311
|
+
case "APIGatewayV1" /* APIGatewayV1 */:
|
|
312
|
+
return createAPIGatewayV1RequestHandler(
|
|
313
|
+
opts
|
|
314
|
+
);
|
|
315
|
+
case "APIGatewayV2" /* APIGatewayV2 */:
|
|
316
|
+
return createAPIGatewayV2RequestHandler(
|
|
317
|
+
opts
|
|
318
|
+
);
|
|
319
|
+
case "ALB" /* ALB */:
|
|
320
|
+
return createALBRequestHandler(opts);
|
|
321
|
+
case "FunctionURL" /* FunctionURL */:
|
|
322
|
+
return createFunctionURLRequestHandler(
|
|
323
|
+
opts
|
|
324
|
+
);
|
|
325
|
+
case "FunctionURLStreaming" /* FunctionURLStreaming */:
|
|
326
|
+
return createFunctionURLStreamingRequestHandler(
|
|
327
|
+
opts
|
|
328
|
+
);
|
|
329
|
+
default:
|
|
330
|
+
return assertNever(awsProxy, `Unsupported buffered AWS Proxy type: ${awsProxy}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function assertNever(x, message) {
|
|
334
|
+
throw new Error(message);
|
|
282
335
|
}
|
|
283
336
|
export {
|
|
284
337
|
AWSProxy,
|
|
338
|
+
createALBRequestHandler,
|
|
339
|
+
createAPIGatewayV1RequestHandler,
|
|
340
|
+
createAPIGatewayV2RequestHandler,
|
|
341
|
+
createFunctionURLRequestHandler,
|
|
342
|
+
createFunctionURLStreamingRequestHandler,
|
|
285
343
|
createRequestHandler
|
|
286
344
|
};
|
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.0",
|
|
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",
|