From 577b1281e18a4a0145901161de05eb28e8626221 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Tue, 16 Jul 2024 15:34:16 +1000 Subject: [PATCH] Port Remix Architect package --- packages/react-router-architect/README.md | 7 + .../__tests__/554828.jpeg | Bin 0 -> 26669 bytes .../__tests__/binaryTypes-test.ts | 11 + .../__tests__/server-test.ts | 288 ++++++++++++++++++ .../react-router-architect/__tests__/setup.ts | 2 + .../react-router-architect/binaryTypes.ts | 69 +++++ packages/react-router-architect/index.ts | 4 + .../react-router-architect/jest.config.js | 5 + packages/react-router-architect/package.json | 59 ++++ .../react-router-architect/rollup.config.js | 55 ++++ packages/react-router-architect/server.ts | 134 ++++++++ .../sessions/arcTableSessionStorage.ts | 121 ++++++++ packages/react-router-architect/tsconfig.json | 20 ++ pnpm-lock.yaml | 183 ++++++++++- rollup.config.js | 1 + tsconfig.json | 1 + 16 files changed, 958 insertions(+), 2 deletions(-) create mode 100644 packages/react-router-architect/README.md create mode 100644 packages/react-router-architect/__tests__/554828.jpeg create mode 100644 packages/react-router-architect/__tests__/binaryTypes-test.ts create mode 100644 packages/react-router-architect/__tests__/server-test.ts create mode 100644 packages/react-router-architect/__tests__/setup.ts create mode 100644 packages/react-router-architect/binaryTypes.ts create mode 100644 packages/react-router-architect/index.ts create mode 100644 packages/react-router-architect/jest.config.js create mode 100644 packages/react-router-architect/package.json create mode 100644 packages/react-router-architect/rollup.config.js create mode 100644 packages/react-router-architect/server.ts create mode 100644 packages/react-router-architect/sessions/arcTableSessionStorage.ts create mode 100644 packages/react-router-architect/tsconfig.json diff --git a/packages/react-router-architect/README.md b/packages/react-router-architect/README.md new file mode 100644 index 0000000000..7180258125 --- /dev/null +++ b/packages/react-router-architect/README.md @@ -0,0 +1,7 @@ +# React Router Architect + +Architect server request handler for React Router. + +```bash +npm install @react-router/architect +``` diff --git a/packages/react-router-architect/__tests__/554828.jpeg b/packages/react-router-architect/__tests__/554828.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..830d2dbb3c21d97f2cab8fbb0a7e0694a9b03837 GIT binary patch literal 26669 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

-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 X_kvxU!uFt?;2c*&K!S=Wpb!7qb{GU- literal 0 HcmV?d00001 diff --git a/packages/react-router-architect/__tests__/binaryTypes-test.ts b/packages/react-router-architect/__tests__/binaryTypes-test.ts new file mode 100644 index 0000000000..1418856d48 --- /dev/null +++ b/packages/react-router-architect/__tests__/binaryTypes-test.ts @@ -0,0 +1,11 @@ +import { isBinaryType } from "../binaryTypes"; + +describe("architect isBinaryType", () => { + it("should detect binary contentType correctly", () => { + expect(isBinaryType(undefined)).toBe(false); + expect(isBinaryType(null)).toBe(false); + expect(isBinaryType("text/html; charset=utf-8")).toBe(false); + expect(isBinaryType("application/octet-stream")).toBe(true); + expect(isBinaryType("application/octet-stream; charset=test")).toBe(true); + }); +}); diff --git a/packages/react-router-architect/__tests__/server-test.ts b/packages/react-router-architect/__tests__/server-test.ts new file mode 100644 index 0000000000..bc818e1c46 --- /dev/null +++ b/packages/react-router-architect/__tests__/server-test.ts @@ -0,0 +1,288 @@ +import fsp from "node:fs/promises"; +import path from "node:path"; +import { createRequestHandler as createReactRequestHandler } from "react-router"; +import type { + APIGatewayProxyEventV2, + APIGatewayProxyStructuredResultV2, +} from "aws-lambda"; +import lambdaTester from "lambda-tester"; + +import { + createRequestHandler, + createReactRouterHeaders, + createReactRouterRequest, + sendReactRouterResponse, +} from "../server"; + +// We don't want to test that the React Router server works here, +// we just want to test the architect adapter +jest.mock("react-router", () => { + let original = jest.requireActual("react-router"); + return { + ...original, + createRequestHandler: jest.fn(), + }; +}); +let mockedCreateRequestHandler = + createReactRequestHandler as jest.MockedFunction< + typeof createReactRequestHandler + >; + +function createMockEvent(event: Partial = {}) { + let now = new Date(); + return { + headers: { + host: "localhost:3333", + accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "upgrade-insecure-requests": "1", + "user-agent": + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15", + "accept-language": "en-US,en;q=0.9", + "accept-encoding": "gzip, deflate", + ...event.headers, + }, + isBase64Encoded: false, + rawPath: "/", + rawQueryString: "", + requestContext: { + http: { + method: "GET", + path: "/", + protocol: "HTTP/1.1", + userAgent: + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15", + sourceIp: "127.0.0.1", + ...event.requestContext?.http, + }, + routeKey: "ANY /{proxy+}", + accountId: "accountId", + requestId: "requestId", + apiId: "apiId", + domainName: "id.execute-api.us-east-1.amazonaws.com", + domainPrefix: "id", + stage: "test", + time: now.toISOString(), + timeEpoch: now.getTime(), + ...event.requestContext, + }, + routeKey: "foo", + version: "2.0", + ...event, + }; +} + +describe("architect createRequestHandler", () => { + describe("basic requests", () => { + afterEach(() => { + mockedCreateRequestHandler.mockReset(); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it("handles requests", async () => { + mockedCreateRequestHandler.mockImplementation(() => async (req) => { + return new Response(`URL: ${new URL(req.url).pathname}`); + }); + + // We don't have a real app to test, but it doesn't matter. We won't ever + // call through to the real createRequestHandler + // @ts-expect-error + await lambdaTester(createRequestHandler({ build: undefined })) + .event(createMockEvent({ rawPath: "/foo/bar" })) + .expectResolve((res: any) => { + expect(res.statusCode).toBe(200); + expect(res.body).toBe("URL: /foo/bar"); + }); + }); + + it("handles root // requests", async () => { + mockedCreateRequestHandler.mockImplementation(() => async (req) => { + return new Response(`URL: ${new URL(req.url).pathname}`); + }); + + // We don't have a real app to test, but it doesn't matter. We won't ever + // call through to the real createRequestHandler + // @ts-expect-error + await lambdaTester(createRequestHandler({ build: undefined })) + .event(createMockEvent({ rawPath: "//" })) + .expectResolve((res: any) => { + expect(res.statusCode).toBe(200); + expect(res.body).toBe("URL: //"); + }); + }); + + it("handles nested // requests", async () => { + mockedCreateRequestHandler.mockImplementation(() => async (req) => { + return new Response(`URL: ${new URL(req.url).pathname}`); + }); + + // We don't have a real app to test, but it doesn't matter. We won't ever + // call through to the real createRequestHandler + // @ts-expect-error + await lambdaTester(createRequestHandler({ build: undefined })) + .event(createMockEvent({ rawPath: "//foo//bar" })) + .expectResolve((res: APIGatewayProxyStructuredResultV2) => { + expect(res.statusCode).toBe(200); + expect(res.body).toBe("URL: //foo//bar"); + }); + }); + + it("handles null body", async () => { + mockedCreateRequestHandler.mockImplementation(() => async () => { + return new Response(null, { status: 200 }); + }); + + // We don't have a real app to test, but it doesn't matter. We won't ever + // call through to the real createRequestHandler + // @ts-expect-error + await lambdaTester(createRequestHandler({ build: undefined })) + .event(createMockEvent({ rawPath: "/foo/bar" })) + .expectResolve((res: APIGatewayProxyStructuredResultV2) => { + expect(res.statusCode).toBe(200); + }); + }); + + it("handles status codes", async () => { + mockedCreateRequestHandler.mockImplementation(() => async () => { + return new Response(null, { status: 204 }); + }); + + // We don't have a real app to test, but it doesn't matter. We won't ever + // call through to the real createRequestHandler + // @ts-expect-error + await lambdaTester(createRequestHandler({ build: undefined })) + .event(createMockEvent({ rawPath: "/foo/bar" })) + .expectResolve((res: APIGatewayProxyStructuredResultV2) => { + expect(res.statusCode).toBe(204); + }); + }); + + it("sets headers", async () => { + mockedCreateRequestHandler.mockImplementation(() => async () => { + let headers = new Headers(); + headers.append("X-Time-Of-Year", "most wonderful"); + headers.append( + "Set-Cookie", + "first=one; Expires=0; Path=/; HttpOnly; Secure; SameSite=Lax" + ); + headers.append( + "Set-Cookie", + "second=two; MaxAge=1209600; Path=/; HttpOnly; Secure; SameSite=Lax" + ); + headers.append( + "Set-Cookie", + "third=three; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Path=/; HttpOnly; Secure; SameSite=Lax" + ); + + return new Response(null, { headers }); + }); + + // We don't have a real app to test, but it doesn't matter. We won't ever + // call through to the real createRequestHandler + // @ts-expect-error + await lambdaTester(createRequestHandler({ build: undefined })) + .event(createMockEvent({ rawPath: "/" })) + .expectResolve((res: APIGatewayProxyStructuredResultV2) => { + expect(res.statusCode).toBe(200); + expect(res.headers?.["x-time-of-year"]).toBe("most wonderful"); + expect(res.cookies).toEqual([ + "first=one; Expires=0; Path=/; HttpOnly; Secure; SameSite=Lax", + "second=two; MaxAge=1209600; Path=/; HttpOnly; Secure; SameSite=Lax", + "third=three; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Path=/; HttpOnly; Secure; SameSite=Lax", + ]); + }); + }); + }); +}); + +describe("architect createReactRouterHeaders", () => { + describe("creates fetch headers from architect headers", () => { + it("handles empty headers", () => { + let headers = createReactRouterHeaders({}); + expect(Object.fromEntries(headers.entries())).toMatchInlineSnapshot(`{}`); + }); + + it("handles simple headers", () => { + let headers = createReactRouterHeaders({ "x-foo": "bar" }); + expect(headers.get("x-foo")).toBe("bar"); + }); + + it("handles multiple headers", () => { + let headers = createReactRouterHeaders({ + "x-foo": "bar", + "x-bar": "baz", + }); + expect(headers.get("x-foo")).toBe("bar"); + expect(headers.get("x-bar")).toBe("baz"); + }); + + it("handles headers with multiple values", () => { + let headers = createReactRouterHeaders({ + "x-foo": "bar, baz", + "x-bar": "baz", + }); + expect(headers.get("x-foo")).toEqual("bar, baz"); + expect(headers.get("x-bar")).toBe("baz"); + }); + + it("handles multiple request cookies", () => { + let headers = createReactRouterHeaders({}, [ + "__session=some_value", + "__other=some_other_value", + ]); + expect(headers.get("cookie")).toEqual( + "__session=some_value; __other=some_other_value" + ); + }); + }); +}); + +describe("architect createReactRouterRequest", () => { + it("creates a request with the correct headers", () => { + let request = createReactRouterRequest( + createMockEvent({ cookies: ["__session=value"] }) + ); + + expect(request.method).toBe("GET"); + expect(request.headers.get("cookie")).toBe("__session=value"); + }); +}); + +describe("sendReactRouterResponse", () => { + it("handles regular responses", async () => { + let response = new Response("anything"); + let result = await sendReactRouterResponse(response); + expect(result.body).toBe("anything"); + }); + + it("handles resource routes with regular data", async () => { + let json = JSON.stringify({ foo: "bar" }); + let response = new Response(json, { + headers: { + "Content-Type": "application/json", + "content-length": json.length.toString(), + }, + }); + + let result = await sendReactRouterResponse(response); + + expect(result.body).toMatch(json); + }); + + it("handles resource routes with binary data", async () => { + let image = await fsp.readFile(path.join(__dirname, "554828.jpeg")); + + let response = new Response(image, { + headers: { + "content-type": "image/jpeg", + "content-length": image.length.toString(), + }, + }); + + let result = await sendReactRouterResponse(response); + + expect(result.body).toMatch(image.toString("base64")); + }); +}); diff --git a/packages/react-router-architect/__tests__/setup.ts b/packages/react-router-architect/__tests__/setup.ts new file mode 100644 index 0000000000..996e99893d --- /dev/null +++ b/packages/react-router-architect/__tests__/setup.ts @@ -0,0 +1,2 @@ +import { installGlobals } from "@react-router/node"; +installGlobals(); diff --git a/packages/react-router-architect/binaryTypes.ts b/packages/react-router-architect/binaryTypes.ts new file mode 100644 index 0000000000..fee3d9619b --- /dev/null +++ b/packages/react-router-architect/binaryTypes.ts @@ -0,0 +1,69 @@ +/** + * Common binary MIME types + * @see https://github.com/architect/functions/blob/45254fc1936a1794c185aac07e9889b241a2e5c6/src/http/helpers/binary-types.js + */ +const binaryTypes = [ + "application/octet-stream", + // Docs + "application/epub+zip", + "application/msword", + "application/pdf", + "application/rtf", + "application/vnd.amazon.ebook", + "application/vnd.ms-excel", + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + // Fonts + "font/otf", + "font/woff", + "font/woff2", + // Images + "image/avif", + "image/bmp", + "image/gif", + "image/jpeg", + "image/png", + "image/tiff", + "image/vnd.microsoft.icon", + "image/webp", + // Audio + "audio/3gpp", + "audio/aac", + "audio/basic", + "audio/mpeg", + "audio/ogg", + "audio/wav", + "audio/webm", + "audio/x-aiff", + "audio/x-midi", + "audio/x-wav", + // Video + "video/3gpp", + "video/mp2t", + "video/mpeg", + "video/ogg", + "video/quicktime", + "video/webm", + "video/x-msvideo", + // Archives + "application/java-archive", + "application/vnd.apple.installer+xml", + "application/x-7z-compressed", + "application/x-apple-diskimage", + "application/x-bzip", + "application/x-bzip2", + "application/x-gzip", + "application/x-java-archive", + "application/x-rar-compressed", + "application/x-tar", + "application/x-zip", + "application/zip", +]; + +export function isBinaryType(contentType: string | null | undefined) { + if (!contentType) return false; + let [test] = contentType.split(";"); + return binaryTypes.includes(test); +} diff --git a/packages/react-router-architect/index.ts b/packages/react-router-architect/index.ts new file mode 100644 index 0000000000..c66f26f567 --- /dev/null +++ b/packages/react-router-architect/index.ts @@ -0,0 +1,4 @@ +export { createArcTableSessionStorage } from "./sessions/arcTableSessionStorage"; + +export type { GetLoadContextFunction, RequestHandler } from "./server"; +export { createRequestHandler } from "./server"; diff --git a/packages/react-router-architect/jest.config.js b/packages/react-router-architect/jest.config.js new file mode 100644 index 0000000000..94bdf00603 --- /dev/null +++ b/packages/react-router-architect/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('jest').Config} */ +module.exports = { + ...require("../../jest/jest.config.shared"), + displayName: "architect", +}; diff --git a/packages/react-router-architect/package.json b/packages/react-router-architect/package.json new file mode 100644 index 0000000000..dab257ba9b --- /dev/null +++ b/packages/react-router-architect/package.json @@ -0,0 +1,59 @@ +{ + "name": "@react-router/architect", + "version": "2.9.0-pre.0", + "description": "Architect server request handler for React Router", + "bugs": { + "url": "https://github.com/remix-run/react-router/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/remix-run/react-router", + "directory": "packages/react-router-architect" + }, + "license": "MIT", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "scripts": { + "tsc": "tsc" + }, + "dependencies": { + "@architect/functions": "^5.2.0", + "@types/aws-lambda": "^8.10.82" + }, + "devDependencies": { + "@react-router/node": "workspace:*", + "@types/lambda-tester": "^3.6.1", + "@types/node": "^18.17.1", + "lambda-tester": "^4.0.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router": "workspace:*", + "typescript": "^5.1.0" + }, + "peerDependencies": { + "@react-router/node": "workspace:^", + "react-router": "workspace:^", + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + }, + "engines": { + "node": ">=18.0.0" + }, + "files": [ + "dist/", + "CHANGELOG.md", + "LICENSE.md", + "README.md" + ] +} diff --git a/packages/react-router-architect/rollup.config.js b/packages/react-router-architect/rollup.config.js new file mode 100644 index 0000000000..4e48ff954e --- /dev/null +++ b/packages/react-router-architect/rollup.config.js @@ -0,0 +1,55 @@ +const path = require("node:path"); + +const babel = require("@rollup/plugin-babel").default; +const nodeResolve = require("@rollup/plugin-node-resolve").default; +const typescript = require("@rollup/plugin-typescript"); +const copy = require("rollup-plugin-copy"); + +const { + isBareModuleId, + getBuildDirectories, + createBanner, + WATCH, +} = require("../../rollup.utils"); +const { name: packageName, version } = require("./package.json"); + +/** @returns {import("rollup").RollupOptions[]} */ +module.exports = function rollup() { + const { SOURCE_DIR, OUTPUT_DIR } = getBuildDirectories( + packageName, + // We don't live in a folder matching our package name + "react-router-architect" + ); + + return [ + { + external(id) { + return isBareModuleId(id); + }, + input: `${SOURCE_DIR}/index.ts`, + output: { + banner: createBanner(packageName, version), + dir: OUTPUT_DIR, + format: "cjs", + preserveModules: true, + exports: "auto", + }, + plugins: [ + babel({ + babelHelpers: "bundled", + exclude: /node_modules/, + extensions: [".ts"], + }), + typescript({ + tsconfig: path.join(__dirname, "tsconfig.json"), + exclude: ["__tests__"], + noEmitOnError: !WATCH, + }), + nodeResolve({ extensions: [".ts"] }), + copy({ + targets: [{ src: "LICENSE.md", dest: SOURCE_DIR }], + }), + ], + }, + ]; +}; diff --git a/packages/react-router-architect/server.ts b/packages/react-router-architect/server.ts new file mode 100644 index 0000000000..bf342dfccd --- /dev/null +++ b/packages/react-router-architect/server.ts @@ -0,0 +1,134 @@ +import type { AppLoadContext, ServerBuild } from "react-router"; +import { createRequestHandler as createReactRouterRequestHandler } from "react-router"; +import { readableStreamToString } from "@react-router/node"; +import type { + APIGatewayProxyEventHeaders, + APIGatewayProxyEventV2, + APIGatewayProxyHandlerV2, + APIGatewayProxyStructuredResultV2, +} from "aws-lambda"; + +import { isBinaryType } from "./binaryTypes"; + +/** + * A function that returns the value to use as `context` in route `loader` and + * `action` functions. + * + * You can think of this as an escape hatch that allows you to pass + * environment/platform-specific values through to your loader/action. + */ +export type GetLoadContextFunction = ( + event: APIGatewayProxyEventV2 +) => Promise | AppLoadContext; + +export type RequestHandler = APIGatewayProxyHandlerV2; + +/** + * Returns a request handler for Architect that serves the response using + * React Router. + */ +export function createRequestHandler({ + build, + getLoadContext, + mode = process.env.NODE_ENV, +}: { + build: ServerBuild; + getLoadContext?: GetLoadContextFunction; + mode?: string; +}): RequestHandler { + let handleRequest = createReactRouterRequestHandler(build, mode); + + return async (event) => { + let request = createReactRouterRequest(event); + let loadContext = await getLoadContext?.(event); + + let response = await handleRequest(request, loadContext); + + return sendReactRouterResponse(response); + }; +} + +export function createReactRouterRequest( + event: APIGatewayProxyEventV2 +): Request { + let host = event.headers["x-forwarded-host"] || event.headers.host; + let search = event.rawQueryString.length ? `?${event.rawQueryString}` : ""; + let scheme = process.env.ARC_SANDBOX ? "http" : "https"; + let url = new URL(`${scheme}://${host}${event.rawPath}${search}`); + let isFormData = event.headers["content-type"]?.includes( + "multipart/form-data" + ); + // Note: No current way to abort these for Architect, but our router expects + // requests to contain a signal, so it can detect aborted requests + let controller = new AbortController(); + + return new Request(url.href, { + method: event.requestContext.http.method, + headers: createReactRouterHeaders(event.headers, event.cookies), + signal: controller.signal, + body: + event.body && event.isBase64Encoded + ? isFormData + ? Buffer.from(event.body, "base64") + : Buffer.from(event.body, "base64").toString() + : event.body, + }); +} + +export function createReactRouterHeaders( + requestHeaders: APIGatewayProxyEventHeaders, + requestCookies?: string[] +): Headers { + let headers = new Headers(); + + for (let [header, value] of Object.entries(requestHeaders)) { + if (value) { + headers.append(header, value); + } + } + + if (requestCookies) { + for (let cookie of requestCookies) { + headers.append("Cookie", cookie); + } + } + + return headers; +} + +export async function sendReactRouterResponse( + nodeResponse: Response +): Promise { + let cookies: string[] = []; + + // Arc/AWS API Gateway will send back set-cookies outside of response headers. + for (let [key, value] of nodeResponse.headers.entries()) { + if (key.toLowerCase() === "set-cookie") { + cookies.push(value); + } + } + + if (cookies.length) { + nodeResponse.headers.delete("Set-Cookie"); + } + + let contentType = nodeResponse.headers.get("Content-Type"); + let isBase64Encoded = isBinaryType(contentType); + let body: string | undefined; + + if (nodeResponse.body) { + if (isBase64Encoded) { + body = await readableStreamToString(nodeResponse.body, "base64"); + } else { + body = await nodeResponse.text(); + } + } + + return { + statusCode: nodeResponse.status, + headers: Object.fromEntries(nodeResponse.headers.entries()), + cookies, + body, + isBase64Encoded, + }; +} diff --git a/packages/react-router-architect/sessions/arcTableSessionStorage.ts b/packages/react-router-architect/sessions/arcTableSessionStorage.ts new file mode 100644 index 0000000000..40988a4b2f --- /dev/null +++ b/packages/react-router-architect/sessions/arcTableSessionStorage.ts @@ -0,0 +1,121 @@ +import * as crypto from "node:crypto"; +import type { + SessionData, + SessionStorage, + SessionIdStorageStrategy, +} from "react-router"; +import { createSessionStorage } from "@react-router/node"; +import arc from "@architect/functions"; +import type { ArcTable } from "@architect/functions/types/tables"; + +interface ArcTableSessionStorageOptions { + /** + * The Cookie used to store the session id on the client, or options used + * to automatically create one. + */ + cookie?: SessionIdStorageStrategy["cookie"]; + + /** + * The table used to store sessions, or its name as it appears in your + * project's app.arc file. + */ + table: ArcTable | string; + + /** + * The name of the DynamoDB attribute used to store the session ID. + * This should be the table's partition key. + */ + idx: string; + + /** + * The name of the DynamoDB attribute used to store the expiration time. + * If absent, then no TTL will be stored and session records will not expire. + */ + ttl?: string; +} + +/** + * Session storage using a DynamoDB table managed by Architect. + * + * Add the following lines to your project's `app.arc` file: + * + * @tables + * arc-sessions + * _idx *String + * _ttl TTL + */ +export function createArcTableSessionStorage< + Data = SessionData, + FlashData = Data +>({ + cookie, + ...props +}: ArcTableSessionStorageOptions): SessionStorage { + async function getTable() { + if (typeof props.table === "string") { + let tables = await arc.tables(); + return tables[props.table]; + } else { + return props.table; + } + } + return createSessionStorage({ + cookie, + async createData(data, expires) { + let table = await getTable(); + while (true) { + let randomBytes = crypto.randomBytes(8); + // This storage manages an id space of 2^64 ids, which is far greater + // than the maximum number of files allowed on an NTFS or ext4 volume + // (2^32). However, the larger id space should help to avoid collisions + // with existing ids when creating new sessions, which speeds things up. + let id = [...randomBytes] + .map((x) => x.toString(16).padStart(2, "0")) + .join(""); + + if (await table.get({ [props.idx]: id })) { + continue; + } + + let params: Record = { + [props.idx]: id, + ...data, + }; + if (props.ttl) { + params[props.ttl] = expires + ? Math.round(expires.getTime() / 1000) + : undefined; + } + await table.put(params); + + return id; + } + }, + async readData(id) { + let table = await getTable(); + let data = await table.get({ [props.idx]: id }); + if (data) { + delete data[props.idx]; + if (props.ttl) delete data[props.ttl]; + } + return data; + }, + async updateData(id, data, expires) { + let table = await getTable(); + let params: Record = { + [props.idx]: id, + ...data, + }; + if (props.ttl) { + params[props.ttl] = expires + ? Math.round(expires.getTime() / 1000) + : undefined; + } + await table.put(params); + }, + async deleteData(id) { + let table = await getTable(); + await table.delete({ [props.idx]: id }); + }, + }); +} diff --git a/packages/react-router-architect/tsconfig.json b/packages/react-router-architect/tsconfig.json new file mode 100644 index 0000000000..4a4e39de83 --- /dev/null +++ b/packages/react-router-architect/tsconfig.json @@ -0,0 +1,20 @@ +{ + "include": ["**/*.ts"], + "exclude": ["dist", "node_modules"], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "target": "ES2022", + + "moduleResolution": "Bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "declaration": true, + "emitDeclarationOnly": true, + "rootDir": ".", + "outDir": "./dist", + + // Avoid naming conflicts between history and react-router-dom relying on + // lib.dom.d.ts Window and this being a WebWorker env. + "skipLibCheck": true + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a87aa62b5..89287bf457 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -554,6 +554,40 @@ importers: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + packages/react-router-architect: + dependencies: + '@architect/functions': + specifier: ^5.2.0 + version: 5.4.1 + '@types/aws-lambda': + specifier: ^8.10.82 + version: 8.10.141 + devDependencies: + '@react-router/node': + specifier: workspace:* + version: link:../react-router-node + '@types/lambda-tester': + specifier: ^3.6.1 + version: 3.6.2 + '@types/node': + specifier: ^18.17.1 + version: 18.19.26 + lambda-tester: + specifier: ^4.0.1 + version: 4.0.1 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + react-router: + specifier: workspace:* + version: link:../react-router + typescript: + specifier: ^5.1.0 + version: 5.4.5 + packages/react-router-cloudflare: devDependencies: '@cloudflare/workers-types': @@ -994,6 +1028,19 @@ packages: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + /@architect/functions@5.4.1: + resolution: {integrity: sha512-F13FBUvVHjerUaSdnXIC3pZUnI10lxyVZ7HsrU8vM2qB5pFNqw5EgqcnVgORz/eqCcqevpIKayQ9yOUf/HuBAA==} + engines: {node: '>=12'} + dependencies: + cookie: 0.5.0 + cookie-signature: 1.2.1 + csrf: 3.1.0 + node-webtokens: 1.0.4 + run-parallel: 1.2.0 + run-waterfall: 1.1.7 + uid-safe: 2.1.5 + dev: false + /@babel/code-frame@7.22.5: resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} engines: {node: '>=6.9.0'} @@ -4361,6 +4408,11 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@extra-number/significant-digits@1.3.9: + resolution: {integrity: sha512-E5PY/bCwrNqEHh4QS6AQBinLZ+sxM1lT8tsSVYk8VwhWIPp6fCU/BMRVq0V8iJ8LwS3FHmaA4vUzb78s4BIIyA==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + dev: true + /@fastify/busboy@2.1.1: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} @@ -5338,6 +5390,9 @@ packages: resolution: {integrity: sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==} dev: false + /@types/aws-lambda@8.10.141: + resolution: {integrity: sha512-SMWlRBukG9KV8ZNjwemp2AzDibp/czIAeKKTw09nCPbWxVskIxactCJCGOp4y6I1hCMY7T7UGfySvBLXNeUbEw==} + /@types/babel__core@7.20.5: resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: @@ -5539,6 +5594,12 @@ packages: '@types/node': 18.19.26 dev: false + /@types/lambda-tester@3.6.2: + resolution: {integrity: sha512-nQRUx0AuvTq5KOz1SaxMOOFJvnybo1oAzvSy/p9bVGthZVvg1Dar/051mhMuzdN1DWg++bs+eGq1MdCbm6wjSQ==} + dependencies: + '@types/aws-lambda': 8.10.141 + dev: true + /@types/lodash@4.17.0: resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} dev: true @@ -6220,6 +6281,11 @@ packages: normalize-path: 3.0.0 picomatch: 2.3.1 + /app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + dev: true + /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -7049,6 +7115,15 @@ packages: wrap-ansi: 7.0.0 dev: false + /clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + dev: true + /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -7256,6 +7331,15 @@ packages: shebang-command: 2.0.0 which: 2.0.2 + /csrf@3.1.0: + resolution: {integrity: sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==} + engines: {node: '>= 0.8'} + dependencies: + rndm: 1.2.0 + tsscmp: 1.0.6 + uid-safe: 2.1.5 + dev: false + /css-select@5.1.0: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} dependencies: @@ -7599,6 +7683,10 @@ packages: domhandler: 5.0.3 dev: false + /dotenv-json@1.0.0: + resolution: {integrity: sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ==} + dev: true + /dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -7607,7 +7695,6 @@ packages: /dotenv@8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} - dev: false /duplexify@3.7.1: resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} @@ -9422,6 +9509,13 @@ packages: engines: {node: '>=12'} dev: false + /is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + dependencies: + isobject: 3.0.1 + dev: true + /is-plain-object@3.0.1: resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} engines: {node: '>=0.10.0'} @@ -9552,6 +9646,11 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + /isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + dev: true + /istanbul-lib-coverage@3.2.0: resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} engines: {node: '>=8'} @@ -10256,7 +10355,6 @@ packages: /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - dev: false /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} @@ -10268,6 +10366,35 @@ packages: engines: {node: '>=6'} dev: false + /lambda-event-mock@1.5.0: + resolution: {integrity: sha512-vx1d+vZqi7FF6B3+mAfHnY/6Tlp6BheL2ta0MJS0cIRB3Rc4I5cviHTkiJxHdE156gXx3ZjlQRJrS4puXvtrhA==} + engines: {node: '>=12.13'} + dependencies: + '@extra-number/significant-digits': 1.3.9 + clone-deep: 4.0.1 + uuid: 3.4.0 + vandium-utils: 1.2.0 + dev: true + + /lambda-leak@2.0.0: + resolution: {integrity: sha512-2c9jwUN3ZLa2GEiOhObbx2BMGQplEUCDHSIkhDtYwUjsTfiV/3jCF6ThIuEXfsvqbUK+0QpZcugIKB8YMbSevQ==} + engines: {node: '>=6.10.0'} + dev: true + + /lambda-tester@4.0.1: + resolution: {integrity: sha512-ft6XHk84B6/dYEzyI3anKoGWz08xQ5allEHiFYDUzaYTymgVK7tiBkCEbuWx+MFvH7OpFNsJXVtjXm0X8iH3Iw==} + engines: {node: '>=10.0'} + dependencies: + app-root-path: 3.1.0 + dotenv: 8.6.0 + dotenv-json: 1.0.0 + lambda-event-mock: 1.5.0 + lambda-leak: 2.0.0 + semver: 6.3.1 + uuid: 3.4.0 + vandium-utils: 2.0.0 + dev: true + /language-subtag-registry@0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: false @@ -11474,6 +11601,11 @@ packages: /node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + /node-webtokens@1.0.4: + resolution: {integrity: sha512-Sla56CeSLWvPbwud2kogqf5edQtKNXZBtXDDpmOzAgNZjwETbK/Am6PXfs54iZPLBm8K8amZ9XWaCQwGqZmKyQ==} + engines: {node: '>=6.6.0'} + dev: false + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -12154,6 +12286,11 @@ packages: engines: {node: '>=8'} dev: false + /random-bytes@1.0.0: + resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} + engines: {node: '>= 0.8'} + dev: false + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: @@ -12513,6 +12650,10 @@ packages: dependencies: glob: 7.2.3 + /rndm@1.2.0: + resolution: {integrity: sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==} + dev: false + /rollup-plugin-copy@3.4.0: resolution: {integrity: sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==} engines: {node: '>=8.3'} @@ -12616,6 +12757,10 @@ packages: dependencies: queue-microtask: 1.2.3 + /run-waterfall@1.1.7: + resolution: {integrity: sha512-iFPgh7SatHXOG1ClcpdwHI63geV3Hc/iL6crGSyBlH2PY7Rm/za+zoKz6FfY/Qlw5K7JwSol8pseO8fN6CMhhQ==} + dev: false + /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: @@ -12787,6 +12932,13 @@ packages: /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + /shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + dependencies: + kind-of: 6.0.3 + dev: true + /shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -13403,6 +13555,11 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + /tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + dev: false + /tsutils@3.21.0(typescript@5.4.5): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -13592,6 +13749,13 @@ packages: /ufo@1.5.3: resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} + /uid-safe@2.1.5: + resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} + engines: {node: '>= 0.8'} + dependencies: + random-bytes: 1.0.0 + dev: false + /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -13822,6 +13986,12 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + /uuid@3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + dev: true + /uvu@0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} @@ -13856,6 +14026,15 @@ packages: builtins: 5.0.1 dev: false + /vandium-utils@1.2.0: + resolution: {integrity: sha512-yxYUDZz4BNo0CW/z5w4mvclitt5zolY7zjW97i6tTE+sU63cxYs1A6Bl9+jtIQa3+0hkeqY87k+7ptRvmeHe3g==} + dev: true + + /vandium-utils@2.0.0: + resolution: {integrity: sha512-XWbQ/0H03TpYDXk8sLScBEZpE7TbA0CHDL6/Xjt37IBYKLsHUQuBlL44ttAUs9zoBOLFxsW7HehXcuWCNyqOxQ==} + engines: {node: '>=10.16'} + dev: true + /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} diff --git a/rollup.config.js b/rollup.config.js index d95efad9f2..b64cc5bc8b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -4,6 +4,7 @@ const path = require("path"); module.exports = function rollup(options) { return [ "react-router", + "react-router-architect", "react-router-cloudflare", "react-router-dom", "react-router-dev", diff --git a/tsconfig.json b/tsconfig.json index 1eb60da9a9..2cf584a90c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "files": [], "references": [ { "path": "packages/react-router" }, + { "path": "packages/react-router-architect" }, { "path": "packages/react-router-cloudflare" }, { "path": "packages/react-router-dom" }, { "path": "packages/react-router-dev" },