@geostrategists/react-router-aws 2.0.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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) React Training LLC 2015-2019 Copyright (c) Remix Software Inc. 2020-2021 Copyright (c) Shopify Inc. 2022-2023 (c) Copyright (c) 2022 Wing Leung Copyright (c) 2025 Geostrategists Consulting GmbH
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @geostrategists/react-router-aws
2
+
3
+ ## AWS adapters for React Router v7 (successor to Remix)
4
+
5
+ [![npm version](https://badge.fury.io/js/@geostrategists%2Freact-router-aws.svg)](https://badge.fury.io/js/@geostrategists%2Freact-router-aws)
6
+ [![install size](https://packagephobia.com/badge?p=@geostrategists/react-router-aws)](https://packagephobia.com/result?p=@geostrategists/react-router-aws)
7
+
8
+ Forked from [remix-aws](https://github.com/wingleung/remix-aws) to support React Router v7, which Remix was merged into.
9
+
10
+ ## 🚀 support
11
+
12
+ - API gateway v1
13
+ - API gateway v2
14
+ - Application load balancer
15
+
16
+ ## Getting started
17
+
18
+ ```shell
19
+ npm install --save @geostrategists/react-router-aws
20
+ ```
21
+
22
+ ```javascript
23
+ // server.js
24
+ import * as build from "virtual:react-router/server-build";
25
+ import { AWSProxy, createRequestHandler } from "@geostrategists/react-router-aws";
26
+
27
+ export const handler = createRequestHandler({
28
+ build,
29
+ mode: process.env.NODE_ENV,
30
+ awsProxy: AWSProxy.APIGatewayV2,
31
+ });
32
+ ```
33
+
34
+ ### `awsProxy`
35
+
36
+ By default, the `awsProxy` is set to `AWSProxy.APIGatewayV2`.
37
+
38
+ #### Options
39
+
40
+ - `AWSProxy.APIGatewayV1`
41
+ - `AWSProxy.APIGatewayV2`
42
+ - `AWSProxy.ALB`
43
+ - `AWSProxy.FunctionURL`
44
+
45
+ ## Deployment recommendation
46
+
47
+ Since Vite already bundles the project into a single entry point, there is no need to further
48
+ bundle the lambda code.
49
+ For example, when using AWS CDK, we recommend using lambda.Function directly instead of lambda.NodeJsFunction.
50
+
51
+ Dependencies can be provided using a layer, for example.
52
+
53
+ We recommend setting the `serverModuleFormat` to ESM.
54
+ However, to ensure that AWS lambda correctly interprets the output file as an ES module, you need to take additional steps.
55
+
56
+ There are two primary methods to achieve this:
57
+
58
+ - Specify the module type in package.json:
59
+ Add `"type": "module"` to your package.json file and ensure that this file is included in the deployment package sent to AWS Lambda.
60
+
61
+ - Use the .mjs extension:
62
+ Alternatively, you can change the file extension to `.mjs`. For example, you can configure the React Router `serverBuildFile` setting to output `index.mjs`.
63
+
64
+ more info: [AWS docs on ES module support in AWS lambdas](https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html#designate-es-module)
65
+
66
+ ## Notes
67
+
68
+ ### split from @remix/architect
69
+
70
+ As mentioned in [#3173](https://github.com/remix-run/remix/pull/3173) the goal would be to provide an AWS adapter for
71
+ the community by the community.
72
+ In doing so the focus will be on AWS integrations and less on Architect. I do think it's added value to provide examples
73
+ for Architect, AWS SAM, AWS CDK, Serverless,...
74
+
75
+ **info:** [ALB types](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/trigger/alb.d.ts#L29-L48)
76
+ vs [API gateway v1 types](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/trigger/api-gateway-proxy.d.ts#L116-L145)
@@ -0,0 +1,31 @@
1
+ import { APIGatewayProxyEventV2, APIGatewayProxyEvent, ALBEvent, APIGatewayProxyHandlerV2, APIGatewayProxyHandler, ALBHandler } from 'aws-lambda';
2
+ import { UNSAFE_MiddlewareEnabled, unstable_InitialContext, AppLoadContext, ServerBuild } from 'react-router';
3
+
4
+ declare enum AWSProxy {
5
+ APIGatewayV1 = "APIGatewayV1",
6
+ APIGatewayV2 = "APIGatewayV2",
7
+ ALB = "ALB",
8
+ FunctionURL = "FunctionURL"
9
+ }
10
+ type MaybePromise<T> = T | Promise<T>;
11
+ /**
12
+ * A function that returns the value to use as `context` in route `loader` and
13
+ * `action` functions.
14
+ *
15
+ * You can think of this as an escape hatch that allows you to pass
16
+ * environment/platform-specific values through to your loader/action.
17
+ */
18
+ type GetLoadContextFunction = (event: APIGatewayProxyEventV2 | APIGatewayProxyEvent | ALBEvent) => UNSAFE_MiddlewareEnabled extends true ? MaybePromise<unstable_InitialContext> : MaybePromise<AppLoadContext>;
19
+ type RequestHandler = APIGatewayProxyHandlerV2 | APIGatewayProxyHandler | ALBHandler;
20
+ /**
21
+ * Returns a request handler for AWS that serves the response using
22
+ * React Router.
23
+ */
24
+ declare function createRequestHandler({ build, getLoadContext, mode, awsProxy, }: {
25
+ build: ServerBuild;
26
+ getLoadContext?: GetLoadContextFunction;
27
+ mode?: string;
28
+ awsProxy?: AWSProxy;
29
+ }): RequestHandler;
30
+
31
+ export { AWSProxy, type GetLoadContextFunction, type RequestHandler, createRequestHandler };
@@ -0,0 +1,31 @@
1
+ import { APIGatewayProxyEventV2, APIGatewayProxyEvent, ALBEvent, APIGatewayProxyHandlerV2, APIGatewayProxyHandler, ALBHandler } from 'aws-lambda';
2
+ import { UNSAFE_MiddlewareEnabled, unstable_InitialContext, AppLoadContext, ServerBuild } from 'react-router';
3
+
4
+ declare enum AWSProxy {
5
+ APIGatewayV1 = "APIGatewayV1",
6
+ APIGatewayV2 = "APIGatewayV2",
7
+ ALB = "ALB",
8
+ FunctionURL = "FunctionURL"
9
+ }
10
+ type MaybePromise<T> = T | Promise<T>;
11
+ /**
12
+ * A function that returns the value to use as `context` in route `loader` and
13
+ * `action` functions.
14
+ *
15
+ * You can think of this as an escape hatch that allows you to pass
16
+ * environment/platform-specific values through to your loader/action.
17
+ */
18
+ type GetLoadContextFunction = (event: APIGatewayProxyEventV2 | APIGatewayProxyEvent | ALBEvent) => UNSAFE_MiddlewareEnabled extends true ? MaybePromise<unstable_InitialContext> : MaybePromise<AppLoadContext>;
19
+ type RequestHandler = APIGatewayProxyHandlerV2 | APIGatewayProxyHandler | ALBHandler;
20
+ /**
21
+ * Returns a request handler for AWS that serves the response using
22
+ * React Router.
23
+ */
24
+ declare function createRequestHandler({ build, getLoadContext, mode, awsProxy, }: {
25
+ build: ServerBuild;
26
+ getLoadContext?: GetLoadContextFunction;
27
+ mode?: string;
28
+ awsProxy?: AWSProxy;
29
+ }): RequestHandler;
30
+
31
+ export { AWSProxy, type GetLoadContextFunction, type RequestHandler, createRequestHandler };
package/dist/index.js ADDED
@@ -0,0 +1,313 @@
1
+ /**
2
+ * @geostrategists/react-router-aws v2.0.0
3
+ *
4
+ * Copyright (c) Geostrategists Consulting GmbH
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ "use strict";
12
+ var __defProp = Object.defineProperty;
13
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
14
+ var __getOwnPropNames = Object.getOwnPropertyNames;
15
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
20
+ var __copyProps = (to, from, except, desc) => {
21
+ if (from && typeof from === "object" || typeof from === "function") {
22
+ for (let key of __getOwnPropNames(from))
23
+ if (!__hasOwnProp.call(to, key) && key !== except)
24
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
25
+ }
26
+ return to;
27
+ };
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AWSProxy: () => AWSProxy,
34
+ createRequestHandler: () => createRequestHandler
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/server.ts
39
+ var import_react_router = require("react-router");
40
+
41
+ // src/adapters/api-gateway-v1.ts
42
+ var import_node = require("@react-router/node");
43
+ var import_url = require("url");
44
+
45
+ // src/binaryTypes.ts
46
+ var binaryTypes = [
47
+ "application/octet-stream",
48
+ // Docs
49
+ "application/epub+zip",
50
+ "application/msword",
51
+ "application/pdf",
52
+ "application/rtf",
53
+ "application/vnd.amazon.ebook",
54
+ "application/vnd.ms-excel",
55
+ "application/vnd.ms-powerpoint",
56
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
57
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
58
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
59
+ // Fonts
60
+ "font/otf",
61
+ "font/woff",
62
+ "font/woff2",
63
+ // Images
64
+ "image/avif",
65
+ "image/bmp",
66
+ "image/gif",
67
+ "image/jpeg",
68
+ "image/png",
69
+ "image/tiff",
70
+ "image/vnd.microsoft.icon",
71
+ "image/webp",
72
+ // Audio
73
+ "audio/3gpp",
74
+ "audio/aac",
75
+ "audio/basic",
76
+ "audio/mpeg",
77
+ "audio/ogg",
78
+ "audio/wav",
79
+ "audio/webm",
80
+ "audio/x-aiff",
81
+ "audio/x-midi",
82
+ "audio/x-wav",
83
+ // Video
84
+ "video/3gpp",
85
+ "video/mp2t",
86
+ "video/mpeg",
87
+ "video/ogg",
88
+ "video/quicktime",
89
+ "video/webm",
90
+ "video/x-msvideo",
91
+ // Archives
92
+ "application/java-archive",
93
+ "application/vnd.apple.installer+xml",
94
+ "application/x-7z-compressed",
95
+ "application/x-apple-diskimage",
96
+ "application/x-bzip",
97
+ "application/x-bzip2",
98
+ "application/x-gzip",
99
+ "application/x-java-archive",
100
+ "application/x-rar-compressed",
101
+ "application/x-tar",
102
+ "application/x-zip",
103
+ "application/zip"
104
+ ];
105
+ function isBinaryType(contentType) {
106
+ if (!contentType) return false;
107
+ const [test] = contentType.split(";");
108
+ return binaryTypes.includes(test);
109
+ }
110
+
111
+ // src/adapters/api-gateway-v1.ts
112
+ function createReactRouterRequestAPIGatewayV1(event) {
113
+ const host = event.headers["x-forwarded-host"] || event.headers.Host;
114
+ const scheme = event.headers["x-forwarded-proto"] || "http";
115
+ const rawQueryString = new import_url.URLSearchParams(event.queryStringParameters).toString();
116
+ const search = rawQueryString.length > 0 ? `?${rawQueryString}` : "";
117
+ const url = new URL(event.path + search, `${scheme}://${host}`);
118
+ const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
119
+ const controller = new AbortController();
120
+ return new Request(url.href, {
121
+ method: event.requestContext.httpMethod,
122
+ headers: createReactRouterHeadersAPIGatewayV1(event.headers),
123
+ signal: controller.signal,
124
+ body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body || void 0
125
+ });
126
+ }
127
+ function createReactRouterHeadersAPIGatewayV1(requestHeaders) {
128
+ const headers = new Headers();
129
+ for (const [header, value] of Object.entries(requestHeaders)) {
130
+ if (value) {
131
+ headers.append(header, value);
132
+ }
133
+ }
134
+ return headers;
135
+ }
136
+ async function sendReactRouterResponseAPIGatewayV1(nodeResponse) {
137
+ const contentType = nodeResponse.headers.get("Content-Type");
138
+ const isBase64Encoded = isBinaryType(contentType);
139
+ let body;
140
+ if (nodeResponse.body) {
141
+ if (isBase64Encoded) {
142
+ body = await (0, import_node.readableStreamToString)(nodeResponse.body, "base64");
143
+ } else {
144
+ body = await nodeResponse.text();
145
+ }
146
+ }
147
+ return {
148
+ statusCode: nodeResponse.status,
149
+ headers: Object.fromEntries(nodeResponse.headers.entries()),
150
+ body: body || "",
151
+ isBase64Encoded
152
+ };
153
+ }
154
+ var apiGatewayV1Adapter = {
155
+ createReactRouterRequest: createReactRouterRequestAPIGatewayV1,
156
+ sendReactRouterResponse: sendReactRouterResponseAPIGatewayV1
157
+ };
158
+
159
+ // src/adapters/api-gateway-v2.ts
160
+ var import_node2 = require("@react-router/node");
161
+ function createReactRouterRequestAPIGateywayV2(event) {
162
+ const host = event.headers["x-forwarded-host"] || event.headers.host;
163
+ const search = event.rawQueryString.length ? `?${event.rawQueryString}` : "";
164
+ const scheme = event.headers["x-forwarded-proto"] || "http";
165
+ const url = new URL(event.rawPath + search, `${scheme}://${host}`);
166
+ const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
167
+ const controller = new AbortController();
168
+ return new Request(url.href, {
169
+ method: event.requestContext.http.method,
170
+ headers: createReactRouterHeadersAPIGatewayV2(event.headers, event.cookies),
171
+ signal: controller.signal,
172
+ body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
173
+ });
174
+ }
175
+ function createReactRouterHeadersAPIGatewayV2(requestHeaders, requestCookies) {
176
+ const headers = new Headers();
177
+ for (const [header, value] of Object.entries(requestHeaders)) {
178
+ if (value) {
179
+ headers.append(header, value);
180
+ }
181
+ }
182
+ if (requestCookies) {
183
+ headers.append("Cookie", requestCookies.join("; "));
184
+ }
185
+ return headers;
186
+ }
187
+ async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
188
+ const cookies = nodeResponse.headers.getSetCookie();
189
+ if (cookies.length) {
190
+ nodeResponse.headers.delete("Set-Cookie");
191
+ }
192
+ const contentType = nodeResponse.headers.get("Content-Type");
193
+ const isBase64Encoded = isBinaryType(contentType);
194
+ let body;
195
+ if (nodeResponse.body) {
196
+ if (isBase64Encoded) {
197
+ body = await (0, import_node2.readableStreamToString)(nodeResponse.body, "base64");
198
+ } else {
199
+ body = await nodeResponse.text();
200
+ }
201
+ }
202
+ return {
203
+ statusCode: nodeResponse.status,
204
+ headers: Object.fromEntries(nodeResponse.headers.entries()),
205
+ cookies,
206
+ body,
207
+ isBase64Encoded
208
+ };
209
+ }
210
+ var apiGatewayV2Adapter = {
211
+ createReactRouterRequest: createReactRouterRequestAPIGateywayV2,
212
+ sendReactRouterResponse: sendReactRouterResponseAPIGatewayV2
213
+ };
214
+
215
+ // src/adapters/application-load-balancer.ts
216
+ var import_node3 = require("@react-router/node");
217
+ var import_url2 = require("url");
218
+ function createReactRouterRequestALB(event) {
219
+ const headers = event?.headers || {};
220
+ const host = headers["x-forwarded-host"] || headers.Host;
221
+ const scheme = headers["x-forwarded-proto"] || "http";
222
+ const rawQueryString = new import_url2.URLSearchParams(event.queryStringParameters).toString();
223
+ const search = rawQueryString.length > 0 ? `?${rawQueryString}` : "";
224
+ const url = new URL(event.path + search, `${scheme}://${host}`);
225
+ const isFormData = headers["content-type"]?.includes("multipart/form-data");
226
+ const controller = new AbortController();
227
+ return new Request(url.href, {
228
+ method: event.httpMethod,
229
+ headers: createReactRouterHeadersALB(headers),
230
+ signal: controller.signal,
231
+ body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body || void 0
232
+ });
233
+ }
234
+ function createReactRouterHeadersALB(requestHeaders) {
235
+ const headers = new Headers();
236
+ for (const [header, value] of Object.entries(requestHeaders)) {
237
+ if (value) {
238
+ headers.append(header, value);
239
+ }
240
+ }
241
+ return headers;
242
+ }
243
+ async function sendReactRouterResponseALB(nodeResponse) {
244
+ const contentType = nodeResponse.headers.get("Content-Type");
245
+ const isBase64Encoded = isBinaryType(contentType);
246
+ let body;
247
+ if (nodeResponse.body) {
248
+ if (isBase64Encoded) {
249
+ body = await (0, import_node3.readableStreamToString)(nodeResponse.body, "base64");
250
+ } else {
251
+ body = await nodeResponse.text();
252
+ }
253
+ }
254
+ return {
255
+ statusCode: nodeResponse.status,
256
+ headers: Object.fromEntries(nodeResponse.headers.entries()),
257
+ body: body || "",
258
+ isBase64Encoded
259
+ };
260
+ }
261
+ var applicationLoadBalancerAdapter = {
262
+ createReactRouterRequest: createReactRouterRequestALB,
263
+ sendReactRouterResponse: sendReactRouterResponseALB
264
+ };
265
+
266
+ // src/adapters/index.ts
267
+ var createReactRouterAdapter = (awsProxy) => {
268
+ switch (awsProxy) {
269
+ case "APIGatewayV1" /* APIGatewayV1 */:
270
+ return apiGatewayV1Adapter;
271
+ case "APIGatewayV2" /* APIGatewayV2 */:
272
+ case "FunctionURL" /* FunctionURL */:
273
+ return apiGatewayV2Adapter;
274
+ case "ALB" /* ALB */:
275
+ return applicationLoadBalancerAdapter;
276
+ }
277
+ };
278
+
279
+ // src/server.ts
280
+ var AWSProxy = /* @__PURE__ */ ((AWSProxy2) => {
281
+ AWSProxy2["APIGatewayV1"] = "APIGatewayV1";
282
+ AWSProxy2["APIGatewayV2"] = "APIGatewayV2";
283
+ AWSProxy2["ALB"] = "ALB";
284
+ AWSProxy2["FunctionURL"] = "FunctionURL";
285
+ return AWSProxy2;
286
+ })(AWSProxy || {});
287
+ function createRequestHandler({
288
+ build,
289
+ getLoadContext,
290
+ mode = process.env.NODE_ENV,
291
+ awsProxy = "APIGatewayV2" /* APIGatewayV2 */
292
+ }) {
293
+ const handleRequest = (0, import_react_router.createRequestHandler)(build, mode);
294
+ return async (event) => {
295
+ const awsAdapter = createReactRouterAdapter(awsProxy);
296
+ let request;
297
+ try {
298
+ request = awsAdapter.createReactRouterRequest(event);
299
+ } catch (e) {
300
+ return awsAdapter.sendReactRouterResponse(
301
+ new Response(`Bad Request: ${e instanceof Error ? e.message : e}`, { status: 400 })
302
+ );
303
+ }
304
+ const loadContext = await getLoadContext?.(event);
305
+ const response = await handleRequest(request, loadContext);
306
+ return awsAdapter.sendReactRouterResponse(response);
307
+ };
308
+ }
309
+ // Annotate the CommonJS export names for ESM import in node:
310
+ 0 && (module.exports = {
311
+ AWSProxy,
312
+ createRequestHandler
313
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,286 @@
1
+ /**
2
+ * @geostrategists/react-router-aws v2.0.0
3
+ *
4
+ * Copyright (c) Geostrategists Consulting GmbH
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+
12
+ // src/server.ts
13
+ import { createRequestHandler as createReactRouterRequestHandler } from "react-router";
14
+
15
+ // src/adapters/api-gateway-v1.ts
16
+ import { readableStreamToString } from "@react-router/node";
17
+ import { URLSearchParams } from "url";
18
+
19
+ // src/binaryTypes.ts
20
+ var binaryTypes = [
21
+ "application/octet-stream",
22
+ // Docs
23
+ "application/epub+zip",
24
+ "application/msword",
25
+ "application/pdf",
26
+ "application/rtf",
27
+ "application/vnd.amazon.ebook",
28
+ "application/vnd.ms-excel",
29
+ "application/vnd.ms-powerpoint",
30
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
31
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
32
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
33
+ // Fonts
34
+ "font/otf",
35
+ "font/woff",
36
+ "font/woff2",
37
+ // Images
38
+ "image/avif",
39
+ "image/bmp",
40
+ "image/gif",
41
+ "image/jpeg",
42
+ "image/png",
43
+ "image/tiff",
44
+ "image/vnd.microsoft.icon",
45
+ "image/webp",
46
+ // Audio
47
+ "audio/3gpp",
48
+ "audio/aac",
49
+ "audio/basic",
50
+ "audio/mpeg",
51
+ "audio/ogg",
52
+ "audio/wav",
53
+ "audio/webm",
54
+ "audio/x-aiff",
55
+ "audio/x-midi",
56
+ "audio/x-wav",
57
+ // Video
58
+ "video/3gpp",
59
+ "video/mp2t",
60
+ "video/mpeg",
61
+ "video/ogg",
62
+ "video/quicktime",
63
+ "video/webm",
64
+ "video/x-msvideo",
65
+ // Archives
66
+ "application/java-archive",
67
+ "application/vnd.apple.installer+xml",
68
+ "application/x-7z-compressed",
69
+ "application/x-apple-diskimage",
70
+ "application/x-bzip",
71
+ "application/x-bzip2",
72
+ "application/x-gzip",
73
+ "application/x-java-archive",
74
+ "application/x-rar-compressed",
75
+ "application/x-tar",
76
+ "application/x-zip",
77
+ "application/zip"
78
+ ];
79
+ function isBinaryType(contentType) {
80
+ if (!contentType) return false;
81
+ const [test] = contentType.split(";");
82
+ return binaryTypes.includes(test);
83
+ }
84
+
85
+ // src/adapters/api-gateway-v1.ts
86
+ function createReactRouterRequestAPIGatewayV1(event) {
87
+ const host = event.headers["x-forwarded-host"] || event.headers.Host;
88
+ const scheme = event.headers["x-forwarded-proto"] || "http";
89
+ const rawQueryString = new URLSearchParams(event.queryStringParameters).toString();
90
+ const search = rawQueryString.length > 0 ? `?${rawQueryString}` : "";
91
+ const url = new URL(event.path + search, `${scheme}://${host}`);
92
+ const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
93
+ const controller = new AbortController();
94
+ return new Request(url.href, {
95
+ method: event.requestContext.httpMethod,
96
+ headers: createReactRouterHeadersAPIGatewayV1(event.headers),
97
+ signal: controller.signal,
98
+ body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body || void 0
99
+ });
100
+ }
101
+ function createReactRouterHeadersAPIGatewayV1(requestHeaders) {
102
+ const headers = new Headers();
103
+ for (const [header, value] of Object.entries(requestHeaders)) {
104
+ if (value) {
105
+ headers.append(header, value);
106
+ }
107
+ }
108
+ return headers;
109
+ }
110
+ async function sendReactRouterResponseAPIGatewayV1(nodeResponse) {
111
+ const contentType = nodeResponse.headers.get("Content-Type");
112
+ const isBase64Encoded = isBinaryType(contentType);
113
+ let body;
114
+ if (nodeResponse.body) {
115
+ if (isBase64Encoded) {
116
+ body = await readableStreamToString(nodeResponse.body, "base64");
117
+ } else {
118
+ body = await nodeResponse.text();
119
+ }
120
+ }
121
+ return {
122
+ statusCode: nodeResponse.status,
123
+ headers: Object.fromEntries(nodeResponse.headers.entries()),
124
+ body: body || "",
125
+ isBase64Encoded
126
+ };
127
+ }
128
+ var apiGatewayV1Adapter = {
129
+ createReactRouterRequest: createReactRouterRequestAPIGatewayV1,
130
+ sendReactRouterResponse: sendReactRouterResponseAPIGatewayV1
131
+ };
132
+
133
+ // src/adapters/api-gateway-v2.ts
134
+ import { readableStreamToString as readableStreamToString2 } from "@react-router/node";
135
+ function createReactRouterRequestAPIGateywayV2(event) {
136
+ const host = event.headers["x-forwarded-host"] || event.headers.host;
137
+ const search = event.rawQueryString.length ? `?${event.rawQueryString}` : "";
138
+ const scheme = event.headers["x-forwarded-proto"] || "http";
139
+ const url = new URL(event.rawPath + search, `${scheme}://${host}`);
140
+ const isFormData = event.headers["content-type"]?.includes("multipart/form-data");
141
+ const controller = new AbortController();
142
+ return new Request(url.href, {
143
+ method: event.requestContext.http.method,
144
+ headers: createReactRouterHeadersAPIGatewayV2(event.headers, event.cookies),
145
+ signal: controller.signal,
146
+ body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body
147
+ });
148
+ }
149
+ function createReactRouterHeadersAPIGatewayV2(requestHeaders, requestCookies) {
150
+ const headers = new Headers();
151
+ for (const [header, value] of Object.entries(requestHeaders)) {
152
+ if (value) {
153
+ headers.append(header, value);
154
+ }
155
+ }
156
+ if (requestCookies) {
157
+ headers.append("Cookie", requestCookies.join("; "));
158
+ }
159
+ return headers;
160
+ }
161
+ async function sendReactRouterResponseAPIGatewayV2(nodeResponse) {
162
+ const cookies = nodeResponse.headers.getSetCookie();
163
+ if (cookies.length) {
164
+ nodeResponse.headers.delete("Set-Cookie");
165
+ }
166
+ const contentType = nodeResponse.headers.get("Content-Type");
167
+ const isBase64Encoded = isBinaryType(contentType);
168
+ let body;
169
+ if (nodeResponse.body) {
170
+ if (isBase64Encoded) {
171
+ body = await readableStreamToString2(nodeResponse.body, "base64");
172
+ } else {
173
+ body = await nodeResponse.text();
174
+ }
175
+ }
176
+ return {
177
+ statusCode: nodeResponse.status,
178
+ headers: Object.fromEntries(nodeResponse.headers.entries()),
179
+ cookies,
180
+ body,
181
+ isBase64Encoded
182
+ };
183
+ }
184
+ var apiGatewayV2Adapter = {
185
+ createReactRouterRequest: createReactRouterRequestAPIGateywayV2,
186
+ sendReactRouterResponse: sendReactRouterResponseAPIGatewayV2
187
+ };
188
+
189
+ // src/adapters/application-load-balancer.ts
190
+ import { readableStreamToString as readableStreamToString3 } from "@react-router/node";
191
+ import { URLSearchParams as URLSearchParams2 } from "url";
192
+ function createReactRouterRequestALB(event) {
193
+ const headers = event?.headers || {};
194
+ const host = headers["x-forwarded-host"] || headers.Host;
195
+ const scheme = headers["x-forwarded-proto"] || "http";
196
+ const rawQueryString = new URLSearchParams2(event.queryStringParameters).toString();
197
+ const search = rawQueryString.length > 0 ? `?${rawQueryString}` : "";
198
+ const url = new URL(event.path + search, `${scheme}://${host}`);
199
+ const isFormData = headers["content-type"]?.includes("multipart/form-data");
200
+ const controller = new AbortController();
201
+ return new Request(url.href, {
202
+ method: event.httpMethod,
203
+ headers: createReactRouterHeadersALB(headers),
204
+ signal: controller.signal,
205
+ body: event.body && event.isBase64Encoded ? isFormData ? Buffer.from(event.body, "base64") : Buffer.from(event.body, "base64").toString() : event.body || void 0
206
+ });
207
+ }
208
+ function createReactRouterHeadersALB(requestHeaders) {
209
+ const headers = new Headers();
210
+ for (const [header, value] of Object.entries(requestHeaders)) {
211
+ if (value) {
212
+ headers.append(header, value);
213
+ }
214
+ }
215
+ return headers;
216
+ }
217
+ async function sendReactRouterResponseALB(nodeResponse) {
218
+ const contentType = nodeResponse.headers.get("Content-Type");
219
+ const isBase64Encoded = isBinaryType(contentType);
220
+ let body;
221
+ if (nodeResponse.body) {
222
+ if (isBase64Encoded) {
223
+ body = await readableStreamToString3(nodeResponse.body, "base64");
224
+ } else {
225
+ body = await nodeResponse.text();
226
+ }
227
+ }
228
+ return {
229
+ statusCode: nodeResponse.status,
230
+ headers: Object.fromEntries(nodeResponse.headers.entries()),
231
+ body: body || "",
232
+ isBase64Encoded
233
+ };
234
+ }
235
+ var applicationLoadBalancerAdapter = {
236
+ createReactRouterRequest: createReactRouterRequestALB,
237
+ sendReactRouterResponse: sendReactRouterResponseALB
238
+ };
239
+
240
+ // src/adapters/index.ts
241
+ var createReactRouterAdapter = (awsProxy) => {
242
+ switch (awsProxy) {
243
+ case "APIGatewayV1" /* APIGatewayV1 */:
244
+ return apiGatewayV1Adapter;
245
+ case "APIGatewayV2" /* APIGatewayV2 */:
246
+ case "FunctionURL" /* FunctionURL */:
247
+ return apiGatewayV2Adapter;
248
+ case "ALB" /* ALB */:
249
+ return applicationLoadBalancerAdapter;
250
+ }
251
+ };
252
+
253
+ // src/server.ts
254
+ var AWSProxy = /* @__PURE__ */ ((AWSProxy2) => {
255
+ AWSProxy2["APIGatewayV1"] = "APIGatewayV1";
256
+ AWSProxy2["APIGatewayV2"] = "APIGatewayV2";
257
+ AWSProxy2["ALB"] = "ALB";
258
+ AWSProxy2["FunctionURL"] = "FunctionURL";
259
+ return AWSProxy2;
260
+ })(AWSProxy || {});
261
+ function createRequestHandler({
262
+ build,
263
+ getLoadContext,
264
+ mode = process.env.NODE_ENV,
265
+ awsProxy = "APIGatewayV2" /* APIGatewayV2 */
266
+ }) {
267
+ const handleRequest = createReactRouterRequestHandler(build, mode);
268
+ return async (event) => {
269
+ const awsAdapter = createReactRouterAdapter(awsProxy);
270
+ let request;
271
+ try {
272
+ request = awsAdapter.createReactRouterRequest(event);
273
+ } catch (e) {
274
+ return awsAdapter.sendReactRouterResponse(
275
+ new Response(`Bad Request: ${e instanceof Error ? e.message : e}`, { status: 400 })
276
+ );
277
+ }
278
+ const loadContext = await getLoadContext?.(event);
279
+ const response = await handleRequest(request, loadContext);
280
+ return awsAdapter.sendReactRouterResponse(response);
281
+ };
282
+ }
283
+ export {
284
+ AWSProxy,
285
+ createRequestHandler
286
+ };
@@ -0,0 +1,12 @@
1
+ // @ts-check
2
+
3
+ import eslint from "@eslint/js";
4
+ import tseslint from "typescript-eslint";
5
+
6
+ export default tseslint.config(
7
+ {
8
+ ignores: ["dist"],
9
+ },
10
+ eslint.configs.recommended,
11
+ tseslint.configs.recommended,
12
+ );
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "@geostrategists/react-router-aws",
3
+ "version": "2.0.0",
4
+ "description": "AWS adapter for React Router v7",
5
+ "license": "MIT",
6
+ "bugs": {
7
+ "url": "https://github.com/geostrategists/react-router-aws/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/geostrategists/react-router-aws.git"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "node": {
16
+ "types": "./dist/index.d.ts",
17
+ "module-sync": "./dist/index.mjs",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "import": {
21
+ "types": "./dist/index.d.mts",
22
+ "default": "./dist/index.mjs"
23
+ },
24
+ "default": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ }
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
31
+ "scripts": {
32
+ "lint": "eslint .",
33
+ "format": "prettier --write .",
34
+ "format:check": "prettier --check .",
35
+ "typecheck": "tsc --noEmit",
36
+ "build": "tsc && tsup",
37
+ "prepack": "yarn build && yarn npmignore --auto",
38
+ "prepare": "husky",
39
+ "release": "semantic-release"
40
+ },
41
+ "peerDependencies": {
42
+ "@react-router/dev": "^7.3.0",
43
+ "@react-router/node": "^7.3.0",
44
+ "react-router": "^7.3.0"
45
+ },
46
+ "devDependencies": {
47
+ "@eslint/js": "^9.22.0",
48
+ "@react-router/dev": "^7.3.0",
49
+ "@react-router/node": "^7.3.0",
50
+ "@semantic-release/exec": "^7.0.3",
51
+ "@types/aws-lambda": "^8.10.125",
52
+ "@types/node": "^20",
53
+ "eslint": "^9.22.0",
54
+ "husky": "^9.1.7",
55
+ "lint-staged": "^15.5.0",
56
+ "npmignore": "^0.3.1",
57
+ "prettier": "3.5.3",
58
+ "react-router": "^7.3.0",
59
+ "semantic-release": "^24.2.3",
60
+ "tsup": "^8.4.0",
61
+ "typescript": "^5.8.2",
62
+ "typescript-eslint": "^8.26.1"
63
+ },
64
+ "packageManager": "yarn@4.7.0+sha256.293632d8a095d8ea4786eb2c5798c83c37544abed17ed31186a3ec4549a07c06",
65
+ "release": {
66
+ "plugins": [
67
+ "@semantic-release/commit-analyzer",
68
+ "@semantic-release/release-notes-generator",
69
+ [
70
+ "@semantic-release/exec",
71
+ {
72
+ "verifyConditionsCmd": "echo //npm.pkg.github.com/:_authToken=${process.env.GITHUB_TOKEN} > /tmp/github.npmrc && npm whoami --userconfig /tmp/github.npmrc --registry https://npm.pkg.github.com/",
73
+ "publishCmd": "npm publish --userconfig /tmp/github.npmrc --tag ${nextRelease.channel || 'latest'} --registry https://npm.pkg.github.com/ --no-git-tag-version",
74
+ "successCmd": "rm /tmp/github.npmrc",
75
+ "failCmd": "rm /tmp/github.npmrc"
76
+ }
77
+ ],
78
+ "@semantic-release/npm",
79
+ "@semantic-release/github"
80
+ ]
81
+ },
82
+ "publishConfig": {
83
+ "ignore": [
84
+ "!dist/**",
85
+ "src/",
86
+ ".*",
87
+ "tsconfig.json",
88
+ "tsup.config.ts"
89
+ ],
90
+ "access": "public",
91
+ "provenance": true
92
+ },
93
+ "lint-staged": {
94
+ "*.{ts,tsx,js,cjs,mjs}": [
95
+ "eslint --fix",
96
+ "prettier --write"
97
+ ],
98
+ "!(*.ts|*.tsx|*.js|*.cjs|*.mjs)": "prettier --ignore-unknown --write"
99
+ },
100
+ "prettier": {
101
+ "printWidth": 120,
102
+ "trailingComma": "all"
103
+ }
104
+ }