From accbe2410dfae2a6d95d5f6c8fa6d6c741f6e89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayg=C3=BCn?= Date: Wed, 6 Nov 2024 21:04:29 +0000 Subject: [PATCH] feat: implement content-length and fix status text behavior (#180) * feat: implement content-length and fix status text behavior * fix: add request streaming tests along with its fixes * fix: add more tests and fixes --- .github/workflows/ci.yml | 1 - .vscode/settings.json | 3 +- .../adapter-aws-lambda/src/streaming.ts | 3 + .../adapter-netlify-functions/src/index.ts | 10 +- packages/adapter/adapter-node/src/request.ts | 17 +- packages/adapter/adapter-node/src/response.ts | 138 +- .../adapter/adapter-uwebsockets/src/common.ts | 110 +- packages/base/response/readme.md | 2 +- pnpm-lock.yaml | 1487 ++++++++++++++++- testbed/basic/aws-deploy.js | 3 +- testbed/basic/ci.test.ts | 307 +++- testbed/basic/entry-netlify-edge.js | 4 + testbed/basic/index.js | 58 +- testbed/basic/package.json | 3 +- testbed/basic/readme.md | 37 + 15 files changed, 1987 insertions(+), 196 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e20543f..bb08c82d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,6 @@ jobs: deno-version: v1.x - name: Install Bun - if: matrix.os != 'windows-latest' uses: oven-sh/setup-bun@v2 with: bun-version: latest diff --git a/.vscode/settings.json b/.vscode/settings.json index eb23c812..e39fc976 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "typescript.tsdk": "node_modules/typescript/lib", - "deno.enablePaths": ["./_deno"] + "deno.enablePaths": ["./_deno"], + "deno.enable": false } diff --git a/packages/adapter/adapter-aws-lambda/src/streaming.ts b/packages/adapter/adapter-aws-lambda/src/streaming.ts index 417f3971..08519ae0 100644 --- a/packages/adapter/adapter-aws-lambda/src/streaming.ts +++ b/packages/adapter/adapter-aws-lambda/src/streaming.ts @@ -107,6 +107,7 @@ export default function awsLambdaAdapter( const readable = Readable.fromWeb(response.body as any); readable.pipe(responseStream); } else { + // Lambda always seems to return 200 if we don't call write first responseStream.write(""); responseStream.end(); } @@ -114,6 +115,8 @@ export default function awsLambdaAdapter( await new Promise((resolve, reject) => { responseStream.on("finish", resolve); responseStream.on("error", reject); + // This never seems to fire + // responseStream.on("close", () => {}); }); }, ); diff --git a/packages/adapter/adapter-netlify-functions/src/index.ts b/packages/adapter/adapter-netlify-functions/src/index.ts index 9b9bc93b..3b8102dc 100644 --- a/packages/adapter/adapter-netlify-functions/src/index.ts +++ b/packages/adapter/adapter-netlify-functions/src/index.ts @@ -26,6 +26,8 @@ export default function netlifyFunctionsAdapter( handler: HattipHandler, ): NetlifyFunction { return async (event, netlifyContext) => { + const requestBody = event.body; + const ip = event.headers["x-nf-client-connection-ip"] || event.headers["client-ip"] || @@ -36,13 +38,13 @@ export default function netlifyFunctionsAdapter( method: event.httpMethod, body: - !event.body || event.httpMethod === "GET" || - event.httpMethod === "HEAD" + event.httpMethod === "HEAD" || + !requestBody ? undefined : event.isBase64Encoded - ? Buffer.from(event.body, "base64") - : event.body, + ? Buffer.from(requestBody, "base64") + : requestBody, headers: event.headers, }), diff --git a/packages/adapter/adapter-node/src/request.ts b/packages/adapter/adapter-node/src/request.ts index 7bd6d59e..a70e377d 100644 --- a/packages/adapter/adapter-node/src/request.ts +++ b/packages/adapter/adapter-node/src/request.ts @@ -3,11 +3,10 @@ import type { IncomingMessage, ServerResponse } from "node:http"; import type { Socket } from "node:net"; import process from "node:process"; import { Buffer } from "node:buffer"; +import { Readable } from "node:stream"; // @ts-ignore -const deno = typeof Deno !== "undefined"; -// @ts-ignore -const bun = typeof Bun !== "undefined"; +const isDeno = typeof Deno !== "undefined"; interface PossiblyEncryptedSocket extends Socket { encrypted?: boolean; @@ -143,16 +142,10 @@ function convertBody(req: DecoratedRequest): BodyInit | null | undefined { return req.rawBody; } - if (!bun && !deno) { - // Real Node can handle ReadableStream + if (!isDeno) { + // Bun and real Node can handle Readable as request body return req as any; } - return new ReadableStream({ - start(controller) { - req.on("data", (chunk) => controller.enqueue(chunk)); - req.on("end", () => controller.close()); - req.on("error", (err) => controller.error(err)); - }, - }); + return Readable.toWeb(req) as any; } diff --git a/packages/adapter/adapter-node/src/response.ts b/packages/adapter/adapter-node/src/response.ts index b6999386..ce0a6b2d 100644 --- a/packages/adapter/adapter-node/src/response.ts +++ b/packages/adapter/adapter-node/src/response.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { ServerResponse } from "node:http"; -import { Readable } from "node:stream"; import { rawBodySymbol } from "./raw-body-symbol"; import { DecoratedRequest } from "./common"; @@ -28,58 +27,88 @@ export async function sendResponse( res: ServerResponse, fetchResponse: Response, ): Promise { + const controller = new AbortController(); + const signal = controller.signal; + + req.once("close", () => { + controller.abort(); + }); + + res.once("close", () => { + controller.abort(); + }); + + const hasContentLength = fetchResponse.headers.has("Content-Length"); + if ((fetchResponse as any)[rawBodySymbol]) { writeHead(fetchResponse, res); res.end((fetchResponse as any)[rawBodySymbol]); return; } - const { body: fetchBody } = fetchResponse; + const body = fetchResponse.body; + if (!body) { + // Deno doesn't handle Content-Length automatically + if (!hasContentLength) { + res.setHeader("Content-Length", "0"); + } + writeHead(fetchResponse, res); + res.end(); + return; + } - let body: Readable | null = null; - if (!deno && fetchBody instanceof Readable) { - body = fetchBody; - } else if (fetchBody instanceof ReadableStream) { - if (!deno && Readable.fromWeb) { - // Available in Node.js 17+ - body = Readable.fromWeb(fetchBody as any); + let setImmediateFired = false; + setImmediate(() => { + setImmediateFired = true; + }); + + const chunks: Uint8Array[] = []; + let bufferWritten = false; + for await (const chunk of body) { + if (signal.aborted) { + body.cancel().catch(() => {}); + return; + } + if (setImmediateFired) { + if (!bufferWritten) { + writeHead(fetchResponse, res); + for (const chunk of chunks) { + await writeAndAwait(chunk, res, signal); + if (signal.aborted) { + body.cancel().catch(() => {}); + return; + } + } + + bufferWritten = true; + } + + await writeAndAwait(chunk, res, signal); + if (signal.aborted) { + body.cancel().catch(() => {}); + return; + } } else { - const reader = fetchBody.getReader(); - body = new Readable({ - async read() { - const { done, value } = await reader.read(); - this.push(done ? null : value); - }, - }); + chunks.push(chunk); } - } else if (fetchBody) { - body = Readable.from(fetchBody as any); } - writeHead(fetchResponse, res); + if (signal.aborted) return; - if (body) { - body.pipe(res); - await new Promise((resolve, reject) => { - body!.once("error", reject); - res.once("finish", resolve); - res.once("error", () => { - if (!res.writableEnded) { - body.destroy(); - } - reject(); - }); - req.once("close", () => { - if (!res.writableEnded) { - body.destroy(); - resolve(); - } - }); - }); - } else { - res.setHeader("content-length", "0"); + if (setImmediateFired) { res.end(); + return; + } + + // We were able to read the whole body. Write at once. + const buffer = Buffer.concat(chunks); + + // Deno doesn't handle Content-Length automatically + if (!hasContentLength) { + res.setHeader("Content-Length", buffer.length); } + writeHead(fetchResponse, res); + res.end(buffer); } function writeHead(fetchResponse: Response, nodeResponse: ServerResponse) { @@ -99,3 +128,34 @@ function writeHead(fetchResponse: Response, nodeResponse: ServerResponse) { } } } + +async function writeAndAwait( + chunk: Uint8Array, + res: ServerResponse, + signal: AbortSignal, +) { + const written = res.write(chunk); + if (!written) { + await new Promise((resolve, reject) => { + function cleanup() { + res.off("drain", success); + res.off("error", failure); + signal.removeEventListener("abort", success); + } + + function success() { + cleanup(); + resolve(); + } + + function failure(reason: unknown) { + cleanup(); + reject(reason); + } + + res.once("drain", success); + res.once("error", reject); + signal.addEventListener("abort", success); + }); + } +} diff --git a/packages/adapter/adapter-uwebsockets/src/common.ts b/packages/adapter/adapter-uwebsockets/src/common.ts index 4e66d3a1..ca243e0a 100644 --- a/packages/adapter/adapter-uwebsockets/src/common.ts +++ b/packages/adapter/adapter-uwebsockets/src/common.ts @@ -7,6 +7,7 @@ import { SSLApp, TemplatedApp, } from "uWebSockets.js"; +import { STATUS_CODES } from "node:http"; /** Adapter options */ export interface UWebSocketAdapterOptions { @@ -79,6 +80,8 @@ export function createServer( return app.any("/*", (res, req) => { let finished = false; const controller = new AbortController(); + const signal = controller.signal; + res.onAborted(() => { if (!finished) { controller.abort(); @@ -166,7 +169,8 @@ export function createServer( : new ReadableStream({ start(controller) { res.onData((chunk, isLast) => { - controller.enqueue(new Uint8Array(chunk)); + // The .slice here is required to here to allow chunked bodies :( + controller.enqueue(new Uint8Array(chunk.slice(0))); if (isLast) controller.close(); }); }, @@ -186,18 +190,22 @@ export function createServer( } async function finish(response: Response) { - const stream = response.body; - + const body = response.body; if (controller.signal.aborted) { + if (body) { + body.cancel().catch(() => {}); + } return; } - res.cork(() => { - res.writeStatus( - `${response.status}${ - response.statusText ? " " + response.statusText : "" - }`, - ); + function writeHead() { + let statusLine = `${response.status}`; + const statusText = response.statusText || STATUS_CODES[response.status]; + if (statusText) { + statusLine += " " + statusText; + } + + res.writeStatus(statusLine); const uniqueHeaderNames = new Set(response.headers.keys()); for (const name of uniqueHeaderNames) { @@ -209,35 +217,81 @@ export function createServer( res.writeHeader(name, response.headers.get(name)!); } } + } - if (!stream) { - res.end(); - } + if (!body) { + writeHead(); + res.cork(() => { + res.endWithoutBody(); + }); + finished = true; + return; + } + + async function writeAndAwait(chunk: Uint8Array) { + await new Promise((resolve) => { + res.cork(() => { + const backpressure = !res.write(chunk); + if (backpressure) { + // TODO: Handle backpressure + } + resolve(); + }); + }); + } + + let setImmediateFired = false; + setImmediate(() => { + setImmediateFired = true; }); - if (stream) { - const reader = stream.getReader(); - for (;;) { - const chunk = await reader.read(); - if (controller.signal.aborted) { - await reader.cancel(); - break; + const chunks: Uint8Array[] = []; + let bufferWritten = false; + for await (const chunk of body) { + if (signal.aborted) { + body.cancel().catch(() => {}); + return; + } + if (setImmediateFired) { + if (!bufferWritten) { + res.cork(() => { + writeHead(); + for (const chunk of chunks) { + // TODO: Handle backpressure + res.write(chunk); + } + }); + + bufferWritten = true; } - if (chunk.done) { - break; + await writeAndAwait(chunk); + if (signal.aborted) { + body.cancel().catch(() => {}); + return; } - - res.cork(() => res.write(chunk.value)); + } else { + chunks.push(chunk); } + } - if (!controller.signal.aborted) { - res.cork(() => { - res.end(); - }); - } + if (signal.aborted) return; + + if (setImmediateFired) { + res.cork(() => { + res.end(); + }); + finished = true; + return; } + // We were able to read the whole body. Write at once. + const buffer = Buffer.concat(chunks); + res.cork(() => { + writeHead(); + res.end(buffer); + }); + finished = true; } }); diff --git a/packages/base/response/readme.md b/packages/base/response/readme.md index 247842a8..5af48cc2 100644 --- a/packages/base/response/readme.md +++ b/packages/base/response/readme.md @@ -56,4 +56,4 @@ serverSentEvents({ `serverSentEvents` is intentionally very low-level. It doesn't handle data serialization (it only accepts strings), or keep track of connections, event IDs, or the `Last-Event-ID` header. But it is very flexible and allows you to implement your own logic for a full pub/sub system. -`serverSentEvents` works on all adapters that support streaming responses but since it requires a long-running server, using it with edge runtimes is not very useful. It leaves Node (optionally with uWebSockets.js) and Deno as the only real options. In particular, Bun and AWS-based serverless offerings of Netlify and Vercel don't support streaming responses. +`serverSentEvents` works on all adapters that support streaming responses but since it requires a long-running server, using it with edge or serverless runtimes is not very useful. It leaves Node, Deno, and Bun as the only real options. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4968454..2d7fcc36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,7 +37,7 @@ importers: version: 5.4.10(@types/node@22.5.5)(terser@5.36.0) vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@22.5.5)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@22.5.5)(terser@5.36.0) deno-build: dependencies: @@ -413,7 +413,7 @@ importers: version: 5.6.3 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@22.5.5)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@22.5.5)(terser@5.36.0) packages/adapter/adapter-uwebsockets: dependencies: @@ -497,7 +497,7 @@ importers: version: 5.6.3 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@22.5.5)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@22.5.5)(terser@5.36.0) packages/base/core: devDependencies: @@ -537,7 +537,7 @@ importers: version: 5.6.3 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@22.5.5)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@22.5.5)(terser@5.36.0) packages/base/multipart: dependencies: @@ -568,7 +568,7 @@ importers: version: 5.6.3 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@22.5.5)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@22.5.5)(terser@5.36.0) packages/base/polyfills: dependencies: @@ -602,7 +602,7 @@ importers: version: 5.6.3 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@18.19.64)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@18.19.64)(terser@5.36.0) packages/base/response: dependencies: @@ -642,7 +642,7 @@ importers: version: 5.6.3 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@18.19.64)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@18.19.64)(terser@5.36.0) packages/base/router: dependencies: @@ -676,7 +676,7 @@ importers: version: 5.6.3 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@22.5.5)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@22.5.5)(terser@5.36.0) packages/bundler/bundler-aws-lambda: dependencies: @@ -871,7 +871,7 @@ importers: version: 5.6.3 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@18.19.64)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@18.19.64)(terser@5.36.0) packages/cli/vite: dependencies: @@ -1003,7 +1003,7 @@ importers: version: 5.6.3 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@18.19.64)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@18.19.64)(terser@5.36.0) packages/middleware/graphql: dependencies: @@ -1110,7 +1110,7 @@ importers: version: 5.6.3 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@18.19.64)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@18.19.64)(terser@5.36.0) packages/middleware/static: dependencies: @@ -1299,9 +1299,12 @@ importers: typescript: specifier: ^5.6.3 version: 5.6.3 + vercel: + specifier: ^37.14.0 + version: 37.14.0 vitest: specifier: ^2.1.4 - version: 2.1.4(@types/node@18.19.64)(terser@5.36.0) + version: 2.1.4(@edge-runtime/vm@3.2.0)(@types/node@18.19.64)(terser@5.36.0) wrangler: specifier: ^3.84.1 version: 3.84.1(@cloudflare/workers-types@4.20241022.0) @@ -2050,6 +2053,41 @@ packages: } engines: { node: ">=14" } + "@edge-runtime/format@2.2.1": + resolution: + { + integrity: sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==, + } + engines: { node: ">=16" } + + "@edge-runtime/node-utils@2.3.0": + resolution: + { + integrity: sha512-uUtx8BFoO1hNxtHjp3eqVPC/mWImGb2exOfGjMLUoipuWgjej+f4o/VP4bUI8U40gu7Teogd5VTeZUkGvJSPOQ==, + } + engines: { node: ">=16" } + + "@edge-runtime/ponyfill@2.4.2": + resolution: + { + integrity: sha512-oN17GjFr69chu6sDLvXxdhg0Qe8EZviGSuqzR9qOiKh4MhFYGdBBcqRNzdmYeAdeRzOW2mM9yil4RftUQ7sUOA==, + } + engines: { node: ">=16" } + + "@edge-runtime/primitives@4.1.0": + resolution: + { + integrity: sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==, + } + engines: { node: ">=16" } + + "@edge-runtime/vm@3.2.0": + resolution: + { + integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==, + } + engines: { node: ">=16" } + "@emnapi/core@1.3.1": resolution: { @@ -4586,6 +4624,12 @@ packages: integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==, } + "@sinclair/typebox@0.25.24": + resolution: + { + integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==, + } + "@sindresorhus/is@5.6.0": resolution: { @@ -4957,6 +5001,13 @@ packages: integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==, } + "@tootallnate/once@2.0.0": + resolution: + { + integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==, + } + engines: { node: ">= 10" } + "@trysound/sax@0.2.0": resolution: { @@ -4964,6 +5015,12 @@ packages: } engines: { node: ">=10.13.0" } + "@ts-morph/common@0.11.1": + resolution: + { + integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==, + } + "@tsconfig/node10@1.0.11": resolution: { @@ -5151,6 +5208,12 @@ packages: integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==, } + "@types/node@16.18.11": + resolution: + { + integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==, + } + "@types/node@18.19.64": resolution: { @@ -5397,6 +5460,49 @@ packages: peerDependencies: vite: ^2.8.1 || 3 || 4 || 5 + "@vercel/build-utils@8.4.12": + resolution: + { + integrity: sha512-pIH0b965wJhd1otROVPndfZenPKFVoYSaRjtSKVOT/oNBT13ifq86UVjb5ZjoVfqUI2TtSTP+68kBqLPeoq30g==, + } + + "@vercel/error-utils@2.0.2": + resolution: + { + integrity: sha512-Sj0LFafGpYr6pfCqrQ82X6ukRl5qpmVrHM/191kNYFqkkB9YkjlMAj6QcEsvCG259x4QZ7Tya++0AB85NDPbKQ==, + } + + "@vercel/fun@1.1.0": + resolution: + { + integrity: sha512-SpuPAo+MlAYMtcMcC0plx7Tv4Mp7SQhJJj1iIENlOnABL24kxHpL09XLQMGzZIzIW7upR8c3edwgfpRtp+dhVw==, + } + engines: { node: ">= 10" } + + "@vercel/gatsby-plugin-vercel-analytics@1.0.11": + resolution: + { + integrity: sha512-iTEA0vY6RBPuEzkwUTVzSHDATo1aF6bdLLspI68mQ/BTbi5UQEGjpjyzdKOVcSYApDtFU6M6vypZ1t4vIEnHvw==, + } + + "@vercel/gatsby-plugin-vercel-builder@2.0.56": + resolution: + { + integrity: sha512-SZM8k/YcOcfk2p1cSZOuSK37CDBJtF/WiEr8CemDI/MBbXM4aC2StfzDd0F0cK/2rExpSA9lTAE9ia3w+cDS9w==, + } + + "@vercel/go@3.2.0": + resolution: + { + integrity: sha512-zUCBoh57x1OEtw+TKdRhSQciqERrpDxLlPeBOYawUCC5uKjsBjhdq0U21+NGz2LcRUaYyYYGMw6BzqVaig9u1g==, + } + + "@vercel/hydrogen@1.0.9": + resolution: + { + integrity: sha512-IPAVaALuGAzt2apvTtBs5tB+8zZRzn/yG3AGp8dFyCsw/v5YOuk0Q5s8Z3fayLvJbFpjrKtqRNDZzVJBBU3MrQ==, + } + "@vercel/ncc@0.38.2": resolution: { @@ -5404,6 +5510,20 @@ packages: } hasBin: true + "@vercel/next@4.3.18": + resolution: + { + integrity: sha512-ih6++AA7/NCcLkMpdsDhr/folMlAKsU1sYUoyOjq4rYE9sSapELtgxls0CArv4ehE2Tt4YwoxBISnKPZKK5SSA==, + } + + "@vercel/nft@0.27.3": + resolution: + { + integrity: sha512-oySTdDSzUAFDXpsSLk9Q943o+/Yu/+TCFxnehpFQEf/3khi2stMpTHPVNwFdvZq/Z4Ky93lE+MGHpXCRpMkSCA==, + } + engines: { node: ">=16" } + hasBin: true + "@vercel/nft@0.27.5": resolution: { @@ -5412,6 +5532,54 @@ packages: engines: { node: ">=16" } hasBin: true + "@vercel/node@3.2.24": + resolution: + { + integrity: sha512-KEm50YBmcfRNOw5NfdcqMI4BkP4+5TD9kRwAByHHlIZXLj1NTTknvMF+69sHBYzwpK/SUZIkeo7jTrtcl4g+RQ==, + } + + "@vercel/python@4.3.1": + resolution: + { + integrity: sha512-pWRApBwUsAQJS8oZ7eKMiaBGbYJO71qw2CZqDFvkTj34FNBZtNIUcWSmqGfJJY5m2pU/9wt8z1CnKIyT9dstog==, + } + + "@vercel/redwood@2.1.8": + resolution: + { + integrity: sha512-qBUBqIDxPEYnxRh3tsvTaPMtBkyK/D2tt9tBugNPe0OeYnMCMXVj9SJYbxiDI2GzAEFUZn4Poh63CZtXMDb9Tg==, + } + + "@vercel/remix-builder@2.2.13": + resolution: + { + integrity: sha512-TenVtvfERodSwUjm0rzjz3v00Drd0FUXLWnwdwnv7VLgqmX2FW/2+1byhmPhJicMp3Eybl52GvF2/KbBkNo95w==, + } + + "@vercel/routing-utils@3.1.0": + resolution: + { + integrity: sha512-Ci5xTjVTJY/JLZXpCXpLehMft97i9fH34nu9PGav6DtwkVUF6TOPX86U0W0niQjMZ5n6/ZP0BwcJK2LOozKaGw==, + } + + "@vercel/ruby@2.1.0": + resolution: + { + integrity: sha512-UZYwlSEEfVnfzTmgkD+kxex9/gkZGt7unOWNyWFN7V/ZnZSsGBUgv6hXLnwejdRi3EztgRQEBd1kUKlXdIeC0Q==, + } + + "@vercel/static-build@2.5.34": + resolution: + { + integrity: sha512-4RL60ghhBufs/45j6J9zQzMpt8JmUhp/4+xE8RxO80n6qTlc/oERKrWxzeXLEGF32whSHsB+ROJt0Ytytoz2Tw==, + } + + "@vercel/static-config@3.0.0": + resolution: + { + integrity: sha512-2qtvcBJ1bGY0dYGYh3iM7yGKkk971FujLEDXzuW5wcZsPr1GSEjO/w2iSr3qve6nDDtBImsGoDEnus5FI4+fIw==, + } + "@vitest/expect@2.1.4": resolution: { @@ -5659,6 +5827,12 @@ packages: integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==, } + ajv@8.6.3: + resolution: + { + integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==, + } + all-node-versions@11.3.0: resolution: { @@ -5812,6 +5986,12 @@ packages: engines: { node: ">=10" } deprecated: This package is no longer supported. + arg@4.1.0: + resolution: + { + integrity: sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==, + } + arg@4.1.3: resolution: { @@ -5945,6 +6125,26 @@ packages: } engines: { node: ">=14" } + async-listen@1.2.0: + resolution: + { + integrity: sha512-CcEtRh/oc9Jc4uWeUwdpG/+Mb2YUHKmdaTf0gUr7Wa+bfp4xx70HOb3RuSTJMvqKNB1TkdTfjLdrcz2X4rkkZA==, + } + + async-listen@3.0.0: + resolution: + { + integrity: sha512-V+SsTpDqkrWTimiotsyl33ePSjA5/KrithwupuvJ6ztsqPvGv6ge4OredFhPffVXiLN/QUWvE0XcqJaYgt6fOg==, + } + engines: { node: ">= 14" } + + async-listen@3.0.1: + resolution: + { + integrity: sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==, + } + engines: { node: ">= 14" } + async-sema@3.1.1: resolution: { @@ -6331,6 +6531,13 @@ packages: } engines: { node: ">=0.10.0" } + bytes@3.1.0: + resolution: + { + integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==, + } + engines: { node: ">= 0.8" } + bytes@3.1.2: resolution: { @@ -6467,6 +6674,13 @@ packages: } engines: { node: ">= 16" } + chokidar@3.3.1: + resolution: + { + integrity: sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==, + } + engines: { node: ">= 8.10.0" } + chokidar@3.6.0: resolution: { @@ -6514,6 +6728,12 @@ packages: integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==, } + cjs-module-lexer@1.2.3: + resolution: + { + integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==, + } + clean-deep@3.4.0: resolution: { @@ -6597,6 +6817,12 @@ packages: } engines: { node: ">=16 <=22" } + code-block-writer@10.1.1: + resolution: + { + integrity: sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==, + } + color-convert@1.9.3: resolution: { @@ -6828,6 +7054,13 @@ packages: } engines: { node: ">= 0.6" } + content-type@1.0.4: + resolution: + { + integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==, + } + engines: { node: ">= 0.6" } + content-type@1.0.5: resolution: { @@ -6835,6 +7068,13 @@ packages: } engines: { node: ">= 0.6" } + convert-hrtime@3.0.0: + resolution: + { + integrity: sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==, + } + engines: { node: ">=8" } + convert-source-map@2.0.0: resolution: { @@ -7086,6 +7326,18 @@ packages: supports-color: optional: true + debug@4.1.1: + resolution: + { + integrity: sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==, + } + deprecated: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797) + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + debug@4.3.6: resolution: { @@ -7436,6 +7688,14 @@ packages: integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==, } + edge-runtime@2.5.9: + resolution: + { + integrity: sha512-pk+k0oK0PVXdlT4oRp4lwh+unuKB7Ng4iZ2HB+EZ7QCEQizX360Rp/F4aRpgpRgdP2ufB35N+1KppHmYjqIGSg==, + } + engines: { node: ">=16" } + hasBin: true + ee-first@1.1.1: resolution: { @@ -7486,6 +7746,12 @@ packages: } engines: { node: ">= 0.8" } + end-of-stream@1.1.0: + resolution: + { + integrity: sha512-EoulkdKF/1xa92q25PbjuDcgJ9RDHYU2Rs3SCIvs2/dSQ3BpmxneNHmA/M7fe60M3PrV7nNGTTNbkK62l6vXiQ==, + } + end-of-stream@1.4.4: resolution: { @@ -7574,6 +7840,12 @@ packages: } engines: { node: ">= 0.4" } + es-module-lexer@1.4.1: + resolution: + { + integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==, + } + es-module-lexer@1.5.4: resolution: { @@ -7613,6 +7885,194 @@ packages: integrity: sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==, } + esbuild-android-64@0.14.47: + resolution: + { + integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==, + } + engines: { node: ">=12" } + cpu: [x64] + os: [android] + + esbuild-android-arm64@0.14.47: + resolution: + { + integrity: sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==, + } + engines: { node: ">=12" } + cpu: [arm64] + os: [android] + + esbuild-darwin-64@0.14.47: + resolution: + { + integrity: sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==, + } + engines: { node: ">=12" } + cpu: [x64] + os: [darwin] + + esbuild-darwin-arm64@0.14.47: + resolution: + { + integrity: sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==, + } + engines: { node: ">=12" } + cpu: [arm64] + os: [darwin] + + esbuild-freebsd-64@0.14.47: + resolution: + { + integrity: sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==, + } + engines: { node: ">=12" } + cpu: [x64] + os: [freebsd] + + esbuild-freebsd-arm64@0.14.47: + resolution: + { + integrity: sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==, + } + engines: { node: ">=12" } + cpu: [arm64] + os: [freebsd] + + esbuild-linux-32@0.14.47: + resolution: + { + integrity: sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==, + } + engines: { node: ">=12" } + cpu: [ia32] + os: [linux] + + esbuild-linux-64@0.14.47: + resolution: + { + integrity: sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==, + } + engines: { node: ">=12" } + cpu: [x64] + os: [linux] + + esbuild-linux-arm64@0.14.47: + resolution: + { + integrity: sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==, + } + engines: { node: ">=12" } + cpu: [arm64] + os: [linux] + + esbuild-linux-arm@0.14.47: + resolution: + { + integrity: sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==, + } + engines: { node: ">=12" } + cpu: [arm] + os: [linux] + + esbuild-linux-mips64le@0.14.47: + resolution: + { + integrity: sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==, + } + engines: { node: ">=12" } + cpu: [mips64el] + os: [linux] + + esbuild-linux-ppc64le@0.14.47: + resolution: + { + integrity: sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==, + } + engines: { node: ">=12" } + cpu: [ppc64] + os: [linux] + + esbuild-linux-riscv64@0.14.47: + resolution: + { + integrity: sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==, + } + engines: { node: ">=12" } + cpu: [riscv64] + os: [linux] + + esbuild-linux-s390x@0.14.47: + resolution: + { + integrity: sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==, + } + engines: { node: ">=12" } + cpu: [s390x] + os: [linux] + + esbuild-netbsd-64@0.14.47: + resolution: + { + integrity: sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==, + } + engines: { node: ">=12" } + cpu: [x64] + os: [netbsd] + + esbuild-openbsd-64@0.14.47: + resolution: + { + integrity: sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==, + } + engines: { node: ">=12" } + cpu: [x64] + os: [openbsd] + + esbuild-sunos-64@0.14.47: + resolution: + { + integrity: sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==, + } + engines: { node: ">=12" } + cpu: [x64] + os: [sunos] + + esbuild-windows-32@0.14.47: + resolution: + { + integrity: sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==, + } + engines: { node: ">=12" } + cpu: [ia32] + os: [win32] + + esbuild-windows-64@0.14.47: + resolution: + { + integrity: sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==, + } + engines: { node: ">=12" } + cpu: [x64] + os: [win32] + + esbuild-windows-arm64@0.14.47: + resolution: + { + integrity: sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==, + } + engines: { node: ">=12" } + cpu: [arm64] + os: [win32] + + esbuild@0.14.47: + resolution: + { + integrity: sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==, + } + engines: { node: ">=12" } + hasBin: true + esbuild@0.17.19: resolution: { @@ -7954,6 +8414,12 @@ packages: integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==, } + events-intercept@2.0.0: + resolution: + { + integrity: sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==, + } + events@1.1.1: resolution: { @@ -7981,6 +8447,13 @@ packages: } engines: { node: ">=12.0.0" } + execa@3.2.0: + resolution: + { + integrity: sha512-kJJfVbI/lZE1PZYDI5VPxp8zXPO9rtxOkhpZ0jMKha56AI9y2gGVC6bkukStQf0ka5Rh15BA5m7cCCH4jmHqkw==, + } + engines: { node: ^8.12.0 || >=9.7.0 } + execa@5.1.1: resolution: { @@ -8534,6 +9007,26 @@ packages: integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==, } + fs-extra@11.1.0: + resolution: + { + integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==, + } + engines: { node: ">=14.14" } + + fs-extra@8.1.0: + resolution: + { + integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==, + } + engines: { node: ">=6 <7 || >=8" } + + fs-minipass@1.2.7: + resolution: + { + integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==, + } + fs-minipass@2.1.0: resolution: { @@ -8547,6 +9040,14 @@ packages: integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, } + fsevents@2.1.3: + resolution: + { + integrity: sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + fsevents@2.3.3: resolution: { @@ -8589,6 +9090,13 @@ packages: engines: { node: ">=10" } deprecated: This package is no longer supported. + generic-pool@3.4.2: + resolution: + { + integrity: sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==, + } + engines: { node: ">= 4" } + gensync@1.0.0-beta.2: resolution: { @@ -9048,6 +9556,20 @@ packages: integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==, } + http-errors@1.4.0: + resolution: + { + integrity: sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw==, + } + engines: { node: ">= 0.6" } + + http-errors@1.7.3: + resolution: + { + integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==, + } + engines: { node: ">= 0.6" } + http-errors@1.8.1: resolution: { @@ -9109,6 +9631,13 @@ packages: } engines: { node: ">= 14" } + human-signals@1.1.1: + resolution: + { + integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==, + } + engines: { node: ">=8.12.0" } + human-signals@2.1.0: resolution: { @@ -9231,6 +9760,12 @@ packages: } deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.1: + resolution: + { + integrity: sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==, + } + inherits@2.0.4: resolution: { @@ -9763,6 +10298,12 @@ packages: } engines: { node: ">=18" } + isarray@0.0.1: + resolution: + { + integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==, + } + isarray@1.0.0: resolution: { @@ -9921,6 +10462,12 @@ packages: integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==, } + json-schema-to-ts@1.6.4: + resolution: + { + integrity: sha512-pR4yQ9DHz6itqswtHCm26mw45FSNfQ9rEQjosaZErhn5J3J2sIViQiz8rDaezjKAhFGpmsoczYVBgGHzFw/stA==, + } + json-schema-traverse@0.4.1: resolution: { @@ -9960,6 +10507,18 @@ packages: integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==, } + jsonfile@4.0.0: + resolution: + { + integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==, + } + + jsonfile@6.1.0: + resolution: + { + integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==, + } + jsonpointer@5.0.1: resolution: { @@ -10485,6 +11044,14 @@ packages: integrity: sha512-+HzcV2H+rbSJzApgkj0NdTakkC+bnyeiUxgT6/m7mjcz1CmM22KYFKp+EVj1sWe4UYcnriJr5uqHQD/gMHLD+g==, } + micro@9.3.5-canary.3: + resolution: + { + integrity: sha512-viYIo9PefV+w9dvoIBh1gI44Mvx1BOk67B4BpC2QK77qdY0xZF0Q+vWLt/BII6cLkIc8rLmSIcJaB/OrXXKe1g==, + } + engines: { node: ">= 8.0.0" } + hasBin: true + micromatch@4.0.8: resolution: { @@ -10612,6 +11179,12 @@ packages: integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, } + minipass@2.9.0: + resolution: + { + integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==, + } + minipass@3.3.6: resolution: { @@ -10640,6 +11213,12 @@ packages: } engines: { node: ">=16 || 14 >=14.17" } + minizlib@1.3.3: + resolution: + { + integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==, + } + minizlib@2.1.2: resolution: { @@ -10723,6 +11302,12 @@ packages: integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==, } + ms@2.1.1: + resolution: + { + integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==, + } + ms@2.1.2: resolution: { @@ -10873,6 +11458,30 @@ packages: integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==, } + node-fetch@2.6.7: + resolution: + { + integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==, + } + engines: { node: 4.x || >=6.0.0 } + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-fetch@2.6.9: + resolution: + { + integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==, + } + engines: { node: 4.x || >=6.0.0 } + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-fetch@2.7.0: resolution: { @@ -11160,6 +11769,12 @@ packages: } engines: { node: ">= 0.8" } + once@1.3.3: + resolution: + { + integrity: sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==, + } + once@1.4.0: resolution: { @@ -11235,6 +11850,13 @@ packages: } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + os-paths@4.4.0: + resolution: + { + integrity: sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg==, + } + engines: { node: ">= 6.0" } + os-tmpdir@1.0.2: resolution: { @@ -11291,6 +11913,13 @@ packages: } engines: { node: ">=4" } + p-finally@2.0.1: + resolution: + { + integrity: sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==, + } + engines: { node: ">=8" } + p-limit@2.3.0: resolution: { @@ -11479,6 +12108,13 @@ packages: } engines: { node: ">=18" } + parse-ms@2.1.0: + resolution: + { + integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==, + } + engines: { node: ">=6" } + parse-ms@3.0.0: resolution: { @@ -11499,6 +12135,12 @@ packages: } engines: { node: ">= 0.8" } + path-browserify@1.0.1: + resolution: + { + integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==, + } + path-exists@4.0.0: resolution: { @@ -11534,6 +12176,12 @@ packages: } engines: { node: ">=12" } + path-match@1.2.4: + resolution: + { + integrity: sha512-UWlehEdqu36jmh4h5CWJ7tARp1OEVKGHKm6+dg9qMq5RKUTV5WJrGgaZ3dN2m7WFAXDbjlHzvJvL/IUpy84Ktw==, + } + path-parse@1.0.7: resolution: { @@ -11567,6 +12215,24 @@ packages: integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==, } + path-to-regexp@1.9.0: + resolution: + { + integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==, + } + + path-to-regexp@6.1.0: + resolution: + { + integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==, + } + + path-to-regexp@6.2.1: + resolution: + { + integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==, + } + path-to-regexp@6.3.0: resolution: { @@ -11619,6 +12285,12 @@ packages: integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==, } + picocolors@1.0.0: + resolution: + { + integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, + } + picocolors@1.1.1: resolution: { @@ -11803,6 +12475,13 @@ packages: } engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 } + pretty-ms@7.0.1: + resolution: + { + integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==, + } + engines: { node: ">=10" } + pretty-ms@8.0.0: resolution: { @@ -11848,6 +12527,12 @@ packages: } engines: { node: ">= 0.6.0" } + promisepipe@3.0.0: + resolution: + { + integrity: sha512-V6TbZDJ/ZswevgkDNpGt/YqNCiZP9ASfgU+p83uJE6NrGtvSGoOcHLiDCqkMs2+yg7F5qHdLV8d0aS8O26G/KA==, + } + prop-types@15.8.1: resolution: { @@ -12007,6 +12692,13 @@ packages: } engines: { node: ">= 0.6" } + raw-body@2.4.1: + resolution: + { + integrity: sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==, + } + engines: { node: ">= 0.8" } + raw-body@2.5.2: resolution: { @@ -12108,6 +12800,13 @@ packages: integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==, } + readdirp@3.3.0: + resolution: + { + integrity: sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==, + } + engines: { node: ">=8.10.0" } + readdirp@3.6.0: resolution: { @@ -12514,6 +13213,14 @@ packages: } hasBin: true + semver@7.3.5: + resolution: + { + integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==, + } + engines: { node: ">=10" } + hasBin: true + semver@7.6.3: resolution: { @@ -12562,6 +13269,12 @@ packages: } engines: { node: ">= 0.4" } + setprototypeof@1.1.1: + resolution: + { + integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==, + } + setprototypeof@1.2.0: resolution: { @@ -12608,6 +13321,13 @@ packages: integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, } + signal-exit@4.0.2: + resolution: + { + integrity: sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==, + } + engines: { node: ">=14" } + signal-exit@4.1.0: resolution: { @@ -12808,6 +13528,12 @@ packages: integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==, } + stat-mode@0.3.0: + resolution: + { + integrity: sha512-QjMLR0A3WwFY2aZdV0okfFEJB5TRjkggXZjxP3A1RsWsNHNu3YPv8btmtc6iCFZ0Rul3FE93OYogvhOUClU+ng==, + } + statuses@1.5.0: resolution: { @@ -12848,6 +13574,18 @@ packages: integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==, } + stream-to-array@2.3.0: + resolution: + { + integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==, + } + + stream-to-promise@2.2.0: + resolution: + { + integrity: sha512-HAGUASw8NT0k8JvIVutB2Y/9iBk7gpgEyAudXwNJmZERdMITGdajOa4VJfD/kNiA3TppQpTP4J+CtcHwdzKBAw==, + } + streamsearch@1.1.0: resolution: { @@ -13157,6 +13895,13 @@ packages: integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==, } + tar@4.4.18: + resolution: + { + integrity: sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg==, + } + engines: { node: ">=4.5" } + tar@6.1.15: resolution: { @@ -13269,6 +14014,13 @@ packages: integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==, } + time-span@4.0.0: + resolution: + { + integrity: sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==, + } + engines: { node: ">=10" } + time-zone@1.0.0: resolution: { @@ -13363,6 +14115,13 @@ packages: } engines: { node: ">=12" } + toidentifier@1.0.0: + resolution: + { + integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==, + } + engines: { node: ">=0.6" } + toidentifier@1.0.1: resolution: { @@ -13451,6 +14210,29 @@ packages: integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==, } + ts-morph@12.0.0: + resolution: + { + integrity: sha512-VHC8XgU2fFW7yO1f/b3mxKDje1vmyzFXHWzOYmKEkCEwcLjDtbdLgBQviqj4ZwP4MJkQtRo6Ha2I29lq/B+VxA==, + } + + ts-node@10.9.1: + resolution: + { + integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==, + } + hasBin: true + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + ts-node@10.9.2: resolution: { @@ -13468,6 +14250,12 @@ packages: "@swc/wasm": optional: true + ts-toolbelt@6.15.5: + resolution: + { + integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==, + } + tsconfig-paths@3.15.0: resolution: { @@ -13640,6 +14428,14 @@ packages: typescript: optional: true + typescript@4.9.5: + resolution: + { + integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==, + } + engines: { node: ">=4.2.0" } + hasBin: true + typescript@5.6.3: resolution: { @@ -13668,6 +14464,12 @@ packages: integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==, } + uid-promise@1.0.0: + resolution: + { + integrity: sha512-R8375j0qwXyIu/7R0tjdF06/sElHqbmdmWC9M2qQHpEVbvE4I5+38KJI7LUUmQMp7NVq4tKHiBMkT0NFM453Ig==, + } + uid-safe@2.1.5: resolution: { @@ -13785,6 +14587,20 @@ packages: integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==, } + universalify@0.1.2: + resolution: + { + integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==, + } + engines: { node: ">= 4.0.0" } + + universalify@2.0.1: + resolution: + { + integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==, + } + engines: { node: ">= 10.0.0" } + unix-dgram@2.0.6: resolution: { @@ -13939,6 +14755,14 @@ packages: } engines: { node: ">= 0.4.0" } + uuid@3.3.2: + resolution: + { + integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==, + } + 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 + uuid@8.0.0: resolution: { @@ -13993,6 +14817,14 @@ packages: } engines: { node: ">= 0.8" } + vercel@37.14.0: + resolution: + { + integrity: sha512-ZSEvhARyJBn4YnEVZULsvti8/OHd5txRCgJqEhNIyo/XXSvBJSvlCjA+SE1zraqn0rqyEOG3+56N3kh1Enk8Tg==, + } + engines: { node: ">= 16" } + hasBin: true + vite-node@2.1.4: resolution: { @@ -14085,6 +14917,12 @@ packages: } engines: { node: ">= 8" } + web-vitals@0.2.4: + resolution: + { + integrity: sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==, + } + webidl-conversions@3.0.1: resolution: { @@ -14303,6 +15141,13 @@ packages: utf-8-validate: optional: true + xdg-app-paths@5.1.0: + resolution: + { + integrity: sha512-RAQ3WkPf4KTU1A8RtFx3gWywzVKe00tfOPFfl2NDGqbIFENQO4kqAJp7mhQjNj/33W5x5hiWWUdyfPq/5SU3QA==, + } + engines: { node: ">=6" } + xdg-basedir@5.1.0: resolution: { @@ -14310,6 +15155,13 @@ packages: } engines: { node: ">=12" } + xdg-portable@7.3.0: + resolution: + { + integrity: sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw==, + } + engines: { node: ">= 6.0" } + xml2js@0.5.0: resolution: { @@ -14394,6 +15246,20 @@ packages: } engines: { node: ">=12" } + yauzl-clone@1.0.4: + resolution: + { + integrity: sha512-igM2RRCf3k8TvZoxR2oguuw4z1xasOnA31joCqHIyLkeWrvAc2Jgay5ISQ2ZplinkoGaJ6orCz56Ey456c5ESA==, + } + engines: { node: ">=6" } + + yauzl-promise@2.1.3: + resolution: + { + integrity: sha512-A1pf6fzh6eYkK0L4Qp7g9jzJSDrM6nN0bOn5T0IbY4Yo3w+YkWlHFkJP7mzknMXjqusHFHlKsK2N+4OLsK2MRA==, + } + engines: { node: ">=6" } + yauzl@2.10.0: resolution: { @@ -15300,9 +16166,9 @@ snapshots: "@eslint/js": 9.14.0 eslint: 9.14.0(jiti@2.4.0) eslint-config-prettier: 9.1.0(eslint@9.14.0(jiti@2.4.0)) - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.14.0(jiti@2.4.0)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)) eslint-plugin-css-modules: 2.12.0(eslint@9.14.0(jiti@2.4.0)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0(jiti@2.4.0)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)) eslint-plugin-no-only-tests: 3.3.0 eslint-plugin-only-warn: 1.1.0 eslint-plugin-react: 7.37.2(eslint@9.14.0(jiti@2.4.0)) @@ -15353,6 +16219,18 @@ snapshots: gonzales-pe: 4.3.0 node-source-walk: 6.0.2 + "@edge-runtime/format@2.2.1": {} + + "@edge-runtime/node-utils@2.3.0": {} + + "@edge-runtime/ponyfill@2.4.2": {} + + "@edge-runtime/primitives@4.1.0": {} + + "@edge-runtime/vm@3.2.0": + dependencies: + "@edge-runtime/primitives": 4.1.0 + "@emnapi/core@1.3.1": dependencies: "@emnapi/wasi-threads": 1.0.1 @@ -16751,6 +17629,8 @@ snapshots: "@rtsao/scc@1.1.0": {} + "@sinclair/typebox@0.25.24": {} + "@sindresorhus/is@5.6.0": {} "@sindresorhus/slugify@2.2.1": @@ -17079,8 +17959,17 @@ snapshots: "@tokenizer/token@0.3.0": {} + "@tootallnate/once@2.0.0": {} + "@trysound/sax@0.2.0": {} + "@ts-morph/common@0.11.1": + dependencies: + fast-glob: 3.3.2 + minimatch: 3.1.2 + mkdirp: 1.0.4 + path-browserify: 1.0.1 + "@tsconfig/node10@1.0.11": {} "@tsconfig/node12@1.0.11": {} @@ -17188,6 +18077,8 @@ snapshots: "@types/node@14.18.63": {} + "@types/node@16.18.11": {} + "@types/node@18.19.64": dependencies: undici-types: 5.26.5 @@ -17385,8 +18276,83 @@ snapshots: dependencies: vite: 5.4.10(@types/node@22.5.5)(terser@5.36.0) + "@vercel/build-utils@8.4.12": {} + + "@vercel/error-utils@2.0.2": {} + + "@vercel/fun@1.1.0": + dependencies: + "@tootallnate/once": 2.0.0 + async-listen: 1.2.0 + debug: 4.1.1 + execa: 3.2.0 + fs-extra: 8.1.0 + generic-pool: 3.4.2 + micro: 9.3.5-canary.3 + ms: 2.1.1 + node-fetch: 2.6.7 + path-match: 1.2.4 + promisepipe: 3.0.0 + semver: 7.3.5 + stat-mode: 0.3.0 + stream-to-promise: 2.2.0 + tar: 4.4.18 + tree-kill: 1.2.2 + uid-promise: 1.0.0 + uuid: 3.3.2 + xdg-app-paths: 5.1.0 + yauzl-promise: 2.1.3 + transitivePeerDependencies: + - encoding + - supports-color + + "@vercel/gatsby-plugin-vercel-analytics@1.0.11": + dependencies: + web-vitals: 0.2.4 + + "@vercel/gatsby-plugin-vercel-builder@2.0.56": + dependencies: + "@sinclair/typebox": 0.25.24 + "@vercel/build-utils": 8.4.12 + "@vercel/routing-utils": 3.1.0 + esbuild: 0.14.47 + etag: 1.8.1 + fs-extra: 11.1.0 + + "@vercel/go@3.2.0": {} + + "@vercel/hydrogen@1.0.9": + dependencies: + "@vercel/static-config": 3.0.0 + ts-morph: 12.0.0 + "@vercel/ncc@0.38.2": {} + "@vercel/next@4.3.18": + dependencies: + "@vercel/nft": 0.27.3 + transitivePeerDependencies: + - encoding + - supports-color + + "@vercel/nft@0.27.3": + dependencies: + "@mapbox/node-pre-gyp": 1.0.11 + "@rollup/pluginutils": 4.2.1 + acorn: 8.14.0 + acorn-import-attributes: 1.9.5(acorn@8.14.0) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + node-gyp-build: 4.8.2 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - supports-color + "@vercel/nft@0.27.5": dependencies: "@mapbox/node-pre-gyp": 1.0.11 @@ -17423,6 +18389,78 @@ snapshots: - encoding - supports-color + "@vercel/node@3.2.24": + dependencies: + "@edge-runtime/node-utils": 2.3.0 + "@edge-runtime/primitives": 4.1.0 + "@edge-runtime/vm": 3.2.0 + "@types/node": 16.18.11 + "@vercel/build-utils": 8.4.12 + "@vercel/error-utils": 2.0.2 + "@vercel/nft": 0.27.3 + "@vercel/static-config": 3.0.0 + async-listen: 3.0.0 + cjs-module-lexer: 1.2.3 + edge-runtime: 2.5.9 + es-module-lexer: 1.4.1 + esbuild: 0.14.47 + etag: 1.8.1 + node-fetch: 2.6.9 + path-to-regexp: 6.2.1 + ts-morph: 12.0.0 + ts-node: 10.9.1(@types/node@16.18.11)(typescript@4.9.5) + typescript: 4.9.5 + undici: 5.28.4 + transitivePeerDependencies: + - "@swc/core" + - "@swc/wasm" + - encoding + - supports-color + + "@vercel/python@4.3.1": {} + + "@vercel/redwood@2.1.8": + dependencies: + "@vercel/nft": 0.27.3 + "@vercel/routing-utils": 3.1.0 + "@vercel/static-config": 3.0.0 + semver: 6.3.1 + ts-morph: 12.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + "@vercel/remix-builder@2.2.13": + dependencies: + "@vercel/error-utils": 2.0.2 + "@vercel/nft": 0.27.3 + "@vercel/static-config": 3.0.0 + ts-morph: 12.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + "@vercel/routing-utils@3.1.0": + dependencies: + path-to-regexp: 6.1.0 + optionalDependencies: + ajv: 6.12.6 + + "@vercel/ruby@2.1.0": {} + + "@vercel/static-build@2.5.34": + dependencies: + "@vercel/gatsby-plugin-vercel-analytics": 1.0.11 + "@vercel/gatsby-plugin-vercel-builder": 2.0.56 + "@vercel/static-config": 3.0.0 + ts-morph: 12.0.0 + + "@vercel/static-config@3.0.0": + dependencies: + ajv: 8.6.3 + json-schema-to-ts: 1.6.4 + ts-morph: 12.0.0 + "@vitest/expect@2.1.4": dependencies: "@vitest/spy": 2.1.4 @@ -17430,6 +18468,14 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 + "@vitest/mocker@2.1.4(vite@5.4.10(@types/node@18.19.64)(terser@5.36.0))": + dependencies: + "@vitest/spy": 2.1.4 + estree-walker: 3.0.3 + magic-string: 0.30.12 + optionalDependencies: + vite: 5.4.10(@types/node@18.19.64)(terser@5.36.0) + "@vitest/mocker@2.1.4(vite@5.4.10(@types/node@22.5.5)(terser@5.36.0))": dependencies: "@vitest/spy": 2.1.4 @@ -17611,6 +18657,13 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ajv@8.6.3: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + all-node-versions@11.3.0: dependencies: fetch-node-website: 7.3.0 @@ -17700,6 +18753,8 @@ snapshots: delegates: 1.0.0 readable-stream: 3.6.2 + arg@4.1.0: {} + arg@4.1.3: {} argparse@1.0.10: @@ -17793,6 +18848,12 @@ snapshots: ast-module-types@5.0.0: {} + async-listen@1.2.0: {} + + async-listen@3.0.0: {} + + async-listen@3.0.1: {} + async-sema@3.1.1: {} async@1.5.2: {} @@ -18060,6 +19121,8 @@ snapshots: byline@5.0.0: {} + bytes@3.1.0: {} + bytes@3.1.2: {} cac@6.7.14: {} @@ -18134,6 +19197,18 @@ snapshots: check-error@2.1.1: {} + chokidar@3.3.1: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.3.0 + optionalDependencies: + fsevents: 2.1.3 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -18162,6 +19237,8 @@ snapshots: dependencies: consola: 3.2.3 + cjs-module-lexer@1.2.3: {} + clean-deep@3.4.0: dependencies: lodash.isempty: 4.4.0 @@ -18216,6 +19293,8 @@ snapshots: util: 0.12.5 uuid: 8.3.2 + code-block-writer@10.1.1: {} + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -18361,8 +19440,12 @@ snapshots: dependencies: safe-buffer: 5.2.1 + content-type@1.0.4: {} + content-type@1.0.5: {} + convert-hrtime@3.0.0: {} + convert-source-map@2.0.0: {} cookie-es@1.2.2: {} @@ -18507,6 +19590,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.1.1: + dependencies: + ms: 2.1.3 + debug@4.3.6: dependencies: ms: 2.1.2 @@ -18717,6 +19804,18 @@ snapshots: dependencies: safe-buffer: 5.2.1 + edge-runtime@2.5.9: + dependencies: + "@edge-runtime/format": 2.2.1 + "@edge-runtime/ponyfill": 2.4.2 + "@edge-runtime/vm": 3.2.0 + async-listen: 3.0.1 + mri: 1.2.0 + picocolors: 1.0.0 + pretty-ms: 7.0.1 + signal-exit: 4.0.2 + time-span: 4.0.0 + ee-first@1.1.1: {} electron-to-chromium@1.5.50: {} @@ -18733,6 +19832,10 @@ snapshots: encodeurl@2.0.0: {} + end-of-stream@1.1.0: + dependencies: + once: 1.3.3 + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -18832,6 +19935,8 @@ snapshots: iterator.prototype: 1.1.3 safe-array-concat: 1.1.2 + es-module-lexer@1.4.1: {} + es-module-lexer@1.5.4: {} es-object-atoms@1.0.0: @@ -18856,6 +19961,89 @@ snapshots: es6-promisify@6.1.1: {} + esbuild-android-64@0.14.47: + optional: true + + esbuild-android-arm64@0.14.47: + optional: true + + esbuild-darwin-64@0.14.47: + optional: true + + esbuild-darwin-arm64@0.14.47: + optional: true + + esbuild-freebsd-64@0.14.47: + optional: true + + esbuild-freebsd-arm64@0.14.47: + optional: true + + esbuild-linux-32@0.14.47: + optional: true + + esbuild-linux-64@0.14.47: + optional: true + + esbuild-linux-arm64@0.14.47: + optional: true + + esbuild-linux-arm@0.14.47: + optional: true + + esbuild-linux-mips64le@0.14.47: + optional: true + + esbuild-linux-ppc64le@0.14.47: + optional: true + + esbuild-linux-riscv64@0.14.47: + optional: true + + esbuild-linux-s390x@0.14.47: + optional: true + + esbuild-netbsd-64@0.14.47: + optional: true + + esbuild-openbsd-64@0.14.47: + optional: true + + esbuild-sunos-64@0.14.47: + optional: true + + esbuild-windows-32@0.14.47: + optional: true + + esbuild-windows-64@0.14.47: + optional: true + + esbuild-windows-arm64@0.14.47: + optional: true + + esbuild@0.14.47: + optionalDependencies: + esbuild-android-64: 0.14.47 + esbuild-android-arm64: 0.14.47 + esbuild-darwin-64: 0.14.47 + esbuild-darwin-arm64: 0.14.47 + esbuild-freebsd-64: 0.14.47 + esbuild-freebsd-arm64: 0.14.47 + esbuild-linux-32: 0.14.47 + esbuild-linux-64: 0.14.47 + esbuild-linux-arm: 0.14.47 + esbuild-linux-arm64: 0.14.47 + esbuild-linux-mips64le: 0.14.47 + esbuild-linux-ppc64le: 0.14.47 + esbuild-linux-riscv64: 0.14.47 + esbuild-linux-s390x: 0.14.47 + esbuild-netbsd-64: 0.14.47 + esbuild-openbsd-64: 0.14.47 + esbuild-sunos-64: 0.14.47 + esbuild-windows-32: 0.14.47 + esbuild-windows-64: 0.14.47 + esbuild-windows-arm64: 0.14.47 + esbuild@0.17.19: optionalDependencies: "@esbuild/android-arm": 0.17.19 @@ -19045,19 +20233,19 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.14.0(jiti@2.4.0)): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)): dependencies: "@nolyfill/is-core-module": 1.0.39 debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 9.14.0(jiti@2.4.0) - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0(jiti@2.4.0)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0(jiti@2.4.0)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)) transitivePeerDependencies: - "@typescript-eslint/parser" - eslint-import-resolver-node @@ -19083,14 +20271,14 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0(jiti@2.4.0)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)): dependencies: debug: 3.2.7 optionalDependencies: "@typescript-eslint/parser": 8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3) eslint: 9.14.0(jiti@2.4.0) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.14.0(jiti@2.4.0)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)) transitivePeerDependencies: - supports-color @@ -19110,7 +20298,7 @@ snapshots: gonzales-pe: 4.3.0 lodash: 4.17.21 - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0(jiti@2.4.0)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)): dependencies: "@rtsao/scc": 1.1.0 array-includes: 3.1.8 @@ -19121,7 +20309,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.14.0(jiti@2.4.0) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0(jiti@2.4.0)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -19298,6 +20486,8 @@ snapshots: eventemitter3@5.0.1: {} + events-intercept@2.0.0: {} + events@1.1.1: {} events@3.3.0: {} @@ -19306,6 +20496,19 @@ snapshots: eventsource@2.0.2: {} + execa@3.2.0: + dependencies: + cross-spawn: 7.0.3 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + p-finally: 2.0.1 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + execa@5.1.1: dependencies: cross-spawn: 7.0.3 @@ -19693,12 +20896,31 @@ snapshots: fs-constants@1.0.0: {} + fs-extra@11.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-minipass@1.2.7: + dependencies: + minipass: 2.9.0 + fs-minipass@2.1.0: dependencies: minipass: 3.3.6 fs.realpath@1.0.0: {} + fsevents@2.1.3: + optional: true + fsevents@2.3.3: optional: true @@ -19727,6 +20949,8 @@ snapshots: strip-ansi: 6.0.1 wide-align: 1.1.5 + generic-pool@3.4.2: {} + gensync@1.0.0-beta.2: {} get-amd-module-type@5.0.1: @@ -20011,6 +21235,19 @@ snapshots: http-cache-semantics@4.1.1: {} + http-errors@1.4.0: + dependencies: + inherits: 2.0.1 + statuses: 1.5.0 + + http-errors@1.7.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.1.1 + statuses: 1.5.0 + toidentifier: 1.0.0 + http-errors@1.8.1: dependencies: depd: 1.1.2 @@ -20075,6 +21312,8 @@ snapshots: transitivePeerDependencies: - supports-color + human-signals@1.1.1: {} + human-signals@2.1.0: {} human-signals@3.0.1: {} @@ -20121,6 +21360,8 @@ snapshots: once: 1.4.0 wrappy: 1.0.2 + inherits@2.0.1: {} + inherits@2.0.4: {} ini@1.3.8: {} @@ -20402,6 +21643,8 @@ snapshots: dependencies: system-architecture: 0.1.0 + isarray@0.0.1: {} + isarray@1.0.0: {} isarray@2.0.5: {} @@ -20484,6 +21727,11 @@ snapshots: dependencies: fast-deep-equal: 3.1.3 + json-schema-to-ts@1.6.4: + dependencies: + "@types/json-schema": 7.0.15 + ts-toolbelt: 6.15.5 + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -20498,6 +21746,16 @@ snapshots: jsonc-parser@3.3.1: {} + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jsonpointer@5.0.1: {} jsonwebtoken@9.0.2: @@ -20803,6 +22061,12 @@ snapshots: micro-memoize@4.1.2: {} + micro@9.3.5-canary.3: + dependencies: + arg: 4.1.0 + content-type: 1.0.4 + raw-body: 2.4.1 + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -20869,6 +22133,11 @@ snapshots: minimist@1.2.8: {} + minipass@2.9.0: + dependencies: + safe-buffer: 5.2.1 + yallist: 3.1.1 + minipass@3.3.6: dependencies: yallist: 4.0.0 @@ -20879,6 +22148,10 @@ snapshots: minipass@7.1.2: {} + minizlib@1.3.3: + dependencies: + minipass: 2.9.0 + minizlib@2.1.2: dependencies: minipass: 3.3.6 @@ -20921,6 +22194,8 @@ snapshots: ms@2.0.0: {} + ms@2.1.1: {} + ms@2.1.2: {} ms@2.1.3: {} @@ -21139,6 +22414,14 @@ snapshots: node-fetch-native@1.6.4: {} + node-fetch@2.6.7: + dependencies: + whatwg-url: 5.0.0 + + node-fetch@2.6.9: + dependencies: + whatwg-url: 5.0.0 + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -21322,6 +22605,10 @@ snapshots: on-headers@1.0.2: {} + once@1.3.3: + dependencies: + wrappy: 1.0.2 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -21390,6 +22677,8 @@ snapshots: macos-release: 3.3.0 windows-release: 5.1.1 + os-paths@4.4.0: {} + os-tmpdir@1.0.2: {} p-cancelable@3.0.0: {} @@ -21416,6 +22705,8 @@ snapshots: p-finally@1.0.0: {} + p-finally@2.0.1: {} + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -21518,12 +22809,16 @@ snapshots: index-to-position: 0.1.2 type-fest: 4.26.1 + parse-ms@2.1.0: {} + parse-ms@3.0.0: {} parse-package-name@1.0.0: {} parseurl@1.3.3: {} + path-browserify@1.0.1: {} + path-exists@4.0.0: {} path-exists@5.0.0: {} @@ -21534,6 +22829,11 @@ snapshots: path-key@4.0.0: {} + path-match@1.2.4: + dependencies: + http-errors: 1.4.0 + path-to-regexp: 1.9.0 + path-parse@1.0.7: {} path-scurry@1.10.1: @@ -21553,6 +22853,14 @@ snapshots: path-to-regexp@0.1.10: {} + path-to-regexp@1.9.0: + dependencies: + isarray: 0.0.1 + + path-to-regexp@6.1.0: {} + + path-to-regexp@6.2.1: {} + path-to-regexp@6.3.0: {} path-type@4.0.0: {} @@ -21571,6 +22879,8 @@ snapshots: pend@1.2.0: {} + picocolors@1.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -21706,6 +23016,10 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + pretty-ms@7.0.1: + dependencies: + parse-ms: 2.1.0 + pretty-ms@8.0.0: dependencies: parse-ms: 3.0.0 @@ -21725,6 +23039,8 @@ snapshots: process@0.11.10: {} + promisepipe@3.0.0: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -21798,6 +23114,13 @@ snapshots: range-parser@1.2.1: {} + raw-body@2.4.1: + dependencies: + bytes: 3.1.0 + http-errors: 1.7.3 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + raw-body@2.5.2: dependencies: bytes: 3.1.2 @@ -21888,6 +23211,10 @@ snapshots: dependencies: minimatch: 5.1.6 + readdirp@3.3.0: + dependencies: + picomatch: 2.3.1 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -22117,6 +23444,10 @@ snapshots: semver@6.3.1: {} + semver@7.3.5: + dependencies: + lru-cache: 6.0.0 + semver@7.6.3: {} send@0.19.0: @@ -22166,6 +23497,8 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + setprototypeof@1.1.1: {} + setprototypeof@1.2.0: {} sharp@0.32.6: @@ -22196,6 +23529,8 @@ snapshots: signal-exit@3.0.7: {} + signal-exit@4.0.2: {} + signal-exit@4.1.0: {} simple-concat@1.0.1: {} @@ -22302,6 +23637,8 @@ snapshots: as-table: 1.0.55 get-source: 2.0.12 + stat-mode@0.3.0: {} + statuses@1.5.0: {} statuses@2.0.1: {} @@ -22316,6 +23653,16 @@ snapshots: dependencies: duplexer: 0.1.2 + stream-to-array@2.3.0: + dependencies: + any-promise: 1.3.0 + + stream-to-promise@2.2.0: + dependencies: + any-promise: 1.3.0 + end-of-stream: 1.1.0 + stream-to-array: 2.3.0 + streamsearch@1.1.0: {} streamx@2.16.1: @@ -22545,6 +23892,16 @@ snapshots: fast-fifo: 1.3.2 streamx: 2.16.1 + tar@4.4.18: + dependencies: + chownr: 1.1.4 + fs-minipass: 1.2.7 + minipass: 2.9.0 + minizlib: 1.3.3 + mkdirp: 0.5.6 + safe-buffer: 5.2.1 + yallist: 3.1.1 + tar@6.1.15: dependencies: chownr: 2.0.0 @@ -22622,6 +23979,10 @@ snapshots: through@2.3.8: {} + time-span@4.0.0: + dependencies: + convert-hrtime: 3.0.0 + time-zone@1.0.0: {} tinybench@2.9.0: {} @@ -22659,6 +24020,8 @@ snapshots: toad-cache@3.7.0: {} + toidentifier@1.0.0: {} + toidentifier@1.0.1: {} token-types@5.0.1: @@ -22694,6 +24057,29 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-morph@12.0.0: + dependencies: + "@ts-morph/common": 0.11.1 + code-block-writer: 10.1.1 + + ts-node@10.9.1(@types/node@16.18.11)(typescript@4.9.5): + dependencies: + "@cspotcode/source-map-support": 0.8.1 + "@tsconfig/node10": 1.0.11 + "@tsconfig/node12": 1.0.11 + "@tsconfig/node14": 1.0.3 + "@tsconfig/node16": 1.0.4 + "@types/node": 16.18.11 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + ts-node@10.9.2(@types/node@18.19.64)(typescript@5.6.3): dependencies: "@cspotcode/source-map-support": 0.8.1 @@ -22712,6 +24098,8 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + ts-toolbelt@6.15.5: {} + tsconfig-paths@3.15.0: dependencies: "@types/json5": 0.0.29 @@ -22836,6 +24224,8 @@ snapshots: - eslint - supports-color + typescript@4.9.5: {} + typescript@5.6.3: {} typical@4.0.0: {} @@ -22845,6 +24235,8 @@ snapshots: ufo@1.5.4: {} + uid-promise@1.0.0: {} + uid-safe@2.1.5: dependencies: random-bytes: 1.0.0 @@ -22910,6 +24302,10 @@ snapshots: universal-user-agent@6.0.1: {} + universalify@0.1.2: {} + + universalify@2.0.1: {} + unix-dgram@2.0.6: dependencies: bindings: 1.5.0 @@ -23008,6 +24404,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@3.3.2: {} + uuid@8.0.0: {} uuid@8.3.2: {} @@ -23029,6 +24427,26 @@ snapshots: vary@1.1.2: {} + vercel@37.14.0: + dependencies: + "@vercel/build-utils": 8.4.12 + "@vercel/fun": 1.1.0 + "@vercel/go": 3.2.0 + "@vercel/hydrogen": 1.0.9 + "@vercel/next": 4.3.18 + "@vercel/node": 3.2.24 + "@vercel/python": 4.3.1 + "@vercel/redwood": 2.1.8 + "@vercel/remix-builder": 2.2.13 + "@vercel/ruby": 2.1.0 + "@vercel/static-build": 2.5.34 + chokidar: 3.3.1 + transitivePeerDependencies: + - "@swc/core" + - "@swc/wasm" + - encoding + - supports-color + vite-node@2.1.4(@types/node@18.19.64)(terser@5.36.0): dependencies: cac: 6.7.14 @@ -23083,10 +24501,10 @@ snapshots: fsevents: 2.3.3 terser: 5.36.0 - vitest@2.1.4(@types/node@18.19.64)(terser@5.36.0): + vitest@2.1.4(@edge-runtime/vm@3.2.0)(@types/node@18.19.64)(terser@5.36.0): dependencies: "@vitest/expect": 2.1.4 - "@vitest/mocker": 2.1.4(vite@5.4.10(@types/node@22.5.5)(terser@5.36.0)) + "@vitest/mocker": 2.1.4(vite@5.4.10(@types/node@18.19.64)(terser@5.36.0)) "@vitest/pretty-format": 2.1.4 "@vitest/runner": 2.1.4 "@vitest/snapshot": 2.1.4 @@ -23106,6 +24524,7 @@ snapshots: vite-node: 2.1.4(@types/node@18.19.64)(terser@5.36.0) why-is-node-running: 2.3.0 optionalDependencies: + "@edge-runtime/vm": 3.2.0 "@types/node": 18.19.64 transitivePeerDependencies: - less @@ -23118,7 +24537,7 @@ snapshots: - supports-color - terser - vitest@2.1.4(@types/node@22.5.5)(terser@5.36.0): + vitest@2.1.4(@edge-runtime/vm@3.2.0)(@types/node@22.5.5)(terser@5.36.0): dependencies: "@vitest/expect": 2.1.4 "@vitest/mocker": 2.1.4(vite@5.4.10(@types/node@22.5.5)(terser@5.36.0)) @@ -23141,6 +24560,7 @@ snapshots: vite-node: 2.1.4(@types/node@22.5.5)(terser@5.36.0) why-is-node-running: 2.3.0 optionalDependencies: + "@edge-runtime/vm": 3.2.0 "@types/node": 22.5.5 transitivePeerDependencies: - less @@ -23168,6 +24588,8 @@ snapshots: web-streams-polyfill@3.2.1: {} + web-vitals@0.2.4: {} + webidl-conversions@3.0.1: {} webidl-conversions@4.0.2: {} @@ -23356,8 +24778,16 @@ snapshots: ws@8.18.0: {} + xdg-app-paths@5.1.0: + dependencies: + xdg-portable: 7.3.0 + xdg-basedir@5.1.0: {} + xdg-portable@7.3.0: + dependencies: + os-paths: 4.4.0 + xml2js@0.5.0: dependencies: sax: 1.2.1 @@ -23396,6 +24826,15 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yauzl-clone@1.0.4: + dependencies: + events-intercept: 2.0.0 + + yauzl-promise@2.1.3: + dependencies: + yauzl: 2.10.0 + yauzl-clone: 1.0.4 + yauzl@2.10.0: dependencies: buffer-crc32: 0.2.13 diff --git a/testbed/basic/aws-deploy.js b/testbed/basic/aws-deploy.js index 05e1ffef..a890c445 100644 --- a/testbed/basic/aws-deploy.js +++ b/testbed/basic/aws-deploy.js @@ -14,7 +14,7 @@ import { import fs from "node:fs"; import { bundle } from "@hattip/bundler-aws-lambda"; -const PREFIX = "hattip"; +const PREFIX = "hattip-streaming"; const LAMBDA_EXECUTION_ROLE_NAME = `${PREFIX}-lambda-execution-role`; const FUNCTION_NAME = `${PREFIX}-function`; @@ -127,6 +127,7 @@ async function deploy() { url = await lambda.createFunctionUrlConfig({ FunctionName: FUNCTION_NAME, InvokeMode: "RESPONSE_STREAM", + // InvokeMode: "BUFFERED", AuthType: "NONE", }); } diff --git a/testbed/basic/ci.test.ts b/testbed/basic/ci.test.ts index 2e5855cb..59ebcaa5 100644 --- a/testbed/basic/ci.test.ts +++ b/testbed/basic/ci.test.ts @@ -5,6 +5,7 @@ import { describe, beforeAll, afterAll, + TestFunction, } from "vitest"; import { ChildProcess, spawn } from "node:child_process"; import psTree from "ps-tree"; @@ -15,6 +16,7 @@ import { testFetch } from "./entry-test"; let host: string; let cases: Array<{ name: string; + platform: string; command?: string; envOverride?: Record; fetch?: typeof fetch; @@ -24,6 +26,12 @@ let cases: Array<{ skipStaticFileTest?: boolean; skipAdvancedStaticFileTest?: boolean; skipMultipartTest?: boolean; + skipContentLengthTest?: boolean; + skipDefaultStatusTextTest?: boolean; + skipCustomStatusTextTest?: boolean; + skipRequestCancelationTest?: boolean; + skipRequestStreamingTest?: boolean; + skipFormDataContentTypeCheck?: boolean; tryStreamingWithoutCompression?: boolean; streamingMinimumChunkCount?: number; }>; @@ -32,80 +40,119 @@ const nodeVersions = process.versions.node.split("."); const nodeVersionMajor = +nodeVersions[0]; // const nodeVersionMinor = +nodeVersions[1]; -const bunAvailable = process.platform !== "win32"; - if (process.env.CI === "true") { const noFetchFlag = "--no-experimental-fetch"; - cases = [ + const unfiltered: Array = [ { name: "Test adapter", + platform: "test", fetch: testFetch, requiresForwardedIp: true, skipStaticFileTest: true, + skipContentLengthTest: true, + skipDefaultStatusTextTest: true, }, { name: "Node with native fetch", + platform: "node", command: "node --experimental-fetch entry-node-native-fetch.js", }, { name: "Node with node-fetch", + platform: "node", command: `node ${noFetchFlag} entry-node.js`, skipCryptoTest: nodeVersionMajor < 16, - skipMultipartTest: true, // node-fetch doesn't support streaming + skipMultipartTest: true, // node-fetch doesn't support streaming request bodies }, { name: "Node with @whatwg-node/fetch", + platform: "node", command: `node ${noFetchFlag} entry-node-whatwg.js`, skipCryptoTest: nodeVersionMajor < 16, }, { name: "Node with fast-fetch patch", + platform: "node", command: `node entry-node-fast-fetch.js`, skipCryptoTest: nodeVersionMajor < 16, }, { name: "Deno", + platform: "deno", command: "pnpm build:deno && pnpm start:deno", requiresForwardedIp: true, + skipCustomStatusTextTest: true, }, { name: "Deno with std/http", + platform: "deno", command: "pnpm build:deno-std && pnpm start:deno", + skipCustomStatusTextTest: true, }, { name: "Deno with node:http", + platform: "node", command: "pnpm build:deno-node && pnpm start:deno", requiresForwardedIp: true, + skipCustomStatusTextTest: true, + skipRequestCancelationTest: true, // Deno's node:http doesn't support request cancelation envOverride: { TRUST_PROXY: "1", }, }, - bunAvailable && { + { name: "Bun", + platform: "bun", command: "bun run entry-bun.js", + skipCustomStatusTextTest: true, + skipFormDataContentTypeCheck: true, // Bun doesn't honor content-type of uploads }, - bunAvailable && { + { name: "Bun with node:http", + platform: "node", command: "bun run entry-node-native-fetch.js", + skipCustomStatusTextTest: true, + skipFormDataContentTypeCheck: true, // Bun doesn't honor content-type of uploads + skipRequestStreamingTest: true, // Bun's node:http can't read streaming requests }, { name: "Cloudflare Workers", + platform: "cloudflare-workers", command: "pnpm build:cfw && pnpm start:cfw", streamingMinimumChunkCount: 1, + // CF does support request cancelation but + // wrangler doesn't seem to. + // + // Even on real CF, request.signal's abort event + // isn't fired, the worker is killed instead. + // But the response stream is canceled + // correctly. + skipRequestCancelationTest: true, }, { name: "Netlify Functions with netlify dev", + platform: "netlify-functions", command: "pnpm build:netlify-functions && pnpm start:netlify", skipStreamingTest: true, skipCryptoTest: nodeVersionMajor < 16, + skipContentLengthTest: true, + skipCustomStatusTextTest: true, + // netlify functions doesn't support request cancelation + skipRequestStreamingTest: true, + skipRequestCancelationTest: true, }, { name: "Netlify Edge Functions with netlify dev", + platform: "netlify-edge", command: "pnpm build:netlify-edge && pnpm start:netlify", + skipContentLengthTest: true, + skipCustomStatusTextTest: true, + skipRequestCancelationTest: true, // netlify dev doesn't support request cancelation }, { name: "uWebSockets.js", + platform: "uwebsockets", command: `node entry-uws.js`, }, false && { @@ -117,15 +164,19 @@ if (process.env.CI === "true") { }, { name: "Google Cloud Functions", + platform: "node", command: "functions-framework --port=3000", requiresForwardedIp: true, }, - ].filter(Boolean) as typeof cases; + ]; + + cases = unfiltered.filter(Boolean) as typeof cases; host = "http://127.0.0.1:3000"; } else { cases = [ { name: "Existing server", + platform: process.env.TEST_PLATFORM || "unknown", }, ]; host = process.env.TEST_HOST || "http://127.0.0.1:3000"; @@ -137,8 +188,8 @@ const test = originalTest as typeof originalTest & { test.failsIf = function failsIf(condition) { if (condition) { - function fails(name, fn, options) { - return originalTest.fails(`[EXPECTED FAIL] ${name}`, fn, options); + function fails(name: string, options: any, fn: TestFunction) { + return originalTest.fails(`[EXPECTED FAIL] ${name}`, options, fn); } Object.assign(fails, originalTest); @@ -155,17 +206,24 @@ describe.each(cases)( "$name", ({ name, + platform, command, envOverride, fetch = globalThis.fetch, - requiresForwardedIp, - tryStreamingWithoutCompression, - skipStreamingTest, - skipCryptoTest, - skipStaticFileTest, - skipAdvancedStaticFileTest, - skipMultipartTest: skipMultipartTest, + requiresForwardedIp = false, + tryStreamingWithoutCompression = false, + skipStreamingTest = false, + skipCryptoTest = false, + skipStaticFileTest = false, + skipAdvancedStaticFileTest = false, + skipMultipartTest = false, streamingMinimumChunkCount = 3, + skipContentLengthTest = false, + skipDefaultStatusTextTest = false, + skipCustomStatusTextTest = false, + skipRequestStreamingTest = false, + skipRequestCancelationTest = false, + skipFormDataContentTypeCheck = false, }) => { beforeAll(async () => { const original = fetch; @@ -186,10 +244,6 @@ describe.each(cases)( if (command) { beforeAll(async () => { console.log("Starting", name); - if (skipStreamingTest) { - console.warn("Skipping streaming test for", name); - } - cp = spawn(command, { shell: true, stdio: "inherit", @@ -263,19 +317,34 @@ describe.each(cases)( const response = await fetch(host + "/platform"); const text = await response.text(); expect(response.status).toBe(200); - console.log(text); + expect(text).toBe(`Platform: ${platform}`); }); test("renders HTML", async () => { + const response = await fetch(host); + const text = await response.text(); + + let hostName = host; + if (name === "Lagon") { + // Lagon CLI reports the wrong protocol + hostName = hostName.replace(/^http/, "https"); + } + expect(text).toContain("

Hello from Hattip!

"); + + expect(response.headers.get("content-type")).toEqual( + "text/html; charset=utf-8", + ); + }); + + test("resolves IP and URL", async () => { const response = await fetch( - host, + host + "/ip-and-url", requiresForwardedIp - ? { - headers: { "x-forwarded-for": "127.0.0.1" }, - } + ? { headers: { "x-forwarded-for": "127.0.0.1" } } : undefined, - ); - const text = await response.text(); + ).then((r) => r.json()); + + expect(response.url).toBe(host + "/ip-and-url"); let ip: string; let ip6: string; @@ -291,32 +360,22 @@ describe.each(cases)( } const ip6_2 = "::ffff:" + ip; - - let hostName = host; - if (name === "Lagon") { - // Lagon CLI reports the wrong protocol - hostName = hostName.replace(/^http/, "https"); - } - - const EXPECTED = `

Hello from Hattip!

URL: ${ - hostName + "/" - }

Your IP address is: ${ip}

`; - - const EXPECTED_6 = `

Hello from Hattip!

URL: ${ - hostName + "/" - }

Your IP address is: ${ip6}

`; - - const EXPECTED_6_2 = `

Hello from Hattip!

URL: ${ - hostName + "/" - }

Your IP address is: ${ip6_2}

`; - - expect([EXPECTED, EXPECTED_6, EXPECTED_6_2]).toContain(text); - - expect(response.headers.get("content-type")).toEqual( - "text/html; charset=utf-8", - ); + expect([ip, ip6, ip6_2]).toContain(response.ip); }); + test.failsIf(skipContentLengthTest)( + "renders text with Content-Length", + async () => { + const response = await fetch(host + "/text", { + // Ensure uncompressed response + headers: { "Accept-Encoding": "" }, + }); + const text = await response.text(); + expect(text).toEqual("Hello world!"); + expect(response.headers.get("content-length")).toEqual("12"); + }, + ); + test.failsIf(skipStaticFileTest)("serves static files", async () => { const response = await fetch(host + "/static.txt"); const text = await response.text(); @@ -384,14 +443,43 @@ describe.each(cases)( ); test("echoes text", async () => { - const response = await fetch(host + "/echo-text", { + const body = "Hello world! 😊"; + + // Stream the text to test request body streaming + const text = await fetch(host + "/echo-text", { method: "POST", - body: "Hello world! 😊", - }); - const text = await response.text(); - expect(text).toEqual("Hello world! 😊"); + body, + }).then((r) => r.text()); + + expect(text).toEqual(body); }); + test.failsIf(skipRequestStreamingTest)( + "echoes streaming request text", + async () => { + let index = 0; + const string = "Hello world! 😊"; + const buffer = Buffer.from(string); + + const text = await fetch(host + "/echo-text", { + method: "POST", + body: new ReadableStream({ + pull(controller) { + if (index >= buffer.length) { + controller.close(); + } else { + controller.enqueue(new Uint8Array([buffer[index++]])); + } + }, + }), + // @ts-ignore + duplex: "half", + }).then((r) => r.text()); + + expect(text).toEqual(string); + }, + ); + test("echoes binary", async () => { const response = await fetch(host + "/echo-bin", { method: "POST", @@ -428,6 +516,15 @@ describe.each(cases)( test("sets status", async () => { const response = await fetch(host + "/status"); expect(response.status).toEqual(400); + + if (!skipDefaultStatusTextTest) { + expect(response.statusText).toEqual("Bad Request"); + } + + if (!skipCustomStatusTextTest) { + const response = await fetch(host + "/status?custom"); + expect(response.statusText).toEqual("Custom Status Text"); + } }); test("sends 404", async () => { @@ -463,12 +560,17 @@ describe.each(cases)( expect(r3).toStrictEqual({ data: { sum: 3 } }); }); - test.failsIf(skipMultipartTest)("multipart form data works", async () => { + test("multipart form data works", async () => { const fd = new FormData(); const data = Uint8Array.from( new Array(300).fill(0).map((_, i) => i & 0xff), ); - fd.append("file", new File([data], "hello.txt", { type: "text/plain" })); + fd.append( + "file", + new File([data], "hello.txt", { + type: "application/vnd.hattip.custom", + }), + ); fd.append("text", "Hello world! 😊"); const r = await fetch(host + "/form", { @@ -481,13 +583,47 @@ describe.each(cases)( file: { name: "file", filename: "hello.txt", - unsanitizedFilename: "hello.txt", - contentType: "text/plain", + contentType: skipFormDataContentTypeCheck + ? "text/plain;charset=utf-8" + : "application/vnd.hattip.custom", body: Buffer.from(data).toString("base64"), }, }); }); + test.failsIf(skipMultipartTest)( + "streaming multipart form data works", + async () => { + const fd = new FormData(); + const data = Uint8Array.from( + new Array(300).fill(0).map((_, i) => i & 0xff), + ); + fd.append( + "file", + new File([data], "hello.txt", { + type: "application/vnd.hattip.custom", + }), + ); + fd.append("text", "Hello world! 😊"); + + const r = await fetch(host + "/streaming-form", { + method: "POST", + body: fd, + }).then((r) => r.json()); + + expect(r).toEqual({ + text: "Hello world! 😊", + file: { + name: "file", + filename: "hello.txt", + unsanitizedFilename: "hello.txt", + contentType: "application/vnd.hattip.custom", + body: Buffer.from(data).toString("base64"), + }, + }); + }, + ); + test.failsIf(skipCryptoTest)("session", async () => { const response = await fetch(host + "/session"); const text = await response.text(); @@ -501,32 +637,37 @@ describe.each(cases)( expect(text2).toEqual("You have visited this page 2 time(s)."); }); - /* Only run manually - test.only("cancels response stream when client disconnects", async () => { - const controller = new AbortController(); - const { signal } = controller; - - const response = await fetch(host + "/abort", { signal }); - const stream = response.body!; - - for await (const chunk of stream) { - expect(chunk.slice(0, 4)).toStrictEqual(new Uint8Array([1, 2, 3, 4])); - break; - } + if (!skipStreamingTest) + test.failsIf(skipRequestCancelationTest)( + "cancels response stream when client disconnects", + async () => { + const controller = new AbortController(); + const { signal } = controller; + + const response = await fetch(host + "/abort", { signal }); + const stream = response.body!; + + for await (const chunk of stream) { + expect(chunk.slice(0, 4)).toStrictEqual( + new Uint8Array([1, 2, 3, 4]), + ); + break; + } - controller.abort(); + controller.abort(); - await new Promise((resolve) => { - setTimeout(resolve, 1000); - }); + // Wait untiol the server has detected the abort + for (;;) { + const result = await fetch(host + "/abort-check").then((r) => + r.json(), + ); - const result = await fetch(host + "/abort-check").then((r) => r.json()); - expect(result).toStrictEqual({ - aborted: true, - intervalCleared: true, - }); - }); - */ + if (result.aborted && result.intervalCleared) { + break; + } + } + }, + ); }, ); diff --git a/testbed/basic/entry-netlify-edge.js b/testbed/basic/entry-netlify-edge.js index e1f7a246..c3d983fc 100644 --- a/testbed/basic/entry-netlify-edge.js +++ b/testbed/basic/entry-netlify-edge.js @@ -3,3 +3,7 @@ import adapterNetlifyEdge from "@hattip/adapter-netlify-edge"; import handler from "./index.js"; export default adapterNetlifyEdge(handler); + +export const config = { + path: "/*", +}; diff --git a/testbed/basic/index.js b/testbed/basic/index.js index 8f2b6806..4506534d 100644 --- a/testbed/basic/index.js +++ b/testbed/basic/index.js @@ -16,6 +16,17 @@ app.get("/", (ctx) => { ); }); +app.get("/ip-and-url", (ctx) => { + return json({ + ip: ctx.ip, + url: ctx.url.href, + }); +}); + +app.get("/text", (ctx) => { + return text("Hello world!"); +}); + app.get( "/binary", () => @@ -77,7 +88,16 @@ app.get("/set-cookie", (ctx) => { return text("Cookies set"); }); -app.get("/status", () => new Response(null, { status: 400 })); +app.get("/status", (ctx) => { + if (ctx.url.searchParams.has("custom")) { + return new Response(null, { + status: 400, + statusText: "Custom Status Text", + }); + } + + return new Response(null, { status: 400 }); +}); app.get("/headers", (ctx) => { const headers = Object.fromEntries(ctx.request.headers.entries()); @@ -186,6 +206,23 @@ function toBase64(data) { } app.post("/form", async (ctx) => { + const fd = await ctx.request.formData(); + + /** @type {File} */ + // @ts-expect-error + const file = fd.get("file"); + return json({ + text: fd.get("text"), + file: { + name: "file", + filename: file.name, + contentType: file.type, + body: toBase64(await readAll(file.stream())), + }, + }); +}); + +app.post("/streaming-form", async (ctx) => { const fd = await parseMultipartFormData(ctx.request, { handleFile: async (fileInfo) => ({ ...fileInfo, @@ -211,11 +248,26 @@ let interval; let aborted = false; app.get("/abort", (ctx) => { + /** @type {() => void} */ + let resolve; + /** @type {(reason: unknown) => void} */ + let reject; + /** @type {Promise} */ + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + ctx.request.signal.addEventListener("abort", () => { console.log("Aborted"); aborted = true; + if (interval === undefined) { + resolve(); + } }); + ctx.waitUntil(promise); + return new Response( new ReadableStream({ async start(controller) { @@ -225,8 +277,12 @@ app.get("/abort", (ctx) => { }, 1000); }, cancel() { + console.log("Response stream canceled"); clearInterval(interval); interval = undefined; + if (aborted) { + resolve(); + } }, }), ); diff --git a/testbed/basic/package.json b/testbed/basic/package.json index 7dfea750..b63b2961 100644 --- a/testbed/basic/package.json +++ b/testbed/basic/package.json @@ -8,7 +8,7 @@ "start:native-fetch": "node --experimental-fetch entry-node-native-fetch.js", "build:cfw": "hattip-cloudflare-workers -e entry-cfw.js dist/cloudflare-workers-bundle/index.js", "start:cfw": "wrangler dev --port 3000", - "build:netlify-functions": "rimraf .netlify/edge-functions-dist && hattip-netlify -c --staticDir public --func entry-netlify-function.js", + "build:netlify-functions": "rimraf .netlify/edge-functions && hattip-netlify -c --staticDir public --func entry-netlify-function.js", "build:netlify-edge": "hattip-netlify -c --staticDir public --edge entry-netlify-edge.js", "start:netlify": "cross-env BROWSER=none netlify dev -op 3000", "build:vercel": "hattip-vercel -c --staticDir public --serverless entry-vercel-serverless.js", @@ -58,6 +58,7 @@ "ps-tree": "^1.2.0", "rimraf": "^6.0.1", "typescript": "^5.6.3", + "vercel": "^37.14.0", "vitest": "^2.1.4", "wrangler": "^3.84.1" }, diff --git a/testbed/basic/readme.md b/testbed/basic/readme.md index 929b13c5..6a26ceb8 100644 --- a/testbed/basic/readme.md +++ b/testbed/basic/readme.md @@ -18,18 +18,55 @@ Publish with `wrangler publish`. Build locally with `pnpm build:vercel` and deploy with `vercel deploy --prebuilt`. +Not supported: + +- Custom status text +- Request cancelation + ### Vercel Edge Functions Build locally with `pnpm build:vercel-edge` and deploy with `vercel deploy --prebuilt`. +Not supported: + +- Custom status text +- Request cancelation + ### Netlify Functions (live) Build locally with `pnpm build:netlify-functions`, deploy with `netlify deploy`. +Not supported: + +- Always uses chunked encoding when without compression +- Streaming responses +- Custom status text +- Request cancelation + ### Netlify Edge Functions (live) Build locally with `pnpm build:netlify-edge`, deploy with `netlify deploy`. +Not supported: + +- Always uses chunked encoding when without compression +- Custom status text + ### Deno Deploy Build with `pnpm build:deno`, `cd` into `dist/deno` and deploy with `deployctl deploy --token --project= index.js`. + +Not supported: + +- Custom status text + +### AWS Lambda + +Change `InvokeMode` to whatever you want and deploy with `node aws-deploy.js deploy`. Destroy with `node aws-deploy.js destroy`. + +Not supported: + +- Streaming responses only supported with `@hattip/adapter-aws-lambda/streaming` +- Always uses chunked encoding when without compression +- Custom status text +- Request cancelation