diff --git a/.gitignore b/.gitignore index 49caa42..55dd4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules/ .env .env.* .STEPS.md +demo/ \ No newline at end of file diff --git a/README.md b/README.md index 74ac082..c1780d1 100644 --- a/README.md +++ b/README.md @@ -150,146 +150,3 @@ For URLs influenced by untrusted input, either call `assertSafeHttpUrl(url)` bef ## License MIT - -A small, dependency-free HTTP client for JavaScript runtimes that expose the standard [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API. It supports instances with defaults, request and response interceptors, HTTP verb helpers, optional request/response transforms, composable middleware, retries, structured debug logging, optional JSON validation ([Standard Schema](https://github.com/standard-schema/standard-schema)), and in-memory caching—without legacy browser-only globals. - -**What you get** - -- **ESM-only** — use `import` / `import type`; there is no CommonJS build. **1.x** follows [semantic versioning](https://semver.org/). -- One transport: `fetch` only (Node 18+, Bun, Deno, Cloudflare Workers, browsers). -- No polyfills required for supported environments. -- Safe for server rendering and React Server Components: no `window`, `document`, `localStorage`, or framework coupling. - -## Installation - -```bash -npm install @hamdymohamedak/openfetch -# or pin the stable major line: -npm install @hamdymohamedak/openfetch@^1 -``` - -## Quick start - -```ts -import openFetch, { createClient } from "@hamdymohamedak/openfetch"; - -const { data, status, headers } = await openFetch.get( - "https://api.example.com/v1/users" -); - -const api = createClient({ - baseURL: "https://api.example.com", - headers: { Authorization: "Bearer " }, - timeout: 10_000, - unwrapResponse: true, -}); - -const users = await api.get("/v1/users"); -``` - -## Features - -| Area | Details | -|------|---------| -| Instances | `createClient()` / `create()` with mutable `defaults` | -| HTTP verbs | `request`, `get`, `post`, `put`, `patch`, `delete`, `head`, `options` | -| Config | `baseURL`, `params`, `headers`, `timeout`, `signal`, `data` / `body`, `auth`, `responseType`, `rawResponse`, `validateStatus`, `throwHttpErrors`, `jsonSchema` (Standard Schema), `init` hooks, `debug` / `logger` (pipeline events), `assertSafeUrl`, `unwrapResponse`, plus `RequestInit` fields (`credentials`, `redirect`, …) | -| Interceptors | Request and response stacks (documented call order) | -| Middleware | Async `use()` hooks wrapping the fetch adapter | -| Errors | `OpenFetchError` with `toShape()` / `toJSON()`; `SchemaValidationError` when `jsonSchema` fails | -| Retry | `createRetryMiddleware()` — backoff, `timeoutTotalMs` / `timeoutPerAttemptMs`, idempotent POST key; `OpenFetchForceRetry` from `hooks({ onAfterResponse })` to force another attempt | -| Cache | `MemoryCacheStore` + `createCacheMiddleware()` (TTL, optional stale-while-revalidate) | -| Plugins | `retry({ attempts, … })`, `timeout(ms)`, `hooks({ onBeforeRetry, onAfterResponse, … })`, `debug({ maskStrategy: 'partial' \| 'hash', … })`, `strictFetch()` | -| Fluent API | `createFluentClient()` — lazy chain; **each** `.json()` / `.json(schema)` / `.raw()` / … runs **one** request unless you use `.memo()`; `.raw()` → `Response` | - -Subpath imports (tree-shaking): `@hamdymohamedak/openfetch/plugins`, `@hamdymohamedak/openfetch/sugar`. - -```ts -import { createFluentClient, retry, timeout } from "@hamdymohamedak/openfetch"; - -const client = createFluentClient({ baseURL: "https://api.example.com" }) - .use(retry({ attempts: 3 })) - .use(timeout(5000)); - -const data = await client("/v1/user").json<{ id: string }>(); - -// Native Response (unread body); second terminal = second HTTP call -const res = await client("/v1/export").get().raw(); -const blob = await res.blob(); - -// One HTTP round-trip, then parse as JSON and as text (body buffered once — not HTTP caching) -const memoed = client("/v1/profile").get().memo(); -const profile = await memoed.json(); -const rawText = await memoed.text(); -``` - -Register **`retry` before `timeout`** so retries wrap the full inner stack. Use **interceptors** to mutate config/response; use **`hooks`** for side-effect logging around the middleware pipeline. - -**Fluent:** `.get()` / `.post()` only build config. **Each terminal** (`.json()`, `.text()`, `.send()`, `.raw()`, …) triggers a **new** `fetch` unless the chain used **`.memo()`** (request-level memoization: one `fetch`, body read once into memory). For two reads of the same native `Response`, use **`cloneResponse(res)`** from the package exports (or `.clone()` on the `Response`). - -**`rawResponse` / `.raw()`:** the adapter does **not** read the body and skips **`transformResponse`**. Client **response interceptors** still run (`data` is the native `Response`). Middleware that expects parsed `ctx.response.data` will not see transforms until you parse yourself. - -**Retry timing:** `retry.timeoutTotalMs` measures elapsed time with a monotonic clock (`performance.now()` when available), so the budget is not skewed by system clock changes. By default (`retry.enforceTotalTimeout !== false`), each attempt merges a deadline into the request `signal` so an in-flight `fetch` aborts when the budget runs out (`ERR_RETRY_TIMEOUT`). Set `retry.enforceTotalTimeout: false` to enforce the budget only between attempts. `retry.timeoutPerAttemptMs` sets `timeout` for every attempt inside the retry middleware. Each `dispatch` uses `clearTimeout` in a `finally` block so per-attempt timers are not left dangling. - -**Debug:** Default logs omit request headers. Logged URLs **redact common sensitive query parameters** (`token`, `code`, `password`, …); set `maskUrlQuery: false` to log raw URLs (avoid in production). Use `debug({ includeRequestHeaders: true, maskHeaders: ["authorization"], maskStrategy: "partial" })` for values like `Bearer ****abcd`, or `maskStrategy: "hash"` for a short fingerprint. **`maskHeaderValues`** supports the same strategies when building your own logs. - -### Execution model - -Understanding order helps avoid surprises with retries, timeouts, and escape hatches. - -1. **Request interceptors** run on the merged config (mutations apply to the in-flight request). -2. **Middleware stack** runs in registration order: the **first** `use()` is the **outer** shell; its `next()` enters the next middleware, and the **last** middleware’s `next()` runs the built-in handler that calls **`dispatch`** (`fetch` + parse, unless `rawResponse`). -3. **Inside `dispatch`:** `transformRequest` → `fetch` → (optional body parse) → **`transformResponse`** (skipped when `rawResponse`). -4. **Response interceptors** run on the `OpenFetchResponse` (for `rawResponse`, `data` is still a native `Response`). -5. **Retry** (`createRetryMiddleware` / `retry()`): each retry calls `next()` again, so middleware **below** retry in the stack runs **once per attempt**; middleware **above** retry wraps the whole loop (one outer enter/exit per logical request). -6. **Terminal methods** (fluent `.json()`, `.text()`, client `.get()`, …) each start a **new** pipeline invocation unless you used **`.memo()`** on that chain. - -**Backoff:** between retries, the retry middleware sleeps with jitter; if the request **`signal`** aborts during that wait, the loop stops (`ERR_CANCELED`). - -### Memory cache and authentication - -The default cache key is ``METHOD fullUrl``. The first request with **`Authorization` or `Cookie`** and no `varyHeaderNames` / custom `key` triggers a **one-time `console.warn`** (suppress with `suppressAuthCacheKeyWarning: true` if you only cache public data). For **authenticated or per-user** GETs, also pass header names that affect the response so entries do not leak across users: - -```ts -createCacheMiddleware(store, { - ttlMs: 60_000, - varyHeaderNames: ["authorization", "cookie"], -}); -``` - -Or build a custom `key` and use `appendCacheKeyVaryHeaders` from the package exports. See [SECURITY.md](https://github.com/openfetch-js/OpenFetch/blob/main/SECURITY.md). - -### Retries and POST/PUT - -By default, retries after network failures or retryable HTTP statuses run only for **GET**, **HEAD**, **OPTIONS**, and **TRACE**. To retry mutating methods, set `retry: { retryNonIdempotentMethods: true }` (per client or per request). - -When `retryNonIdempotentMethods` is true and `maxAttempts > 1`, **POST** requests automatically receive a stable **`Idempotency-Key`** header (if you did not set one) so retries share the same key (Stripe-style deduplication). Opt out with `retry: { autoIdempotencyKey: false }`. You can still set `Idempotency-Key` / `idempotency-key` yourself; it will be respected. - -If the request `signal` is aborted (`AbortController.abort()`), the retry middleware stops: no more `fetch` attempts, and backoff ends early when a signal is linked. - -For low-level access without consuming the body in openFetch, set `rawResponse: true` on a request or use fluent `.raw()`. - -### Optional URL guard (server-side) - -For URLs influenced by untrusted input, either call `assertSafeHttpUrl(url)` before requesting or enable **`assertSafeUrl: true`** on the client (defaults or per request). That blocks literal private/loopback IPs for `http:`/`https:` on the fully resolved URL; it does not fix DNS rebinding — see [SECURITY.md](https://github.com/openfetch-js/OpenFetch/blob/main/SECURITY.md). - -### Errors and logging - -`OpenFetchError.toShape()` / `toJSON()` omit `config.auth` and, **by default**, omit response **`data`** and **`headers`**; pass `includeResponseData: true` / `includeResponseHeaders: true` when you need them for trusted diagnostics. By default the serialized `url` **redacts common sensitive query parameters**; pass `redactSensitiveUrlQuery: false` only in trusted environments. The error instance itself can still hold full `config`; do not expose it raw. - -## Documentation - -- **Guide (VitePress):** [openfetch-js.github.io/openfetch-docs/](https://openfetch-js.github.io/openfetch-docs/) -- **Changelog:** [CHANGELOG.md](https://github.com/openfetch-js/OpenFetch/blob/main/CHANGELOG.md) -- **Security:** [SECURITY.md](https://github.com/openfetch-js/OpenFetch/blob/main/SECURITY.md) -- **Claude Code:** `claude plugin marketplace add openfetch-js/OpenFetch`, then `claude plugin install openfetch@openfetch-js`. Published skill plugin: [openFetchSkill — README](https://github.com/openfetch-js/openFetchSkill/blob/main/README.md). -- **Skill folder template (this monorepo):** [examples/claude-skill](https://github.com/openfetch-js/OpenFetch/tree/main/examples/claude-skill) — layout reference; see [examples/README.md](https://github.com/openfetch-js/OpenFetch/blob/main/examples/README.md). -- **Contributing:** [CONTRIBUTING.md](https://github.com/openfetch-js/OpenFetch/blob/main/CONTRIBUTING.md) - -## Requirements - -- Node.js **18** or newer (or any runtime with `fetch` and `AbortController`). - -## License - -MIT diff --git a/dist/domain/types.d.ts b/dist/domain/types.d.ts index de75bcb..1f3bc73 100644 --- a/dist/domain/types.d.ts +++ b/dist/domain/types.d.ts @@ -16,6 +16,18 @@ export type OpenFetchMemoryCacheRequestOptions = { /** Bypass memory cache read/write for this request. */ skip?: boolean; }; +/** Byte progress for {@link OpenFetchConfig.onUploadProgress} / {@link OpenFetchConfig.onDownloadProgress}. */ +export type OpenFetchProgressEvent = { + /** Bytes read from the response body or consumed from the request body so far. */ + transferredBytes: number; + /** + * Known total when available (`Content-Length` for downloads; measured body size for common upload types). + * `null` when unknown (e.g. chunked transfer, user-provided `ReadableStream` upload without length). + */ + totalBytes: number | null; + /** 0–100 when `totalBytes` is a positive number; otherwise `null` (unknown total). */ + percent: number | null; +}; export type OpenFetchConfig = { url?: string | URL; baseURL?: string; @@ -90,6 +102,28 @@ export type OpenFetchConfig = { debug?: boolean | "basic" | "verbose"; /** Custom sink for structured {@link OpenFetchDebugEvent} records when `debug` is enabled. */ logger?: (log: OpenFetchDebugEvent) => void; + /** + * Called as the **request** body is read by `fetch` (bytes leaving the client). + * Best-effort: `FormData` and other bodies that cannot be wrapped as a counting stream are unchanged — no events. + */ + onUploadProgress?: (event: OpenFetchProgressEvent) => void; + /** + * Called as the **response** body is read. `totalBytes` / `percent` use `Content-Length` when present; + * otherwise `totalBytes` and `percent` stay `null` while `transferredBytes` still increases. + */ + onDownloadProgress?: (event: OpenFetchProgressEvent) => void; + /** + * Node.js (Undici): forwarded to `fetch` when the runtime supports it (Undici `dispatcher` option). + * Use for custom agents, proxies, or HTTP/2 — e.g. `new Agent({ allowH2: true })` from `undici`. + * When set, it takes precedence over {@link allowH2}. + */ + dispatcher?: unknown; + /** + * Node.js: shorthand for Undici `new Agent({ allowH2: true })` passed as `fetch(..., { dispatcher })`. + * Requires the `undici` package to be installed (optional peer). Ignored when {@link dispatcher} is set. + * No-op / may be ignored on runtimes whose `fetch` does not accept `dispatcher` (e.g. browsers). + */ + allowH2?: boolean; } & Partial>; export type OpenFetchResponse = { data: T; diff --git a/dist/domain/types.d.ts.map b/dist/domain/types.d.ts.map index 0010376..8c23f33 100644 --- a/dist/domain/types.d.ts.map +++ b/dist/domain/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/domain/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/domain/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts index f88891a..e3b935d 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -19,5 +19,5 @@ export { cloneResponse } from "./shared/cloneResponse.js"; export { SchemaValidationError, isSchemaValidationError, } from "./domain/schemaValidationError.js"; export { OpenFetchForceRetry, isOpenFetchForceRetry, } from "./domain/forceRetry.js"; export type { StandardSchemaV1, StandardSchemaV1InferOutput, StandardSchemaV1Issue, StandardSchemaV1Options, StandardSchemaV1Result, StandardSchemaV1SuccessResult, StandardSchemaV1FailureResult, StandardSchemaV1Types, } from "./domain/standardSchema.js"; -export type { Middleware, NextFn, OpenFetchClient, OpenFetchConfig, OpenFetchContext, OpenFetchDebugEvent, OpenFetchInterceptors, OpenFetchMemoryCacheRequestOptions, OpenFetchResponse, OpenFetchRetryOptions, RequestConfig, TransformRequest, TransformResponse, } from "./types/index.js"; +export type { Middleware, NextFn, OpenFetchClient, OpenFetchConfig, OpenFetchContext, OpenFetchDebugEvent, OpenFetchInterceptors, OpenFetchMemoryCacheRequestOptions, OpenFetchProgressEvent, OpenFetchResponse, OpenFetchRetryOptions, RequestConfig, TransformRequest, TransformResponse, } from "./types/index.js"; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map index d7e4e39..6999d46 100644 --- a/dist/index.d.ts.map +++ b/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE3D,QAAA,MAAM,SAAS,sCAAiB,CAAC;AAEjC,eAAe,SAAS,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;AAEhC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,EACL,KAAK,EACL,OAAO,EACP,KAAK,EACL,KAAK,EACL,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,UAAU,GACX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,mBAAmB,EACnB,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACL,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,mCAAmC,EACnC,KAAK,qBAAqB,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,EACL,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,gBAAgB,EAChB,2BAA2B,EAC3B,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,6BAA6B,EAC7B,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACV,UAAU,EACV,MAAM,EACN,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,kCAAkC,EAClC,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE3D,QAAA,MAAM,SAAS,sCAAiB,CAAC;AAEjC,eAAe,SAAS,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;AAEhC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,EACL,KAAK,EACL,OAAO,EACP,KAAK,EACL,KAAK,EACL,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,UAAU,GACX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,mBAAmB,EACnB,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACL,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,mCAAmC,EACnC,KAAK,qBAAqB,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,EACL,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,gBAAgB,EAChB,2BAA2B,EAC3B,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,6BAA6B,EAC7B,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACV,UAAU,EACV,MAAM,EACN,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,kCAAkC,EAClC,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC"} \ No newline at end of file diff --git a/dist/shared/progress.d.ts b/dist/shared/progress.d.ts new file mode 100644 index 0000000..6f62cf7 --- /dev/null +++ b/dist/shared/progress.d.ts @@ -0,0 +1,10 @@ +import type { OpenFetchProgressEvent } from "../domain/types.js"; +export declare function parsePositiveContentLength(headerValue: string | null): number | null; +export declare function progressFromCounts(transferredBytes: number, totalBytes: number | null): OpenFetchProgressEvent; +/** + * Wraps a request body so `onUploadProgress` fires as bytes are read by `fetch`. + * Unsupported shapes (`FormData`, etc.) are returned unchanged — no progress events. + */ +export declare function wrapBodyForUploadProgress(body: BodyInit, onUploadProgress: (e: OpenFetchProgressEvent) => void): BodyInit; +export declare function wrapReadableStreamWithDownloadProgress(body: ReadableStream, totalBytes: number | null, onDownloadProgress: (e: OpenFetchProgressEvent) => void): ReadableStream; +//# sourceMappingURL=progress.d.ts.map \ No newline at end of file diff --git a/dist/shared/progress.d.ts.map b/dist/shared/progress.d.ts.map new file mode 100644 index 0000000..95e87e2 --- /dev/null +++ b/dist/shared/progress.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/shared/progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,MAAM,GAAG,IAAI,CAKf;AAED,wBAAgB,kBAAkB,CAChC,gBAAgB,EAAE,MAAM,EACxB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,sBAAsB,CAUxB;AAoDD;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,QAAQ,EACd,gBAAgB,EAAE,CAAC,CAAC,EAAE,sBAAsB,KAAK,IAAI,GACpD,QAAQ,CAqCV;AAED,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,kBAAkB,EAAE,CAAC,CAAC,EAAE,sBAAsB,KAAK,IAAI,GACtD,cAAc,CAAC,UAAU,CAAC,CAM5B"} \ No newline at end of file diff --git a/dist/shared/progress.js b/dist/shared/progress.js new file mode 100644 index 0000000..fe30e30 --- /dev/null +++ b/dist/shared/progress.js @@ -0,0 +1,91 @@ +export function parsePositiveContentLength(headerValue) { + if (headerValue == null || headerValue === "") + return null; + const n = Number.parseInt(headerValue, 10); + if (!Number.isFinite(n) || n < 0) + return null; + return n; +} +export function progressFromCounts(transferredBytes, totalBytes) { + let percent = null; + if (totalBytes != null) { + if (totalBytes <= 0) { + percent = transferredBytes <= 0 ? 100 : null; + } + else { + percent = Math.min(100, (transferredBytes / totalBytes) * 100); + } + } + return { transferredBytes, totalBytes, percent }; +} +const UPLOAD_CHUNK = 64 * 1024; +function countingReadableStreamFromSource(source, totalBytes, onProgress) { + let transferred = 0; + const reader = source.getReader(); + return new ReadableStream({ + async pull(controller) { + const { done, value } = await reader.read(); + if (done) { + onProgress(progressFromCounts(transferred, totalBytes)); + controller.close(); + return; + } + transferred += value.byteLength; + onProgress(progressFromCounts(transferred, totalBytes)); + controller.enqueue(value); + }, + cancel(reason) { + return reader.cancel(reason); + }, + }); +} +function countingReadableStreamFromUint8Array(data, onProgress) { + const totalBytes = data.byteLength; + let offset = 0; + return new ReadableStream({ + start() { + onProgress(progressFromCounts(0, totalBytes)); + }, + pull(controller) { + if (offset >= totalBytes) { + controller.close(); + return; + } + const end = Math.min(offset + UPLOAD_CHUNK, totalBytes); + controller.enqueue(data.subarray(offset, end)); + offset = end; + onProgress(progressFromCounts(offset, totalBytes)); + }, + }); +} +/** + * Wraps a request body so `onUploadProgress` fires as bytes are read by `fetch`. + * Unsupported shapes (`FormData`, etc.) are returned unchanged — no progress events. + */ +export function wrapBodyForUploadProgress(body, onUploadProgress) { + if (typeof body === "string") { + const bytes = new TextEncoder().encode(body); + return countingReadableStreamFromUint8Array(bytes, onUploadProgress); + } + if (body instanceof URLSearchParams) { + const bytes = new TextEncoder().encode(body.toString()); + return countingReadableStreamFromUint8Array(bytes, onUploadProgress); + } + if (body instanceof Blob) { + return countingReadableStreamFromSource(body.stream(), typeof body.size === "number" ? body.size : null, onUploadProgress); + } + if (body instanceof ArrayBuffer) { + return countingReadableStreamFromUint8Array(new Uint8Array(body), onUploadProgress); + } + if (ArrayBuffer.isView(body)) { + const v = body; + return countingReadableStreamFromUint8Array(new Uint8Array(v.buffer, v.byteOffset, v.byteLength), onUploadProgress); + } + if (body instanceof ReadableStream) { + return countingReadableStreamFromSource(body, null, onUploadProgress); + } + return body; +} +export function wrapReadableStreamWithDownloadProgress(body, totalBytes, onDownloadProgress) { + return countingReadableStreamFromSource(body, totalBytes, onDownloadProgress); +} diff --git a/dist/shared/undiciDispatcher.d.ts b/dist/shared/undiciDispatcher.d.ts new file mode 100644 index 0000000..f6d4dc5 --- /dev/null +++ b/dist/shared/undiciDispatcher.d.ts @@ -0,0 +1,7 @@ +import type { OpenFetchConfig } from "../domain/types.js"; +/** + * Resolves Undici `fetch` `dispatcher` from {@link OpenFetchConfig.dispatcher} or {@link OpenFetchConfig.allowH2}. + * When neither applies, returns `undefined` without importing `undici`. + */ +export declare function resolveFetchDispatcher(config: OpenFetchConfig): Promise; +//# sourceMappingURL=undiciDispatcher.d.ts.map \ No newline at end of file diff --git a/dist/shared/undiciDispatcher.d.ts.map b/dist/shared/undiciDispatcher.d.ts.map new file mode 100644 index 0000000..94fb3ef --- /dev/null +++ b/dist/shared/undiciDispatcher.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"undiciDispatcher.d.ts","sourceRoot":"","sources":["../../src/shared/undiciDispatcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI1D;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAqB9B"} \ No newline at end of file diff --git a/dist/shared/undiciDispatcher.js b/dist/shared/undiciDispatcher.js new file mode 100644 index 0000000..64c2f4e --- /dev/null +++ b/dist/shared/undiciDispatcher.js @@ -0,0 +1,26 @@ +import { OpenFetchError } from "../domain/error.js"; +let cachedAllowH2Agent; +/** + * Resolves Undici `fetch` `dispatcher` from {@link OpenFetchConfig.dispatcher} or {@link OpenFetchConfig.allowH2}. + * When neither applies, returns `undefined` without importing `undici`. + */ +export async function resolveFetchDispatcher(config) { + if (config.dispatcher != null) { + return config.dispatcher; + } + if (config.allowH2 !== true) { + return undefined; + } + if (cachedAllowH2Agent !== undefined) { + return cachedAllowH2Agent; + } + let undici; + try { + undici = await import("undici"); + } + catch { + throw new OpenFetchError("OpenFetch: `allowH2: true` requires the `undici` package (dynamic import failed). Install: npm i undici", { config, code: "ERR_UNDICI_REQUIRED" }); + } + cachedAllowH2Agent = new undici.Agent({ allowH2: true }); + return cachedAllowH2Agent; +} diff --git a/dist/transport/dispatch.d.ts.map b/dist/transport/dispatch.d.ts.map index 554e412..0621005 100644 --- a/dist/transport/dispatch.d.ts.map +++ b/dist/transport/dispatch.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/transport/dispatch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAgG7E,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG;IAAE,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAC;AAErE,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAqO5B"} \ No newline at end of file +{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/transport/dispatch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EAEf,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AA+L5B,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG;IAAE,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAC;AAErE,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,iBAAiB,CAAC,CA+R5B"} \ No newline at end of file diff --git a/dist/transport/dispatch.js b/dist/transport/dispatch.js index d21e7f4..7438f3d 100644 --- a/dist/transport/dispatch.js +++ b/dist/transport/dispatch.js @@ -6,7 +6,9 @@ import { buildURL } from "../shared/buildURL.js"; import { encodeBasicAuth } from "../shared/basicAuth.js"; import { clearOpenFetchDebugAttempt, emitOpenFetchDebug, getOpenFetchDebugAttempt, headersForDebugLog, monotonicNowMs, redactUrlForDebug, } from "../shared/openFetchDebug.js"; import { mergeAbortSignals } from "../shared/mergeAbortSignals.js"; +import { parsePositiveContentLength, progressFromCounts, wrapBodyForUploadProgress, wrapReadableStreamWithDownloadProgress, } from "../shared/progress.js"; import { headersToRecord } from "../shared/responseHeaders.js"; +import { resolveFetchDispatcher } from "../shared/undiciDispatcher.js"; const defaultValidateStatus = (status) => status >= 200 && status < 300; function resolveValidateStatus(config) { if (config.validateStatus) { @@ -45,6 +47,18 @@ function normalizeHeaders(h) { } return out; } +function concatUint8Arrays(chunks) { + let len = 0; + for (const c of chunks) + len += c.byteLength; + const out = new Uint8Array(len); + let o = 0; + for (const c of chunks) { + out.set(c, o); + o += c.byteLength; + } + return out; +} async function parseBody(res, responseType) { if (responseType === "arraybuffer") return res.arrayBuffer(); @@ -80,6 +94,68 @@ async function parseBody(res, responseType) { } return res.text(); } +async function parseBodyWithDownloadProgress(res, responseType, totalBytes, onDownloadProgress) { + if (responseType === "stream") { + if (!res.body) { + onDownloadProgress(progressFromCounts(0, totalBytes)); + return res.body; + } + onDownloadProgress(progressFromCounts(0, totalBytes)); + return wrapReadableStreamWithDownloadProgress(res.body, totalBytes, onDownloadProgress); + } + onDownloadProgress(progressFromCounts(0, totalBytes)); + if (!res.body) { + return parseBody(res, responseType); + } + const reader = res.body.getReader(); + const chunks = []; + let transferred = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) + break; + transferred += value.byteLength; + chunks.push(value); + onDownloadProgress(progressFromCounts(transferred, totalBytes)); + } + const bytes = concatUint8Arrays(chunks); + if (responseType === "arraybuffer") { + return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); + } + if (responseType === "blob") { + const buf = bytes.buffer; + return new Blob([ + buf.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength), + ]); + } + if (responseType === "text") + return new TextDecoder().decode(bytes); + if (responseType === "json") { + const t = new TextDecoder().decode(bytes); + if (!t.trim()) + return null; + try { + return JSON.parse(t); + } + catch { + return t; + } + } + const ct = res.headers.get("content-type"); + const asJson = ct?.includes("application/json") ?? false; + if (asJson) { + const t = new TextDecoder().decode(bytes); + if (!t.trim()) + return null; + try { + return JSON.parse(t); + } + catch { + return t; + } + } + return new TextDecoder().decode(bytes); +} export async function dispatch(config) { const urlString = buildURL(config.url, config); if (config.assertSafeUrl === true) { @@ -110,6 +186,11 @@ export async function dispatch(config) { } body = JSON.stringify(body); } + if (body !== undefined && + body !== null && + config.onUploadProgress != null) { + body = wrapBodyForUploadProgress(body, config.onUploadProgress); + } const credentials = config.withCredentials === true ? "include" : (config.credentials ?? undefined); @@ -132,8 +213,9 @@ export async function dispatch(config) { headers: headersForDebugLog(headers), }); try { + const dispatcher = await resolveFetchDispatcher(config); const tFetch = monotonicNowMs(); - const res = await fetch(urlString, { + const fetchInit = { method: (config.method ?? "GET").toUpperCase(), headers, body: body === undefined ? undefined : body, @@ -146,7 +228,18 @@ export async function dispatch(config) { redirect: config.redirect, referrer: config.referrer, referrerPolicy: config.referrerPolicy, - }); + }; + if (body !== undefined && + body !== null && + typeof ReadableStream !== "undefined" && + body instanceof ReadableStream) { + // Node (Undici) requires `duplex` when `body` is a stream (e.g. upload progress wrapping). + fetchInit.duplex = "half"; + } + if (dispatcher != null) { + fetchInit.dispatcher = dispatcher; + } + const res = await fetch(urlString, fetchInit); const fetchDurationMs = Math.round(monotonicNowMs() - tFetch); const contentLength = res.headers.get("content-length"); emitOpenFetchDebug(config, "fetch_complete", { @@ -157,9 +250,24 @@ export async function dispatch(config) { contentLength: contentLength ?? undefined, }); const headerRecord = headersToRecord(res.headers); + const downloadTotalBytes = parsePositiveContentLength(res.headers.get("content-length")); if (config.rawResponse === true) { + let responseForClient = res; + if (config.onDownloadProgress != null) { + if (res.body) { + config.onDownloadProgress(progressFromCounts(0, downloadTotalBytes)); + responseForClient = new Response(wrapReadableStreamWithDownloadProgress(res.body, downloadTotalBytes, config.onDownloadProgress), { + status: res.status, + statusText: res.statusText, + headers: res.headers, + }); + } + else { + config.onDownloadProgress(progressFromCounts(0, downloadTotalBytes)); + } + } const openResponse = { - data: res, + data: responseForClient, status: res.status, statusText: res.statusText, headers: headerRecord, @@ -182,7 +290,10 @@ export async function dispatch(config) { } let parsed; try { - parsed = await parseBody(res, config.responseType); + parsed = + config.onDownloadProgress != null + ? await parseBodyWithDownloadProgress(res, config.responseType, downloadTotalBytes, config.onDownloadProgress) + : await parseBody(res, config.responseType); emitOpenFetchDebug(config, "parse", { attempt, ok: true, diff --git a/package-lock.json b/package-lock.json index 42873fc..59a439e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "express": "^5.2.1", "playwright": "^1.58.2", "tsx": "^4.21.0", - "typescript": "^5.7.0" + "typescript": "^5.7.0", + "undici": "^7.25.0" }, "engines": { "node": ">=18.0.0" @@ -3560,6 +3561,16 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", diff --git a/package.json b/package.json index 4cd6605..eb9bce0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hamdymohamedak/openfetch", "version": "1.0.0", - "description": "OpenFetch is a lean, TypeScript-first HTTP client on the native Fetch API—middleware and interceptors instead of a monolithic core, with plug-in retries, timeouts, lifecycle hooks, and structured debug logging. Optional in-memory caching, fluent chaining via the sugar export, and small utilities for safer URLs, idempotency keys, and redacted logs (headers and query strings) keep requests observable without leaking secrets. ESM-only, zero runtime dependencies, tree-shakeable exports; runs on Node 18+, browsers, Bun, Deno, and edge runtimes (e.g. Cloudflare Workers), with a design that plays well with Server Components", + "description": "OpenFetch is a lean, TypeScript-first HTTP client on the native Fetch API—middleware and interceptors instead of a monolithic core, with plug-in retries, timeouts, lifecycle hooks, and structured debug logging. Optional in-memory caching, fluent chaining via the sugar export, and small utilities for safer URLs, idempotency keys, and redacted logs (headers and query strings) keep requests observable without leaking secrets. ESM-only, no required runtime dependencies beyond the platform `fetch`, tree-shakeable exports; optional peer `undici` enables Node HTTP/2 via `allowH2` or a custom `dispatcher`. Runs on Node 18+, browsers, Bun, Deno, and edge runtimes (e.g. Cloudflare Workers), with a design that plays well with Server Components", "type": "module", "sideEffects": false, "main": "./dist/index.js", @@ -31,6 +31,8 @@ ], "scripts": { "build": "tsc", + "demo:progress": "npm run build && node demo/upload-download-progress/demo-progress.mjs", + "demo:proxy-h2": "npm run build && node demo/proxy-h2/demo-proxy-h2.mjs", "prepublishOnly": "npm run build", "test:security": "npm run build && node security-tests/run.mjs", "test:unit": "npm run build && node --test test/*.test.mjs", @@ -67,6 +69,8 @@ "isomorphic", "universal", "zero-dependencies", + "http2", + "undici", "fluent", "abortcontroller", "axios-alternative", @@ -82,6 +86,14 @@ "bugs": { "url": "https://github.com/openfetch-js/OpenFetch/issues" }, + "peerDependencies": { + "undici": ">=6.0.0" + }, + "peerDependenciesMeta": { + "undici": { + "optional": true + } + }, "devDependencies": { "@types/express": "^5.0.6", "@types/node": "^22.10.0", @@ -89,7 +101,8 @@ "express": "^5.2.1", "playwright": "^1.58.2", "tsx": "^4.21.0", - "typescript": "^5.7.0" + "typescript": "^5.7.0", + "undici": "^7.25.0" }, "ava": { "extensions": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..6f25099 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2222 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/express': + specifier: ^5.0.6 + version: 5.0.6 + '@types/node': + specifier: ^22.10.0 + version: 22.19.17 + ava: + specifier: ^6.4.1 + version: 6.4.1 + express: + specifier: ^5.2.1 + version: 5.2.1 + playwright: + specifier: ^1.58.2 + version: 1.59.1 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + undici: + specifier: ^7.25.0 + version: 7.25.0 + +packages: + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@mapbox/node-pre-gyp@2.0.3': + resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} + engines: {node: '>=18'} + hasBin: true + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + + '@types/qs@6.15.0': + resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@vercel/nft@0.29.4': + resolution: {integrity: sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==} + engines: {node: '>=18'} + hasBin: true + + abbrev@3.0.1: + resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} + engines: {node: ^18.17.0 || >=20.5.0} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + array-find-index@1.0.2: + resolution: {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==} + engines: {node: '>=0.10.0'} + + arrgv@1.0.2: + resolution: {integrity: sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==} + engines: {node: '>=8.0.0'} + + arrify@3.0.0: + resolution: {integrity: sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==} + engines: {node: '>=12'} + + async-sema@3.1.1: + resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + + ava@6.4.1: + resolution: {integrity: sha512-vxmPbi1gZx9zhAjHBgw81w/iEDKcrokeRk/fqDTyA2DQygZ0o+dUGRHFOtX8RA5N0heGJTTsIk7+xYxitDb61Q==} + engines: {node: ^18.18 || ^20.8 || ^22 || ^23 || >=24} + hasBin: true + peerDependencies: + '@ava/typescript': '*' + peerDependenciesMeta: + '@ava/typescript': + optional: true + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + blueimp-md5@2.19.0: + resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@4.2.0: + resolution: {integrity: sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==} + engines: {node: '>=12.20'} + + cbor@10.0.12: + resolution: {integrity: sha512-exQDevYd7ZQLP4moMQcZkKCVZsXLAtUSflObr3xTh4xzFIv/xBCdvCd6L259kQOUP2kcTC0jvC6PpZIf/WmRXA==} + engines: {node: '>=20'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + chunkd@2.0.1: + resolution: {integrity: sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + ci-parallel-vars@1.0.1: + resolution: {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + code-excerpt@4.0.0: + resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + common-path-prefix@3.0.0: + resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} + + concordance@5.0.4: + resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} + engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-to-spaces@2.0.1: + resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + currently-unhandled@0.4.1: + resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} + engines: {node: '>=0.10.0'} + + date-time@3.1.0: + resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} + engines: {node: '>=6'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + emittery@1.2.1: + resolution: {integrity: sha512-sFz64DCRjirhwHLxofFqxYQm6DCp6o0Ix7jwKQvuCHPn4GMRZNuBZyLPu9Ccmk/QSCAMZt6FOUqA8JZCQvA9fw==} + engines: {node: '>=14.16'} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + globby@14.1.0: + resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ignore-by-default@2.1.0: + resolution: {integrity: sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==} + engines: {node: '>=10 <11 || >=12 <13 || >=14'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + irregular-plurals@3.5.0: + resolution: {integrity: sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + js-string-escape@1.0.1: + resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} + engines: {node: '>= 0.8'} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + load-json-file@7.0.1: + resolution: {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + matcher@5.0.0: + resolution: {integrity: sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + md5-hex@3.0.1: + resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} + engines: {node: '>=8'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + memoize@10.2.0: + resolution: {integrity: sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==} + engines: {node: '>=18'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + nofilter@3.1.0: + resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} + engines: {node: '>=12.19'} + + nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + p-map@7.0.4: + resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} + engines: {node: '>=18'} + + package-config@5.0.0: + resolution: {integrity: sha512-GYTTew2slBcYdvRHqjhwaaydVMvn/qrGC323+nKclYioNSLTDUM/lGgtGTgyHVtYcozb+XkE8CNhwcraOmZ9Mg==} + engines: {node: '>=18'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + path-type@6.0.0: + resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} + engines: {node: '>=18'} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + playwright-core@1.59.1: + resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.59.1: + resolution: {integrity: sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==} + engines: {node: '>=18'} + hasBin: true + + plur@5.1.0: + resolution: {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + supertap@3.0.1: + resolution: {integrity: sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + tar@7.5.13: + resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==} + engines: {node: '>=18'} + + temp-dir@3.0.0: + resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} + engines: {node: '>=14.16'} + + time-zone@1.0.0: + resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + engines: {node: '>=20.18.1'} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + well-known-symbols@2.0.0: + resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} + engines: {node: '>=6'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@6.0.0: + resolution: {integrity: sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + +snapshots: + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.3 + + '@mapbox/node-pre-gyp@2.0.3': + dependencies: + consola: 3.4.2 + detect-libc: 2.1.2 + https-proxy-agent: 7.0.6 + node-fetch: 2.7.0 + nopt: 8.1.0 + semver: 7.7.4 + tar: 7.5.13 + transitivePeerDependencies: + - encoding + - supports-color + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/pluginutils@5.3.0': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.4 + + '@sindresorhus/merge-streams@2.3.0': {} + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 22.19.17 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 22.19.17 + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 22.19.17 + '@types/qs': 6.15.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + + '@types/http-errors@2.0.5': {} + + '@types/node@22.19.17': + dependencies: + undici-types: 6.21.0 + + '@types/qs@6.15.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@1.2.1': + dependencies: + '@types/node': 22.19.17 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 22.19.17 + + '@vercel/nft@0.29.4': + dependencies: + '@mapbox/node-pre-gyp': 2.0.3 + '@rollup/pluginutils': 5.3.0 + acorn: 8.16.0 + acorn-import-attributes: 1.9.5(acorn@8.16.0) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 10.5.0 + graceful-fs: 4.2.11 + node-gyp-build: 4.8.4 + picomatch: 4.0.4 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + abbrev@3.0.1: {} + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-import-attributes@1.9.5(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn-walk@8.3.5: + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + agent-base@7.1.4: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + array-find-index@1.0.2: {} + + arrgv@1.0.2: {} + + arrify@3.0.0: {} + + async-sema@3.1.1: {} + + ava@6.4.1: + dependencies: + '@vercel/nft': 0.29.4 + acorn: 8.16.0 + acorn-walk: 8.3.5 + ansi-styles: 6.2.3 + arrgv: 1.0.2 + arrify: 3.0.0 + callsites: 4.2.0 + cbor: 10.0.12 + chalk: 5.6.2 + chunkd: 2.0.1 + ci-info: 4.4.0 + ci-parallel-vars: 1.0.1 + cli-truncate: 4.0.0 + code-excerpt: 4.0.0 + common-path-prefix: 3.0.0 + concordance: 5.0.4 + currently-unhandled: 0.4.1 + debug: 4.4.3 + emittery: 1.2.1 + figures: 6.1.0 + globby: 14.1.0 + ignore-by-default: 2.1.0 + indent-string: 5.0.0 + is-plain-object: 5.0.0 + is-promise: 4.0.0 + matcher: 5.0.0 + memoize: 10.2.0 + ms: 2.1.3 + p-map: 7.0.4 + package-config: 5.0.0 + picomatch: 4.0.4 + plur: 5.1.0 + pretty-ms: 9.3.0 + resolve-cwd: 3.0.0 + stack-utils: 2.0.6 + strip-ansi: 7.2.0 + supertap: 3.0.1 + temp-dir: 3.0.0 + write-file-atomic: 6.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + balanced-match@1.0.2: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + blueimp-md5@2.19.0: {} + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + brace-expansion@2.1.0: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@4.2.0: {} + + cbor@10.0.12: + dependencies: + nofilter: 3.1.0 + + chalk@5.6.2: {} + + chownr@3.0.0: {} + + chunkd@2.0.1: {} + + ci-info@4.4.0: {} + + ci-parallel-vars@1.0.1: {} + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + code-excerpt@4.0.0: + dependencies: + convert-to-spaces: 2.0.1 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + common-path-prefix@3.0.0: {} + + concordance@5.0.4: + dependencies: + date-time: 3.1.0 + esutils: 2.0.3 + fast-diff: 1.3.0 + js-string-escape: 1.0.1 + lodash: 4.18.1 + md5-hex: 3.0.1 + semver: 7.7.4 + well-known-symbols: 2.0.0 + + consola@3.4.2: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + convert-to-spaces@2.0.1: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + currently-unhandled@0.4.1: + dependencies: + array-find-index: 1.0.2 + + date-time@3.1.0: + dependencies: + time-zone: 1.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + depd@2.0.0: {} + + detect-libc@2.1.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + emittery@1.2.1: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@5.0.0: {} + + esprima@4.0.1: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + file-uri-to-path@1.0.0: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-up-simple@1.0.1: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.5.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globby@14.1.0: + dependencies: + '@sindresorhus/merge-streams': 2.3.0 + fast-glob: 3.3.3 + ignore: 7.0.5 + path-type: 6.0.0 + slash: 5.1.0 + unicorn-magic: 0.3.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ignore-by-default@2.1.0: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + indent-string@5.0.0: {} + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + irregular-plurals@3.5.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-plain-object@5.0.0: {} + + is-promise@4.0.0: {} + + is-unicode-supported@2.1.0: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + js-string-escape@1.0.1: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + load-json-file@7.0.1: {} + + lodash@4.18.1: {} + + lru-cache@10.4.3: {} + + matcher@5.0.0: + dependencies: + escape-string-regexp: 5.0.0 + + math-intrinsics@1.1.0: {} + + md5-hex@3.0.1: + dependencies: + blueimp-md5: 2.19.0 + + media-typer@1.1.0: {} + + memoize@10.2.0: + dependencies: + mimic-function: 5.0.1 + + merge-descriptors@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimic-function@5.0.1: {} + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.0 + + minipass@7.1.3: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + + ms@2.1.3: {} + + negotiator@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-gyp-build@4.8.4: {} + + nofilter@3.1.0: {} + + nopt@8.1.0: + dependencies: + abbrev: 3.0.1 + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + p-map@7.0.4: {} + + package-config@5.0.0: + dependencies: + find-up-simple: 1.0.1 + load-json-file: 7.0.1 + + package-json-from-dist@1.0.1: {} + + parse-ms@4.0.0: {} + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-to-regexp@8.4.2: {} + + path-type@6.0.0: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + playwright-core@1.59.1: {} + + playwright@1.59.1: + dependencies: + playwright-core: 1.59.1 + optionalDependencies: + fsevents: 2.3.2 + + plur@5.1.0: + dependencies: + irregular-plurals: 3.5.0 + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + require-directory@2.1.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + reusify@1.1.0: {} + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + semver@7.7.4: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + slash@5.1.0: {} + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 4.0.0 + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + statuses@2.0.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + supertap@3.0.1: + dependencies: + indent-string: 5.0.0 + js-yaml: 3.14.2 + serialize-error: 7.0.1 + strip-ansi: 7.2.0 + + tar@7.5.13: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + + temp-dir@3.0.0: {} + + time-zone@1.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tr46@0.0.3: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + + type-fest@0.13.1: {} + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + undici@7.25.0: {} + + unicorn-magic@0.3.0: {} + + unpipe@1.0.0: {} + + vary@1.1.2: {} + + webidl-conversions@3.0.1: {} + + well-known-symbols@2.0.0: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + write-file-atomic@6.0.0: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + y18n@5.0.8: {} + + yallist@5.0.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 diff --git a/src/domain/types.ts b/src/domain/types.ts index 5bc558c..3b753a2 100644 --- a/src/domain/types.ts +++ b/src/domain/types.ts @@ -25,6 +25,19 @@ export type OpenFetchMemoryCacheRequestOptions = { skip?: boolean; }; +/** Byte progress for {@link OpenFetchConfig.onUploadProgress} / {@link OpenFetchConfig.onDownloadProgress}. */ +export type OpenFetchProgressEvent = { + /** Bytes read from the response body or consumed from the request body so far. */ + transferredBytes: number; + /** + * Known total when available (`Content-Length` for downloads; measured body size for common upload types). + * `null` when unknown (e.g. chunked transfer, user-provided `ReadableStream` upload without length). + */ + totalBytes: number | null; + /** 0–100 when `totalBytes` is a positive number; otherwise `null` (unknown total). */ + percent: number | null; +}; + export type OpenFetchConfig = { url?: string | URL; baseURL?: string; @@ -96,6 +109,28 @@ export type OpenFetchConfig = { debug?: boolean | "basic" | "verbose"; /** Custom sink for structured {@link OpenFetchDebugEvent} records when `debug` is enabled. */ logger?: (log: OpenFetchDebugEvent) => void; + /** + * Called as the **request** body is read by `fetch` (bytes leaving the client). + * Best-effort: `FormData` and other bodies that cannot be wrapped as a counting stream are unchanged — no events. + */ + onUploadProgress?: (event: OpenFetchProgressEvent) => void; + /** + * Called as the **response** body is read. `totalBytes` / `percent` use `Content-Length` when present; + * otherwise `totalBytes` and `percent` stay `null` while `transferredBytes` still increases. + */ + onDownloadProgress?: (event: OpenFetchProgressEvent) => void; + /** + * Node.js (Undici): forwarded to `fetch` when the runtime supports it (Undici `dispatcher` option). + * Use for custom agents, proxies, or HTTP/2 — e.g. `new Agent({ allowH2: true })` from `undici`. + * When set, it takes precedence over {@link allowH2}. + */ + dispatcher?: unknown; + /** + * Node.js: shorthand for Undici `new Agent({ allowH2: true })` passed as `fetch(..., { dispatcher })`. + * Requires the `undici` package to be installed (optional peer). Ignored when {@link dispatcher} is set. + * No-op / may be ignored on runtimes whose `fetch` does not accept `dispatcher` (e.g. browsers). + */ + allowH2?: boolean; } & Partial< Pick< RequestInit, diff --git a/src/index.ts b/src/index.ts index 4c4c3d3..749e797 100644 --- a/src/index.ts +++ b/src/index.ts @@ -93,6 +93,7 @@ export type { OpenFetchDebugEvent, OpenFetchInterceptors, OpenFetchMemoryCacheRequestOptions, + OpenFetchProgressEvent, OpenFetchResponse, OpenFetchRetryOptions, RequestConfig, diff --git a/src/shared/progress.ts b/src/shared/progress.ts new file mode 100644 index 0000000..dabe3c7 --- /dev/null +++ b/src/shared/progress.ts @@ -0,0 +1,133 @@ +import type { OpenFetchProgressEvent } from "../domain/types.js"; + +export function parsePositiveContentLength( + headerValue: string | null +): number | null { + if (headerValue == null || headerValue === "") return null; + const n = Number.parseInt(headerValue, 10); + if (!Number.isFinite(n) || n < 0) return null; + return n; +} + +export function progressFromCounts( + transferredBytes: number, + totalBytes: number | null +): OpenFetchProgressEvent { + let percent: number | null = null; + if (totalBytes != null) { + if (totalBytes <= 0) { + percent = transferredBytes <= 0 ? 100 : null; + } else { + percent = Math.min(100, (transferredBytes / totalBytes) * 100); + } + } + return { transferredBytes, totalBytes, percent }; +} + +const UPLOAD_CHUNK = 64 * 1024; + +function countingReadableStreamFromSource( + source: ReadableStream, + totalBytes: number | null, + onProgress: (e: OpenFetchProgressEvent) => void +): ReadableStream { + let transferred = 0; + const reader = source.getReader(); + return new ReadableStream({ + async pull(controller) { + const { done, value } = await reader.read(); + if (done) { + onProgress(progressFromCounts(transferred, totalBytes)); + controller.close(); + return; + } + transferred += value.byteLength; + onProgress(progressFromCounts(transferred, totalBytes)); + controller.enqueue(value); + }, + cancel(reason) { + return reader.cancel(reason); + }, + }); +} + +function countingReadableStreamFromUint8Array( + data: Uint8Array, + onProgress: (e: OpenFetchProgressEvent) => void +): ReadableStream { + const totalBytes = data.byteLength; + let offset = 0; + return new ReadableStream({ + start() { + onProgress(progressFromCounts(0, totalBytes)); + }, + pull(controller) { + if (offset >= totalBytes) { + controller.close(); + return; + } + const end = Math.min(offset + UPLOAD_CHUNK, totalBytes); + controller.enqueue(data.subarray(offset, end)); + offset = end; + onProgress(progressFromCounts(offset, totalBytes)); + }, + }); +} + +/** + * Wraps a request body so `onUploadProgress` fires as bytes are read by `fetch`. + * Unsupported shapes (`FormData`, etc.) are returned unchanged — no progress events. + */ +export function wrapBodyForUploadProgress( + body: BodyInit, + onUploadProgress: (e: OpenFetchProgressEvent) => void +): BodyInit { + if (typeof body === "string") { + const bytes = new TextEncoder().encode(body); + return countingReadableStreamFromUint8Array(bytes, onUploadProgress); + } + if (body instanceof URLSearchParams) { + const bytes = new TextEncoder().encode(body.toString()); + return countingReadableStreamFromUint8Array(bytes, onUploadProgress); + } + if (body instanceof Blob) { + return countingReadableStreamFromSource( + body.stream(), + typeof body.size === "number" ? body.size : null, + onUploadProgress + ); + } + if (body instanceof ArrayBuffer) { + return countingReadableStreamFromUint8Array( + new Uint8Array(body), + onUploadProgress + ); + } + if (ArrayBuffer.isView(body)) { + const v = body as ArrayBufferView; + return countingReadableStreamFromUint8Array( + new Uint8Array(v.buffer, v.byteOffset, v.byteLength), + onUploadProgress + ); + } + if (body instanceof ReadableStream) { + return countingReadableStreamFromSource( + body as ReadableStream, + null, + onUploadProgress + ); + } + return body; +} + +export function wrapReadableStreamWithDownloadProgress( + body: ReadableStream, + totalBytes: number | null, + onDownloadProgress: (e: OpenFetchProgressEvent) => void +): ReadableStream { + return countingReadableStreamFromSource( + body, + totalBytes, + onDownloadProgress + ); +} diff --git a/src/shared/undiciDispatcher.ts b/src/shared/undiciDispatcher.ts new file mode 100644 index 0000000..0e534da --- /dev/null +++ b/src/shared/undiciDispatcher.ts @@ -0,0 +1,33 @@ +import { OpenFetchError } from "../domain/error.js"; +import type { OpenFetchConfig } from "../domain/types.js"; + +let cachedAllowH2Agent: unknown; + +/** + * Resolves Undici `fetch` `dispatcher` from {@link OpenFetchConfig.dispatcher} or {@link OpenFetchConfig.allowH2}. + * When neither applies, returns `undefined` without importing `undici`. + */ +export async function resolveFetchDispatcher( + config: OpenFetchConfig +): Promise { + if (config.dispatcher != null) { + return config.dispatcher; + } + if (config.allowH2 !== true) { + return undefined; + } + if (cachedAllowH2Agent !== undefined) { + return cachedAllowH2Agent; + } + let undici: typeof import("undici"); + try { + undici = await import("undici"); + } catch { + throw new OpenFetchError( + "OpenFetch: `allowH2: true` requires the `undici` package (dynamic import failed). Install: npm i undici", + { config, code: "ERR_UNDICI_REQUIRED" } + ); + } + cachedAllowH2Agent = new undici.Agent({ allowH2: true }); + return cachedAllowH2Agent; +} diff --git a/src/transport/dispatch.ts b/src/transport/dispatch.ts index 5ab140b..01b5969 100644 --- a/src/transport/dispatch.ts +++ b/src/transport/dispatch.ts @@ -1,6 +1,10 @@ import { OpenFetchError } from "../domain/error.js"; import { SchemaValidationError } from "../domain/schemaValidationError.js"; -import type { OpenFetchConfig, OpenFetchResponse } from "../domain/types.js"; +import type { + OpenFetchConfig, + OpenFetchProgressEvent, + OpenFetchResponse, +} from "../domain/types.js"; import { validateJsonWithStandardSchema } from "../domain/validateJsonSchema.js"; import { assertSafeHttpUrl } from "../shared/assertSafeHttpUrl.js"; import { buildURL } from "../shared/buildURL.js"; @@ -14,7 +18,14 @@ import { redactUrlForDebug, } from "../shared/openFetchDebug.js"; import { mergeAbortSignals } from "../shared/mergeAbortSignals.js"; +import { + parsePositiveContentLength, + progressFromCounts, + wrapBodyForUploadProgress, + wrapReadableStreamWithDownloadProgress, +} from "../shared/progress.js"; import { headersToRecord } from "../shared/responseHeaders.js"; +import { resolveFetchDispatcher } from "../shared/undiciDispatcher.js"; const defaultValidateStatus = (status: number): boolean => status >= 200 && status < 300; @@ -63,6 +74,18 @@ function normalizeHeaders(h: Record): Record { return out; } +function concatUint8Arrays(chunks: Uint8Array[]): Uint8Array { + let len = 0; + for (const c of chunks) len += c.byteLength; + const out = new Uint8Array(len); + let o = 0; + for (const c of chunks) { + out.set(c, o); + o += c.byteLength; + } + return out; +} + async function parseBody( res: Response, responseType: OpenFetchConfig["responseType"] @@ -96,6 +119,82 @@ async function parseBody( return res.text(); } +async function parseBodyWithDownloadProgress( + res: Response, + responseType: OpenFetchConfig["responseType"], + totalBytes: number | null, + onDownloadProgress: (event: OpenFetchProgressEvent) => void +): Promise { + if (responseType === "stream") { + if (!res.body) { + onDownloadProgress(progressFromCounts(0, totalBytes)); + return res.body; + } + onDownloadProgress(progressFromCounts(0, totalBytes)); + return wrapReadableStreamWithDownloadProgress( + res.body, + totalBytes, + onDownloadProgress + ); + } + + onDownloadProgress(progressFromCounts(0, totalBytes)); + + if (!res.body) { + return parseBody(res, responseType); + } + + const reader = res.body.getReader(); + const chunks: Uint8Array[] = []; + let transferred = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + transferred += value.byteLength; + chunks.push(value); + onDownloadProgress(progressFromCounts(transferred, totalBytes)); + } + + const bytes = concatUint8Arrays(chunks); + + if (responseType === "arraybuffer") { + return bytes.buffer.slice( + bytes.byteOffset, + bytes.byteOffset + bytes.byteLength + ); + } + if (responseType === "blob") { + const buf = bytes.buffer as ArrayBuffer; + return new Blob([ + buf.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength), + ]); + } + if (responseType === "text") return new TextDecoder().decode(bytes); + + if (responseType === "json") { + const t = new TextDecoder().decode(bytes); + if (!t.trim()) return null; + try { + return JSON.parse(t) as unknown; + } catch { + return t; + } + } + + const ct = res.headers.get("content-type"); + const asJson = ct?.includes("application/json") ?? false; + if (asJson) { + const t = new TextDecoder().decode(bytes); + if (!t.trim()) return null; + try { + return JSON.parse(t) as unknown; + } catch { + return t; + } + } + return new TextDecoder().decode(bytes); +} + export type DispatchConfig = OpenFetchConfig & { url: string | URL }; export async function dispatch( @@ -140,6 +239,17 @@ export async function dispatch( body = JSON.stringify(body); } + if ( + body !== undefined && + body !== null && + config.onUploadProgress != null + ) { + body = wrapBodyForUploadProgress( + body as BodyInit, + config.onUploadProgress + ); + } + const credentials = config.withCredentials === true ? "include" @@ -168,8 +278,9 @@ export async function dispatch( }); try { + const dispatcher = await resolveFetchDispatcher(config); const tFetch = monotonicNowMs(); - const res = await fetch(urlString, { + const fetchInit: RequestInit & { dispatcher?: unknown; duplex?: "half" } = { method: (config.method ?? "GET").toUpperCase(), headers, body: body === undefined ? undefined : body, @@ -182,7 +293,20 @@ export async function dispatch( redirect: config.redirect, referrer: config.referrer, referrerPolicy: config.referrerPolicy, - }); + }; + if ( + body !== undefined && + body !== null && + typeof ReadableStream !== "undefined" && + body instanceof ReadableStream + ) { + // Node (Undici) requires `duplex` when `body` is a stream (e.g. upload progress wrapping). + fetchInit.duplex = "half"; + } + if (dispatcher != null) { + fetchInit.dispatcher = dispatcher; + } + const res = await fetch(urlString, fetchInit); const fetchDurationMs = Math.round(monotonicNowMs() - tFetch); const contentLength = res.headers.get("content-length"); @@ -195,10 +319,35 @@ export async function dispatch( }); const headerRecord = headersToRecord(res.headers); + const downloadTotalBytes = parsePositiveContentLength( + res.headers.get("content-length") + ); if (config.rawResponse === true) { + let responseForClient: Response = res; + if (config.onDownloadProgress != null) { + if (res.body) { + config.onDownloadProgress(progressFromCounts(0, downloadTotalBytes)); + responseForClient = new Response( + wrapReadableStreamWithDownloadProgress( + res.body, + downloadTotalBytes, + config.onDownloadProgress + ), + { + status: res.status, + statusText: res.statusText, + headers: res.headers, + } + ); + } else { + config.onDownloadProgress( + progressFromCounts(0, downloadTotalBytes) + ); + } + } const openResponse: OpenFetchResponse = { - data: res, + data: responseForClient, status: res.status, statusText: res.statusText, headers: headerRecord, @@ -222,7 +371,15 @@ export async function dispatch( let parsed: unknown; try { - parsed = await parseBody(res, config.responseType); + parsed = + config.onDownloadProgress != null + ? await parseBodyWithDownloadProgress( + res, + config.responseType, + downloadTotalBytes, + config.onDownloadProgress + ) + : await parseBody(res, config.responseType); emitOpenFetchDebug(config, "parse", { attempt, ok: true, diff --git a/test/progress.test.mjs b/test/progress.test.mjs new file mode 100644 index 0000000..51823c9 --- /dev/null +++ b/test/progress.test.mjs @@ -0,0 +1,117 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { createClient } from "../dist/index.js"; + +test("onDownloadProgress: chunked body and Content-Length", async () => { + const originalFetch = globalThis.fetch; + const encoder = new TextEncoder(); + const parts = [encoder.encode("hel"), encoder.encode("lo")]; + globalThis.fetch = async () => + new Response( + new ReadableStream({ + pull(controller) { + const next = parts.shift(); + if (next) controller.enqueue(next); + else controller.close(); + }, + }), + { + status: 200, + headers: { + "content-type": "text/plain", + "content-length": "5", + }, + } + ); + try { + const events = []; + const client = createClient(); + const res = await client.request("http://progress.test/dl", { + responseType: "text", + unwrapResponse: false, + onDownloadProgress: (e) => events.push({ ...e }), + }); + assert.equal(res.data, "hello"); + assert.ok(events.length >= 1); + const last = events.at(-1); + assert.equal(last.transferredBytes, 5); + assert.equal(last.totalBytes, 5); + assert.equal(last.percent, 100); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("onUploadProgress: string body reports bytes and percent", async () => { + const originalFetch = globalThis.fetch; + const encoder = new TextEncoder(); + const payload = "abcd"; + let received = 0; + globalThis.fetch = async (_url, init) => { + const body = init.body; + assert.ok(body instanceof ReadableStream); + const reader = body.getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + received += value.byteLength; + } + return new Response("{}", { + status: 200, + headers: { "content-type": "application/json" }, + }); + }; + try { + const events = []; + const client = createClient(); + await client.request("http://progress.test/ul", { + method: "POST", + data: payload, + responseType: "json", + unwrapResponse: true, + onUploadProgress: (e) => events.push({ ...e }), + }); + assert.equal(received, encoder.encode(payload).byteLength); + const last = events.at(-1); + assert.equal(last.transferredBytes, encoder.encode(payload).byteLength); + assert.equal(last.totalBytes, encoder.encode(payload).byteLength); + assert.equal(last.percent, 100); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("rawResponse + onDownloadProgress wraps body stream", async () => { + const originalFetch = globalThis.fetch; + const encoder = new TextEncoder(); + globalThis.fetch = async () => + new Response( + new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode("x")); + controller.close(); + }, + }), + { + status: 200, + headers: { + "content-type": "text/plain", + "content-length": "1", + }, + } + ); + try { + const events = []; + const client = createClient(); + const res = await client.request("http://progress.test/raw", { + rawResponse: true, + unwrapResponse: false, + onDownloadProgress: (e) => events.push(e.transferredBytes), + }); + const text = await res.data.text(); + assert.equal(text, "x"); + assert.ok(events.includes(1)); + } finally { + globalThis.fetch = originalFetch; + } +}); diff --git a/test/undici-dispatcher.test.mjs b/test/undici-dispatcher.test.mjs new file mode 100644 index 0000000..1d74d14 --- /dev/null +++ b/test/undici-dispatcher.test.mjs @@ -0,0 +1,78 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { Agent } from "undici"; +import { createClient } from "../dist/index.js"; + +test("dispatcher is passed through to fetch (Undici Agent)", async () => { + const originalFetch = globalThis.fetch; + let seenDispatcher; + globalThis.fetch = async (_url, init) => { + seenDispatcher = init.dispatcher; + return new Response('{"ok":true}', { + status: 200, + headers: { "content-type": "application/json" }, + }); + }; + const agent = new Agent({ allowH2: true }); + try { + const client = createClient(); + await client.request("http://undici.test/d", { + dispatcher: agent, + responseType: "json", + unwrapResponse: true, + }); + assert.strictEqual(seenDispatcher, agent); + } finally { + globalThis.fetch = originalFetch; + await agent.close(); + } +}); + +test("allowH2: true passes a dispatcher from undici Agent", async () => { + const originalFetch = globalThis.fetch; + let dispatcherConstructor; + globalThis.fetch = async (_url, init) => { + dispatcherConstructor = init.dispatcher?.constructor?.name; + return new Response("{}", { + status: 200, + headers: { "content-type": "application/json" }, + }); + }; + try { + const client = createClient(); + await client.request("http://undici.test/h2", { + allowH2: true, + responseType: "json", + unwrapResponse: true, + }); + assert.equal(dispatcherConstructor, "Agent"); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("dispatcher wins over allowH2 (only custom dispatcher used)", async () => { + const originalFetch = globalThis.fetch; + const custom = new Agent({ allowH2: false }); + let used; + globalThis.fetch = async (_url, init) => { + used = init.dispatcher; + return new Response("{}", { + status: 200, + headers: { "content-type": "application/json" }, + }); + }; + try { + const client = createClient(); + await client.request("http://undici.test/priority", { + dispatcher: custom, + allowH2: true, + responseType: "json", + unwrapResponse: true, + }); + assert.strictEqual(used, custom); + } finally { + globalThis.fetch = originalFetch; + await custom.close(); + } +});