diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c8e1c28a64..51315afc68 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ master, v23, v24, v25, v26 ] + branches: [ master, v24, v25, v26, v27, make-v28 ] pull_request: # The branches below must be a subset of the branches above - branches: [ master, v23, v24, v25, v26 ] + branches: [ master, v24, v25, v26, v27, make-v28 ] schedule: - cron: '26 8 * * 1' diff --git a/.github/workflows/headers.yml b/.github/workflows/headers.yml index abc9038571..692449caac 100644 --- a/.github/workflows/headers.yml +++ b/.github/workflows/headers.yml @@ -6,7 +6,7 @@ on: - cron: "0 0 * * 0" # Runs every Sunday at midnight UTC permissions: - contents: write # Grants write access to push changes + contents: write # Grants write access to push changes pull-requests: write # and create PRs jobs: @@ -31,7 +31,7 @@ jobs: run: pnpm install - name: Check for new headers on IANA.ORG - run: pnpm unrun tools/headers.ts + run: pnpm node tools/headers.ts - name: Check for changes id: git-state diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 471da807de..005c604d6e 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,9 +5,9 @@ name: Node.js CI on: push: - branches: [ master, v23, v24, v25, v26 ] + branches: [ master, v24, v25, v26, v27, make-v28 ] pull_request: - branches: [ master, v23, v24, v25, v26 ] + branches: [ master, v24, v25, v26, v27, make-v28 ] jobs: build: @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.19.0, 20.x, 22.12.0, 22.x, 24.0.0, 24.x] + node-version: [22.19.0, 22.x, 24.0.0, 24.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - name: Checkout diff --git a/AGENTS.md b/AGENTS.md index e1d23fcb43..aae03dd745 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -103,17 +103,16 @@ interface SampleInterface { - **Zod**: Use named import `import { z } from "zod"` - **Ramda**: Use namespace import `import * as R from "ramda"` - **Node.js built-ins**: Use `node:` prefix -- **Type-only imports**: Use `import type` for types and interfaces -- **Relative imports**: Must be extensionless -- Combine import from the same module into a single line +- **Type-only imports**: Use `import type` for types and interfaces (verbatimModuleSyntax) +- **Relative imports**: Use `.ts` extension when file is meant to be run by `node` (`example` and testing workspaces) +- Combine imports from the same module into a single statement ```typescript import { z } from "zod"; import * as R from "ramda"; import { dirname } from "node:path"; -import type { SomeType } from "./module-a"; -import { someValue } from "./module-b"; -import { anotherValue, type AnotherType } from "./module-c"; +import type { SomeType } from "./module-a"; // for compiled code +import { someValue } from "./module-b.ts"; // for node execution ``` ### 5. Type Declaration Convention diff --git a/CHANGELOG.md b/CHANGELOG.md index f3d5bb9470..77c278fc81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,57 @@ # Changelog +## Version 28 + +### v28.0.0 + +- Supported Node.js versions: `^22.19.0 || ^24.0.0`; +- Zod compatibility: `^4.3.4` (supports Zod 4.4+ without upper limit); +- The Zod plugin is no longer installed automatically — it's an optional peer dependency now: + - To keep using `.example()`, `.label()`, `.remap()`, `.deprecated()` and methods on schemas, as well as runtime + distinguishable brands, install the `@express-zod-api/zod-plugin` manually and import it (ideally at the top of a + file declaring your `Routing`); + - Breaking change: `ZodType::brand()` method is no longer patched by the plugin: + - Use `.xBrand()` method instead — alias for `.meta({ "x-brand": ... })` and does not conflict with Zod 4.4; +- Breaking changes to the `createConfig()` argument (object): + - property `wrongMethodBehavior` (number) changed to `hintAllowedMethods` (boolean); + - property `methodLikeRouteBehavior` (string literal) changed to `recognizeMethodDependentRoutes` (boolean); +- Breaking change to the `EndpointsFactory::build()` argument (object): + - property `shortDescription` renamed to `summary`; +- Breaking change to the `Documentation` constructor argument (object): + - property `hasSummaryFromDescription` (boolean) replaced with `summarizer` (function); + - If used with `false` value, replace it with `summarizer: ({ summary, trim }) => trim(summary)` for same behavior; +- Featuring `summarizer` option to customize the summary of the Endpoint in the generated Documentation: + - The function receives `summary`, `description` and the default `trim()` function as arguments; + - The default summarizer uses `description` as a fallback for missing `summary`; + - The `trim()` function accepts a string and the limit (default: 50, best practice) that you can now customize; +- Breaking change to the `Integration` constructor argument (object): + - property `noContent` renamed to `noBodySchema`; +- Consider using [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration). + +```diff + createConfig({ +- wrongMethodBehavior: 404, ++ hintAllowedMethods: false, +- methodLikeRouteBehavior: "path", ++ recognizeMethodDependentRoutes: false, + }); + + factory.build({ +- shortDescription: "Retrieves the user.", ++ summary: "Retrieves the user.", + }); + + new Documentation({ +- hasSummaryFromDescription: false, ++ summarizer: ({ summary, trim }) => trim(summary), + }); + + new Integration({ +- noContent: z.undefined(), ++ noBodySchema: z.undefined(), + }); +``` + ## Version 27 ### v27.2.6 diff --git a/README.md b/README.md index c59dbee79e..23602bf466 100644 --- a/README.md +++ b/README.md @@ -164,8 +164,7 @@ Much can be customized to fit your needs. - [Typescript](https://www.typescriptlang.org/) first. - Web server — [Express.js](https://expressjs.com/) v5. -- Schema validation — [Zod 4.x](https://github.com/colinhacks/zod) including [Zod Plugin](#zod-plugin): - - For using with Zod 3.x, install the framework versions below 24.0.0. +- Schema validation — [Zod 4.x](https://github.com/colinhacks/zod); - Supports any logger having `info()`, `debug()`, `error()` and `warn()` methods; - Built-in console logger with colorful and pretty inspections by default. - Generators: @@ -243,7 +242,7 @@ const helloWorldEndpoint = defaultEndpointsFactory.build({ Connect your endpoint to the `/v1/hello` route: ```ts -import { Routing } from "express-zod-api"; +import type { Routing } from "express-zod-api"; const routing: Routing = { v1: { @@ -920,7 +919,7 @@ it normalizes errors into consistent HTTP responses with sensible status codes. - Routing, parsing and upload issues: - Handled by `ResultHandler` configured as `errorHandler` (the defaults is `defaultResultHandler`); - Parsing errors: passed through as-is (typically `HttpError` with `4XX` code used for response by default); - - Routing errors: `404` or `405`, based on `wrongMethodBehavior` configuration; + - Routing errors: `404` or `405`, based on `hintAllowedMethods` configuration; - Upload issues: thrown only if `upload.limitError` is configured (`HttpError::statusCode` can be used for response); - For other errors the default status code is `500`; - `ResultHandler` failures: @@ -1100,8 +1099,21 @@ expect(output).toEqual({ collectedContext: ["prev"], testLength: 9 }); ## Zod Plugin -Express Zod API augments Zod using [Zod Plugin](https://www.npmjs.com/package/@express-zod-api/zod-plugin), -adding the runtime helpers the framework relies on. +The [@express-zod-api/zod-plugin](https://www.npmjs.com/package/@express-zod-api/zod-plugin) is an optional package +that extends Zod with convenience methods: + +- `.xBrand(name)` — shorthand for `.meta({ "x-brand": name })`; +- `.example(value)` — shorthand for `.meta({ examples: [value] })`; +- `.deprecated()` — shorthand for `.meta({ deprecated: true })`; +- `.label(text)` — shorthand for `.meta({ default: text })` on `ZodDefault`; +- `.remap(mapping)` — for renaming `ZodObject` shape properties; + +To benefit from these methods, install `@express-zod-api/zod-plugin` and import it once, preferably at the top of a +file declaring your `Routing`. + +```ts +import "@express-zod-api/zod-plugin"; // in your routing.ts file +``` ## End-to-End Type Safety @@ -1162,12 +1174,12 @@ in the generated documentation of your API. Consider the following example: import { defaultEndpointsFactory } from "express-zod-api"; const exampleEndpoint = defaultEndpointsFactory.build({ - shortDescription: "Retrieves the user.", // <—— this becomes the summary line + summary: "Retrieves the user.", description: "The detailed explanaition on what this endpoint does.", input: z.object({ id: z - .string() - .example("123") // input examples should be set before transformations + .string() // input examples should be set before transformations + .example("123") // requires Zod Plugin, or .meta({ examples: ["123"] }) .transform(Number) .describe("the ID of the user"), }), @@ -1175,7 +1187,8 @@ const exampleEndpoint = defaultEndpointsFactory.build({ }); ``` -You can also use `schema.meta({ id: "UniqueName" })` for custom schema naming. +Setting examples via `.example()` requires [Zod Plugin](#zod-plugin). You can also use `.meta({ examples: [] })` and +`.meta({ id: "UniqueName" })` for custom schema naming. _See the complete example of the generated documentation [here](https://github.com/RobinTail/express-zod-api/blob/master/example/example.documentation.yaml)_ @@ -1213,18 +1226,18 @@ new Documentation({ ## Deprecated schemas and routes -As your API evolves, you may need to mark some parameters or routes as deprecated before deleting them. For this -purpose, the `.deprecated()` method is available on each schema and `Endpoint`, it's immutable. -You can also deprecate all routes the `Endpoint` assigned to by setting `EndpointsFactory::build({ deprecated: true })`. +As your API evolves, you may need to mark some parameters or routes as deprecated before deleting them. This can be +achieved using the corresponding method or metadata. The `.deprecated()` method on Zod schema requires to install the +[Zod Plugin](#zod-plugin). Consider the following example: ```ts -import { Routing } from "express-zod-api"; +import type { Routing } from "express-zod-api"; import { z } from "zod"; const someEndpoint = factory.build({ deprecated: true, // deprecates all routes the endpoint assigned to input: z.object({ - prop: z.string().deprecated(), // deprecates the property or a path parameter + prop: z.string().deprecated(), // requires Zod Plugin, or .meta({ deprecated: true }) }), }); @@ -1236,10 +1249,11 @@ const routing: Routing = { ## Customizable brands handling -You can customize handling rules for your schemas in Documentation and Integration. Use the `.brand()` method on your -schema to make it special and distinguishable for the framework in runtime. Using symbols is recommended for branding. -After that use the `brandHandling` feature of both constructors to declare your custom implementation. In case you need -to reuse a handling rule for multiple brands, use the exposed types `Depicter` and `Producer`. +You can customize handling rules for your schemas in Documentation and Integration. The framework treats your schema +specially based on its `x-brand` metadata. When the [Zod Plugin](#zod-plugin) is installed you can conveniently use +the `.xBrand()` method on Zod schema, preferably with a symbol argument for its branding. After that use the +`brandHandling` feature of both constructors to declare your custom implementation. In case you need to reuse a +handling rule for multiple brands, use the exposed types `Depicter` and `Producer`. ```ts import ts from "typescript"; @@ -1252,7 +1266,7 @@ import { } from "express-zod-api"; const myBrand = Symbol("MamaToldMeImSpecial"); // I recommend to use symbols for this purpose -const myBrandedSchema = z.string().brand(myBrand); +const myBrandedSchema = z.string().xBrand(myBrand); // requires Zod Plugin, or .meta({ "x-brand": myBrand }) const ruleForDocs: Depicter = ( { zodSchema, jsonSchema }, // jsonSchema is the default depiction diff --git a/SECURITY.md b/SECURITY.md index f70054dc3f..2d7e5fa6fc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,6 +4,7 @@ | Version | Code name | Release | Supported | | ------: | :------------ | :------ | :----------------: | +| 28.x.x | Koko | 05.2026 | :white_check_mark: | | 27.x.x | Nikki | 02.2026 | :white_check_mark: | | 26.x.x | Lia | 12.2025 | :white_check_mark: | | 25.x.x | Sara | 08.2025 | :white_check_mark: | diff --git a/cjs-test/tsconfig.json b/cjs-test/tsconfig.json index c657e2e4e8..d9ee4f5b7e 100644 --- a/cjs-test/tsconfig.json +++ b/cjs-test/tsconfig.json @@ -1,4 +1,4 @@ { - "extends": "@tsconfig/node20/tsconfig.json", + "extends": "@tsconfig/node22/tsconfig.json", "include": ["quick-start.ts"] } diff --git a/cjs-test/zod-plugin.spec.ts b/cjs-test/zod-plugin.spec.ts index c817518dc6..493c30e8e6 100644 --- a/cjs-test/zod-plugin.spec.ts +++ b/cjs-test/zod-plugin.spec.ts @@ -1,7 +1,7 @@ import { createRequire } from "node:module"; const require = createRequire(import.meta.url); -require("express-zod-api"); // side effect here via Zod Plugin +require("@express-zod-api/zod-plugin"); // side effect here const z = require("zod"); // ensure CJS version of Zod is used describe("Zod plugin in CJS environment", () => { diff --git a/compat-test/dts.spec.ts b/compat-test/dts.spec.ts index 6ef40e1806..758ed76824 100644 --- a/compat-test/dts.spec.ts +++ b/compat-test/dts.spec.ts @@ -2,14 +2,6 @@ import { describe, test, expect } from "vitest"; import { readFile } from "node:fs/promises"; describe("DTS", () => { - test("Framework must import Zod plugin", async () => { - const fwDts = await readFile( - "./node_modules/express-zod-api/dist/index.d.ts", - "utf-8", - ); - expect(fwDts).toMatch(`import "@express-zod-api/zod-plugin";`); - }); - test("Zod plugin must import augmentation", async () => { const pluginDts = await readFile( "./node_modules/express-zod-api/node_modules/@express-zod-api/zod-plugin/dist/index.d.ts", diff --git a/compat-test/eslint.config.js b/compat-test/eslint.config.js index a8a8ce4714..6dfb154dd5 100644 --- a/compat-test/eslint.config.js +++ b/compat-test/eslint.config.js @@ -3,5 +3,5 @@ import migration from "@express-zod-api/migration"; export default [ { languageOptions: { parser }, plugins: { migration } }, - { files: ["**/*.ts"], rules: { "migration/v27": "error" } }, + { files: ["**/*.ts"], rules: { "migration/v28": "error" } }, ]; diff --git a/compat-test/migration.spec.ts b/compat-test/migration.spec.ts index b109e47886..71563430dd 100644 --- a/compat-test/migration.spec.ts +++ b/compat-test/migration.spec.ts @@ -2,10 +2,8 @@ import { readFile } from "node:fs/promises"; import { describe, test, expect } from "vitest"; describe("Migration", () => { - test("should fix the import", async () => { + test("should migrate", async () => { const fixed = await readFile("./sample.ts", "utf-8"); - expect(fixed).toBe( - `import typescript from "typescript";\n\nnew Integration({ typescript, routing });\n`, - ); + expect(fixed.split("\n")[0]).toBe(`createConfig({ hintAllowedMethods: false });`); }); }); diff --git a/compat-test/package.json b/compat-test/package.json index 11a1d2f698..19982c4f17 100644 --- a/compat-test/package.json +++ b/compat-test/package.json @@ -3,18 +3,18 @@ "type": "module", "private": true, "scripts": { - "pretest": "echo 'new Integration({ routing });' > sample.ts", + "pretest": "echo 'createConfig({ wrongMethodBehavior: 404 });' > sample.ts", "test": "eslint --fix && vitest --run", "posttest": "rm sample.ts" }, "devDependencies": { "@express-zod-api/migration": "workspace:*", - "eslint": "npm:eslint@9.0.0", + "eslint": "npm:eslint@10.0.0", "express": "npm:express@5.1.0", "express-zod-api": "workspace:*", "http-errors": "npm:http-errors@2.0.1", "typescript": "npm:typescript@5.1.3", - "typescript-eslint": "npm:typescript-eslint@8.0.0", + "typescript-eslint": "npm:typescript-eslint@8.58.0", "zod": "npm:zod@4.3.4" } } diff --git a/compat-test/quick-start.spec.ts b/compat-test/quick-start.spec.ts index 12658ed174..089a9eb359 100644 --- a/compat-test/quick-start.spec.ts +++ b/compat-test/quick-start.spec.ts @@ -8,14 +8,14 @@ import { expect, test, } from "vitest"; -import { givePort } from "../tools/ports"; +import { givePort } from "../tools/ports.ts"; describe("ESM Test", async () => { let out = ""; const listener = (chunk: Buffer) => { out += chunk.toString(); }; - const quickStart = spawn("unrun", ["quick-start.ts"]); + const quickStart = spawn("node", ["quick-start.ts"]); quickStart.stdout.on("data", listener); quickStart.stderr.on("data", listener); await vi.waitFor(() => assert(out.includes(`Listening`)), { timeout: 1e4 }); diff --git a/eslint.config.js b/eslint.config.js index 4197ace1ca..99c0a736be 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -34,13 +34,6 @@ const importConcerns = [ })), ]; -const compatibilityConcerns = [ - { - selector: "CallExpression > MemberExpression[property.name='example']", - message: "avoid using example() method to operate without zod plugin", - }, -]; - const performanceConcerns = [ { selector: "ImportDeclaration[source.value=/assert/]", // #2169 @@ -199,7 +192,7 @@ export default tsPlugin.config( prettierRules, { name: "globally/ignored", - ignores: ["**/dist/", "**/coverage/", "compat-test/sample.ts"], + ignores: ["**/dist/", "**/coverage/", "compat-test"], }, { name: "globally/disabled", @@ -214,6 +207,7 @@ export default tsPlugin.config( curly: ["warn", "multi-or-nest", "consistent"], "@typescript-eslint/no-shadow": "warn", "no-restricted-syntax": ["warn", ...importConcerns], + "no-duplicate-imports": "warn", }, }, { @@ -226,7 +220,6 @@ export default tsPlugin.config( "warn", ...importConcerns, ...performanceConcerns, - ...compatibilityConcerns, ], }, }, @@ -244,7 +237,7 @@ export default tsPlugin.config( }, { name: "source/migration", - files: ["migration/index.ts"], + files: ["migration/index.ts", "migration/helpers.ts"], rules: { "allowed/dependencies": ["error", { packageDir: migrationDir }], "no-restricted-syntax": ["warn", ...importConcerns], @@ -279,6 +272,7 @@ export default tsPlugin.config( files: ["*-test/quick-start.ts", "example/example.client.ts"], rules: { "prettier/prettier": "off", + "no-duplicate-imports": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-empty-object-type": [ "error", diff --git a/esm-test/quick-start.spec.ts b/esm-test/quick-start.spec.ts index cc1daaf563..84994fb85e 100644 --- a/esm-test/quick-start.spec.ts +++ b/esm-test/quick-start.spec.ts @@ -1,12 +1,12 @@ import { spawn } from "node:child_process"; -import { givePort } from "../tools/ports"; +import { givePort } from "../tools/ports.ts"; describe("ESM Test", async () => { let out = ""; const listener = (chunk: Buffer) => { out += chunk.toString(); }; - const quickStart = spawn("unrun", ["quick-start.ts"]); + const quickStart = spawn("node", ["quick-start.ts"]); quickStart.stdout.on("data", listener); quickStart.stderr.on("data", listener); const port = givePort("esm"); diff --git a/example/config.ts b/example/config.ts index b9bcc7025e..1d262d4773 100644 --- a/example/config.ts +++ b/example/config.ts @@ -1,7 +1,7 @@ import { BuiltinLogger, createConfig } from "express-zod-api"; import ui from "swagger-ui-express"; import createHttpError from "http-errors"; -import { givePort } from "../tools/ports"; +import { givePort } from "../tools/ports.ts"; import qs from "qs"; export const config = createConfig({ diff --git a/example/endpoints/create-user.ts b/example/endpoints/create-user.ts index 0cbbba7893..b7b6f44cfd 100644 --- a/example/endpoints/create-user.ts +++ b/example/endpoints/create-user.ts @@ -1,7 +1,7 @@ import createHttpError from "http-errors"; import assert from "node:assert/strict"; import { z } from "zod"; -import { statusDependingFactory } from "../factories"; +import { statusDependingFactory } from "../factories.ts"; const namePart = z.string().regex(/^\w+$/); diff --git a/example/endpoints/delete-user.ts b/example/endpoints/delete-user.ts index e655a3e4ae..eb521ba951 100644 --- a/example/endpoints/delete-user.ts +++ b/example/endpoints/delete-user.ts @@ -1,7 +1,7 @@ import createHttpError from "http-errors"; import assert from "node:assert/strict"; import { z } from "zod"; -import { noContentFactory } from "../factories"; +import { noContentFactory } from "../factories.ts"; /** @desc The endpoint demonstrates no content response established by its factory */ export const deleteUserEndpoint = noContentFactory.buildVoid({ diff --git a/example/endpoints/list-users-paginated.ts b/example/endpoints/list-users-paginated.ts index f052f89896..bc39fc771f 100644 --- a/example/endpoints/list-users-paginated.ts +++ b/example/endpoints/list-users-paginated.ts @@ -33,7 +33,7 @@ const users: z.output[] = [ */ export const listUsersPaginatedEndpoint = defaultEndpointsFactory.build({ tag: "users", - shortDescription: "Lists users with pagination.", + summary: "Lists users with pagination.", description: "Returns a page of users. Optionally filter by roles. Uses offset-based pagination (limit and offset).", input: paginatedUsers.input.and( diff --git a/example/endpoints/list-users.ts b/example/endpoints/list-users.ts index b9844dfa3a..c5e919ef27 100644 --- a/example/endpoints/list-users.ts +++ b/example/endpoints/list-users.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { arrayRespondingFactory } from "../factories"; +import { arrayRespondingFactory } from "../factories.ts"; const roleSchema = z.enum(["manager", "operator", "admin"]); diff --git a/example/endpoints/retrieve-user.ts b/example/endpoints/retrieve-user.ts index 9996d18f59..c70d043867 100644 --- a/example/endpoints/retrieve-user.ts +++ b/example/endpoints/retrieve-user.ts @@ -2,7 +2,7 @@ import createHttpError from "http-errors"; import assert from "node:assert/strict"; import { z } from "zod"; import { defaultEndpointsFactory } from "express-zod-api"; -import { methodProviderMiddleware } from "../middlewares"; +import { methodProviderMiddleware } from "../middlewares.ts"; // Demonstrating circular schemas using z.object() const feature = z @@ -19,7 +19,7 @@ export const retrieveUserEndpoint = defaultEndpointsFactory .addMiddleware(methodProviderMiddleware) .build({ tag: "users", - shortDescription: "Retrieves the user.", + summary: "Retrieves the user.", description: "Example user retrieval endpoint.", input: z.object({ id: z diff --git a/example/endpoints/send-avatar.ts b/example/endpoints/send-avatar.ts index 9bc91b442d..655ce281e1 100644 --- a/example/endpoints/send-avatar.ts +++ b/example/endpoints/send-avatar.ts @@ -1,9 +1,9 @@ import { z } from "zod"; -import { fileSendingEndpointsFactory } from "../factories"; +import { fileSendingEndpointsFactory } from "../factories.ts"; import { readFile } from "node:fs/promises"; export const sendAvatarEndpoint = fileSendingEndpointsFactory.build({ - shortDescription: "Sends a file content.", + summary: "Sends a file content.", tag: ["files", "users"], input: z.object({ userId: z diff --git a/example/endpoints/stream-avatar.ts b/example/endpoints/stream-avatar.ts index 677edc57b6..f052637ace 100644 --- a/example/endpoints/stream-avatar.ts +++ b/example/endpoints/stream-avatar.ts @@ -1,8 +1,8 @@ import { z } from "zod"; -import { fileStreamingEndpointsFactory } from "../factories"; +import { fileStreamingEndpointsFactory } from "../factories.ts"; export const streamAvatarEndpoint = fileStreamingEndpointsFactory.build({ - shortDescription: "Streams a file content.", + summary: "Streams a file content.", tag: ["users", "files"], input: z.object({ userId: z diff --git a/example/endpoints/time-subscription.ts b/example/endpoints/time-subscription.ts index bc571a98d0..9416aedf47 100644 --- a/example/endpoints/time-subscription.ts +++ b/example/endpoints/time-subscription.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { setTimeout } from "node:timers/promises"; -import { eventsFactory } from "../factories"; +import { eventsFactory } from "../factories.ts"; /** @desc The endpoint demonstrates emitting server-sent events (SSE) */ export const subscriptionEndpoint = eventsFactory.buildVoid({ diff --git a/example/endpoints/update-user.ts b/example/endpoints/update-user.ts index 8f15b9fc11..0abc67df43 100644 --- a/example/endpoints/update-user.ts +++ b/example/endpoints/update-user.ts @@ -2,7 +2,7 @@ import createHttpError from "http-errors"; import assert from "node:assert/strict"; import { z } from "zod"; import { ez } from "express-zod-api"; -import { keyAndTokenAuthenticatedEndpointsFactory } from "../factories"; +import { keyAndTokenAuthenticatedEndpointsFactory } from "../factories.ts"; export const updateUserEndpoint = keyAndTokenAuthenticatedEndpointsFactory.build({ diff --git a/example/example.documentation.yaml b/example/example.documentation.yaml index 7ac745fe56..014ebab983 100644 --- a/example/example.documentation.yaml +++ b/example/example.documentation.yaml @@ -1052,7 +1052,6 @@ components: required: - title additionalProperties: false - id: Feature responses: {} parameters: {} examples: {} diff --git a/example/factories.ts b/example/factories.ts index 3486217b94..e637820165 100644 --- a/example/factories.ts +++ b/example/factories.ts @@ -7,7 +7,7 @@ import { EventStreamFactory, defaultEndpointsFactory, } from "express-zod-api"; -import { authMiddleware } from "./middlewares"; +import { authMiddleware } from "./middlewares.ts"; import { createReadStream } from "node:fs"; import { z } from "zod"; import { stat } from "node:fs/promises"; diff --git a/example/generate-client.ts b/example/generate-client.ts index 16f7e3057a..4d6ce87fec 100644 --- a/example/generate-client.ts +++ b/example/generate-client.ts @@ -1,7 +1,7 @@ import { writeFile } from "node:fs/promises"; import { Integration } from "express-zod-api"; -import { routing } from "./routing"; -import { config } from "./config"; +import { routing } from "./routing.ts"; +import { config } from "./config.ts"; import typescript from "typescript"; await writeFile( diff --git a/example/generate-documentation.ts b/example/generate-documentation.ts index 82f70845a1..badba033ca 100644 --- a/example/generate-documentation.ts +++ b/example/generate-documentation.ts @@ -1,8 +1,8 @@ import { writeFile } from "node:fs/promises"; import { Documentation } from "express-zod-api"; -import { config } from "./config"; -import { routing } from "./routing"; -import manifest from "./package.json"; +import { config } from "./config.ts"; +import { routing } from "./routing.ts"; +import manifest from "./package.json" with { type: "json" }; await writeFile( "example.documentation.yaml", diff --git a/example/index.spec.ts b/example/index.spec.ts index 76916f4e54..41a7d1c62d 100644 --- a/example/index.spec.ts +++ b/example/index.spec.ts @@ -1,11 +1,10 @@ -import assert from "node:assert/strict"; +import assert, { fail } from "node:assert/strict"; import { spawn } from "node:child_process"; import { createReadStream, readFileSync } from "node:fs"; -import { Client, Subscription } from "./example.client"; -import { givePort } from "../tools/ports"; +import { Client, Subscription } from "./example.client.ts"; +import { givePort } from "../tools/ports.ts"; import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; -import { fail } from "node:assert"; describe("Example", async () => { let out = ""; @@ -13,7 +12,7 @@ describe("Example", async () => { out += chunk.toString(); }; const matchOut = (regExp: RegExp) => regExp.test(out); - const example = spawn("unrun", ["index.ts"]); + const example = spawn("node", ["index.ts"]); example.stdout.on("data", listener); const port = givePort("example"); await vi.waitFor(() => assert(out.includes(`Listening`)), { timeout: 1e4 }); diff --git a/example/index.ts b/example/index.ts index 9f62dc28d3..3292f7dfcf 100644 --- a/example/index.ts +++ b/example/index.ts @@ -1,6 +1,6 @@ import { createServer } from "express-zod-api"; -import { config } from "./config"; -import { routing } from "./routing"; +import { config } from "./config.ts"; +import { routing } from "./routing.ts"; /** * "await" is only needed for using entities returned from this method. diff --git a/example/middlewares.ts b/example/middlewares.ts index e3b68021f9..c4b922b764 100644 --- a/example/middlewares.ts +++ b/example/middlewares.ts @@ -1,7 +1,7 @@ import createHttpError from "http-errors"; import assert from "node:assert/strict"; import { z } from "zod"; -import { Method, Middleware } from "express-zod-api"; +import { Middleware, type Method } from "express-zod-api"; export const authMiddleware = new Middleware({ security: { diff --git a/example/package.json b/example/package.json index 0c87bc76f3..3037a30bc1 100644 --- a/example/package.json +++ b/example/package.json @@ -4,14 +4,15 @@ "version": "0.0.0", "type": "module", "scripts": { - "start": "unrun index.ts", + "start": "node index.ts", "build": "pnpm build:docs && pnpm build:client", - "build:docs": "unrun generate-documentation.ts", - "build:client": "unrun generate-client.ts", + "build:docs": "node generate-documentation.ts", + "build:client": "node generate-client.ts", "pretest": "tsc", "test": "vitest run index.spec.ts" }, "devDependencies": { + "@express-zod-api/zod-plugin": "workspace:*", "@types/http-errors": "catalog:dev", "@types/swagger-ui-express": "^4.1.8", "express-zod-api": "workspace:*", diff --git a/example/routing.ts b/example/routing.ts index 0bd924ba9c..20139e4579 100644 --- a/example/routing.ts +++ b/example/routing.ts @@ -1,16 +1,17 @@ -import { Routing, ServeStatic } from "express-zod-api"; -import { rawAcceptingEndpoint } from "./endpoints/accept-raw"; -import { createUserEndpoint } from "./endpoints/create-user"; -import { deleteUserEndpoint } from "./endpoints/delete-user"; -import { listUsersEndpoint } from "./endpoints/list-users"; -import { listUsersPaginatedEndpoint } from "./endpoints/list-users-paginated"; -import { submitFeedbackEndpoint } from "./endpoints/submit-feedback"; -import { subscriptionEndpoint } from "./endpoints/time-subscription"; -import { uploadAvatarEndpoint } from "./endpoints/upload-avatar"; -import { retrieveUserEndpoint } from "./endpoints/retrieve-user"; -import { sendAvatarEndpoint } from "./endpoints/send-avatar"; -import { updateUserEndpoint } from "./endpoints/update-user"; -import { streamAvatarEndpoint } from "./endpoints/stream-avatar"; +import "@express-zod-api/zod-plugin"; // adds .example() method +import { type Routing, ServeStatic } from "express-zod-api"; +import { rawAcceptingEndpoint } from "./endpoints/accept-raw.ts"; +import { createUserEndpoint } from "./endpoints/create-user.ts"; +import { deleteUserEndpoint } from "./endpoints/delete-user.ts"; +import { listUsersEndpoint } from "./endpoints/list-users.ts"; +import { listUsersPaginatedEndpoint } from "./endpoints/list-users-paginated.ts"; +import { submitFeedbackEndpoint } from "./endpoints/submit-feedback.ts"; +import { subscriptionEndpoint } from "./endpoints/time-subscription.ts"; +import { uploadAvatarEndpoint } from "./endpoints/upload-avatar.ts"; +import { retrieveUserEndpoint } from "./endpoints/retrieve-user.ts"; +import { sendAvatarEndpoint } from "./endpoints/send-avatar.ts"; +import { updateUserEndpoint } from "./endpoints/update-user.ts"; +import { streamAvatarEndpoint } from "./endpoints/stream-avatar.ts"; export const routing: Routing = { v1: { diff --git a/express-zod-api/package.json b/express-zod-api/package.json index 89942766bd..016787ec93 100644 --- a/express-zod-api/package.json +++ b/express-zod-api/package.json @@ -1,6 +1,6 @@ { "name": "express-zod-api", - "version": "27.2.6", + "version": "28.0.0-beta.4", "description": "A Typescript framework to help you get an API server up and running with I/O schema validation and custom middlewares in minutes.", "license": "MIT", "repository": { @@ -16,7 +16,7 @@ "bugs": "https://github.com/RobinTail/express-zod-api/issues", "funding": "https://github.com/sponsors/RobinTail", "scripts": { - "build": "tsdown --config-loader unrun", + "build": "tsdown", "pretest": "tsc", "test": "vitest run --coverage", "bench": "vitest bench --run ./bench", @@ -24,7 +24,6 @@ "prepack": "find .. -maxdepth 1 \\( -name '*.md' ! -name 'AGENTS.md' -o -name 'LICENSE' \\) -exec cp {} . \\;" }, "type": "module", - "sideEffects": true, "main": "dist/index.js", "types": "dist/index.d.ts", "module": "dist/index.js", @@ -39,16 +38,16 @@ "*.md" ], "engines": { - "node": "^20.19.0 || ^22.12.0 || ^24.0.0" + "node": "^22.19.0 || ^24.0.0" }, "dependencies": { - "@express-zod-api/zod-plugin": "workspace:^", "ansis": "^4.2.0", "node-mocks-http": "^1.17.2", "openapi3-ts": "^4.5.0", "ramda": "catalog:prod" }, "peerDependencies": { + "@express-zod-api/zod-plugin": "workspace:^", "@types/compression": "^1.7.5", "@types/express": "^5.0.0", "@types/express-fileupload": "^1.5.0", @@ -61,6 +60,9 @@ "zod": "catalog:peer" }, "peerDependenciesMeta": { + "@express-zod-api/zod-plugin": { + "optional": true + }, "@types/compression": { "optional": true }, @@ -84,6 +86,7 @@ } }, "devDependencies": { + "@express-zod-api/zod-plugin": "workspace:^", "@types/compression": "catalog:dev", "@types/cors": "^2.8.19", "@types/depd": "^1.1.37", @@ -102,7 +105,7 @@ "node-forge": "^1.3.3", "snakify-ts": "^2.3.0", "typescript": "catalog:dev", - "undici": "^7.16.0", + "undici": "^8.0.0", "zod": "catalog:dev" }, "keywords": [ diff --git a/express-zod-api/src/buffer-schema.ts b/express-zod-api/src/buffer-schema.ts index 6fc7d8c993..18f6d52374 100644 --- a/express-zod-api/src/buffer-schema.ts +++ b/express-zod-api/src/buffer-schema.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { brandProperty } from "./metadata"; export const ezBufferBrand = Symbol("Buffer"); @@ -7,4 +8,4 @@ export const buffer = () => .custom((subject) => Buffer.isBuffer(subject), { error: "Expected Buffer", }) - .brand(ezBufferBrand as symbol); + .meta({ [brandProperty]: ezBufferBrand }); diff --git a/express-zod-api/src/builtin-logger.ts b/express-zod-api/src/builtin-logger.ts index 1008bff276..f5b3c5f01b 100644 --- a/express-zod-api/src/builtin-logger.ts +++ b/express-zod-api/src/builtin-logger.ts @@ -1,13 +1,13 @@ import ansis from "ansis"; import { inspect } from "node:util"; import { performance } from "node:perf_hooks"; -import { FlatObject, isProduction } from "./common-helpers"; +import { isProduction, type FlatObject } from "./common-helpers"; import { - AbstractLogger, formatDuration, isHidden, - Severity, styles, + type AbstractLogger, + type Severity, } from "./logger-helpers"; interface Context extends FlatObject { diff --git a/express-zod-api/src/common-helpers.ts b/express-zod-api/src/common-helpers.ts index d727e4cc04..e0f1c9dec4 100644 --- a/express-zod-api/src/common-helpers.ts +++ b/express-zod-api/src/common-helpers.ts @@ -1,16 +1,16 @@ -import { Request } from "express"; +import type { Request } from "express"; import * as R from "ramda"; import { z } from "zod"; -import { CommonConfig, InputSource, InputSources } from "./config-type"; +import type { CommonConfig, InputSource, InputSources } from "./config-type"; import { contentTypes } from "./content-type"; import { - ClientMethod, - SomeMethod, + type ClientMethod, + type SomeMethod, + type Method, + type CORSMethod, isMethod, - Method, - CORSMethod, } from "./method"; -import { NormalizedResponse } from "./api-response"; +import type { NormalizedResponse } from "./api-response"; /** @since zod 3.25.61 output type fixed */ export const emptySchema = z.object({}); diff --git a/express-zod-api/src/config-type.ts b/express-zod-api/src/config-type.ts index 7b7f781703..f9c6bcbf29 100644 --- a/express-zod-api/src/config-type.ts +++ b/express-zod-api/src/config-type.ts @@ -1,14 +1,14 @@ import type compression from "compression"; -import { IRouter, Request, RequestHandler } from "express"; +import type { IRouter, Request, RequestHandler } from "express"; import type fileUpload from "express-fileupload"; -import { ServerOptions } from "node:https"; -import { BuiltinLoggerConfig } from "./builtin-logger"; -import { AbstractEndpoint } from "./endpoint"; -import { AbstractLogger, ActualLogger } from "./logger-helpers"; -import { Method } from "./method"; -import { AbstractResultHandler } from "./result-handler"; -import { ListenOptions } from "node:net"; -import { GetLogger } from "./server-helpers"; +import type { ServerOptions } from "node:https"; +import type { BuiltinLoggerConfig } from "./builtin-logger"; +import type { AbstractEndpoint } from "./endpoint"; +import type { AbstractLogger, ActualLogger } from "./logger-helpers"; +import type { Method } from "./method"; +import type { AbstractResultHandler } from "./result-handler"; +import type { ListenOptions } from "node:net"; +import type { GetLogger } from "./server-helpers"; export type InputSource = keyof Pick< Request, @@ -40,20 +40,21 @@ export interface CommonConfig { */ cors: boolean | HeadersProvider; /** - * @desc How to respond to a request that uses a wrong method to an existing endpoint - * @example 404 — Not found - * @example 405 — Method not allowed, incl. the "Allow" header with a list of methods - * @default 405 - * */ - wrongMethodBehavior?: 404 | 405; + * @desc Controls how to respond to a request to an existing endpoint with an invalid HTTP method. + * @example true — respond with status code 405 and "Allow" header containing a list of valid methods + * @example false — respond with status code 404 (Not found) + * @default true + */ + hintAllowedMethods?: boolean; /** - * @desc How to treat Routing keys that look like methods (when assigned with an Endpoint) - * @see Method - * @example "method" — the key is treated as method of its parent path - * @example "path" — the key is treated as a nested path segment - * @default "method" - * */ - methodLikeRouteBehavior?: "method" | "path"; + * @desc Controls how to treat Routing keys matching HTTP methods ("get", "post") and having Endpoint assigned. + * @example true — treat such keys as HTTP methods complementing their parent paths + * { users: { get: ... }} becomes GET /users + * @example false — treat such keys as nested path segments regardless of the name + * { users: { get: ... }} remains /users/get + * @default true + */ + recognizeMethodDependentRoutes?: boolean; /** * @desc The ResultHandler to use for handling routing, parsing and upload errors * @default defaultResultHandler diff --git a/express-zod-api/src/date-in-schema.ts b/express-zod-api/src/date-in-schema.ts index cc7d4fd335..7ee70b9637 100644 --- a/express-zod-api/src/date-in-schema.ts +++ b/express-zod-api/src/date-in-schema.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { brandProperty } from "./metadata"; export const ezDateInBrand = Symbol("DateIn"); @@ -20,6 +21,5 @@ export const dateIn = ({ examples, ...rest }: DateInParams = {}) => { .meta({ examples }) .transform((str) => new Date(str)) .pipe(z.date()) - .brand(ezDateInBrand as symbol) - .meta(rest); + .meta({ ...rest, [brandProperty]: ezDateInBrand }); }; diff --git a/express-zod-api/src/date-out-schema.ts b/express-zod-api/src/date-out-schema.ts index 2c5c7ca8af..2731ab28fe 100644 --- a/express-zod-api/src/date-out-schema.ts +++ b/express-zod-api/src/date-out-schema.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { brandProperty } from "./metadata"; export const ezDateOutBrand = Symbol("DateOut"); @@ -13,5 +14,4 @@ export const dateOut = (meta: DateOutParams = {}) => z .date() .transform((date) => date.toISOString()) - .brand(ezDateOutBrand as symbol) - .meta(meta); + .meta({ ...meta, [brandProperty]: ezDateOutBrand }); diff --git a/express-zod-api/src/deep-checks.ts b/express-zod-api/src/deep-checks.ts index 7a4eb25864..b6406ede26 100644 --- a/express-zod-api/src/deep-checks.ts +++ b/express-zod-api/src/deep-checks.ts @@ -5,9 +5,9 @@ import { ezDateInBrand } from "./date-in-schema"; import { ezDateOutBrand } from "./date-out-schema"; import { DeepCheckError } from "./errors"; import { ezFormBrand } from "./form-schema"; -import { IOSchema } from "./io-schema"; -import { getBrand } from "@express-zod-api/zod-plugin"; -import { FirstPartyKind } from "./schema-walker"; +import type { IOSchema } from "./io-schema"; +import { getBrand } from "./metadata"; +import type { FirstPartyKind } from "./schema-walker"; import { ezUploadBrand } from "./upload-schema"; import { ezRawBrand } from "./raw-schema"; diff --git a/express-zod-api/src/diagnostics.ts b/express-zod-api/src/diagnostics.ts index c21b602fef..1b9e2dc44a 100644 --- a/express-zod-api/src/diagnostics.ts +++ b/express-zod-api/src/diagnostics.ts @@ -1,11 +1,11 @@ import { z } from "zod"; import { responseVariants } from "./api-response"; -import { FlatObject, getRoutePathParams } from "./common-helpers"; +import { type FlatObject, getRoutePathParams } from "./common-helpers"; import { contentTypes } from "./content-type"; import { findJsonIncompatible } from "./deep-checks"; import { AbstractEndpoint } from "./endpoint"; import { flattenIO } from "./json-schema-helpers"; -import { ActualLogger } from "./logger-helpers"; +import type { ActualLogger } from "./logger-helpers"; import type { OnEndpoint } from "./routing-walker"; interface Findings { diff --git a/express-zod-api/src/documentation-helpers.ts b/express-zod-api/src/documentation-helpers.ts index d50aee9514..f6f19a4e45 100644 --- a/express-zod-api/src/documentation-helpers.ts +++ b/express-zod-api/src/documentation-helpers.ts @@ -1,49 +1,49 @@ import { - ExamplesObject, + type ExamplesObject, + type MediaTypeObject, + type OAuthFlowObject, + type ParameterObject, + type ReferenceObject, + type RequestBodyObject, + type ResponseObject, + type SchemaObject, + type SchemaObjectType, + type SecurityRequirementObject, + type SecuritySchemeObject, + type TagObject, isReferenceObject, isSchemaObject, - MediaTypeObject, - OAuthFlowObject, - ParameterObject, - ReferenceObject, - RequestBodyObject, - ResponseObject, - SchemaObject, - SchemaObjectType, - SecurityRequirementObject, - SecuritySchemeObject, - TagObject, } from "openapi3-ts/oas31"; import * as R from "ramda"; import { z } from "zod"; -import { NormalizedResponse, ResponseVariant } from "./api-response"; +import type { NormalizedResponse, ResponseVariant } from "./api-response"; import { ezBufferBrand } from "./buffer-schema"; import { + type FlatObject, + type Tag, shouldHaveContent, - FlatObject, getRoutePathParams, getTransformedType, isObject, isSchema, makeCleanId, routePathParamsRegex, - Tag, ucFirst, } from "./common-helpers"; -import { InputSource } from "./config-type"; +import type { InputSource } from "./config-type"; import { contentTypes } from "./content-type"; import { ezDateInBrand } from "./date-in-schema"; import { ezDateOutBrand } from "./date-out-schema"; import { DocumentationError } from "./errors"; -import { IOSchema } from "./io-schema"; +import type { IOSchema } from "./io-schema"; import { flattenIO } from "./json-schema-helpers"; -import { Alternatives } from "./logical-container"; -import { getBrand } from "@express-zod-api/zod-plugin"; -import { ClientMethod } from "./method"; -import { ProprietaryBrand } from "./proprietary-schemas"; +import type { Alternatives } from "./logical-container"; +import { getBrand } from "./metadata"; +import type { ClientMethod } from "./method"; +import type { ProprietaryBrand } from "./proprietary-schemas"; import { ezRawBrand } from "./raw-schema"; -import { FirstPartyKind } from "./schema-walker"; -import { Security } from "./security"; +import type { FirstPartyKind } from "./schema-walker"; +import type { Security } from "./security"; import { ezUploadBrand } from "./upload-schema"; import { getWellKnownHeaders } from "./well-known-headers"; @@ -78,7 +78,6 @@ export type IsHeader = ( export type BrandHandling = Record; -const shortDescriptionLimit = 50; const isoDateDocumentationUrl = "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString"; @@ -371,6 +370,8 @@ const fixReferences = ( ctx: OpenAPIContext, ) => { const stack: unknown[] = [subject, defs]; + const filterNaming = (name: string) => + /schema\d+$/.test(name) ? undefined : name; for (let idx = 0; idx < stack.length; idx++) { const entry = stack[idx]; if (R.is(Object, entry)) { @@ -381,7 +382,7 @@ const fixReferences = ( entry.$ref = ctx.makeRef( depiction.id || depiction, // avoiding serialization, because changing $ref asOAS(depiction), - depiction.id, + depiction.id || filterNaming(actualName), ).$ref; } continue; @@ -664,10 +665,11 @@ export const depictTags = ( return agg.concat(entry); }, []); -export const ensureShortDescription = (description: string) => - description.length <= shortDescriptionLimit - ? description - : description.slice(0, shortDescriptionLimit - 1) + "…"; +/** @desc Ensures the summary string does not exceed the limit */ +export const trimSummary = (summary?: string, limit = 50) => + !summary || summary.length <= limit + ? summary + : summary.slice(0, limit - 1) + "…"; export const nonEmpty = (subject: T[] | ReadonlyArray) => subject.length ? subject.slice() : undefined; diff --git a/express-zod-api/src/documentation.ts b/express-zod-api/src/documentation.ts index 173930cbe6..f1a8bb5a8e 100644 --- a/express-zod-api/src/documentation.ts +++ b/express-zod-api/src/documentation.ts @@ -1,20 +1,20 @@ import { + type OperationObject, + type ReferenceObject, + type ResponsesObject, + type SchemaObject, + type SecuritySchemeObject, + type SecuritySchemeType, OpenApiBuilder, - OperationObject, - ReferenceObject, - ResponsesObject, - SchemaObject, - SecuritySchemeObject, - SecuritySchemeType, } from "openapi3-ts/oas31"; import * as R from "ramda"; import { responseVariants } from "./api-response"; import { contentTypes } from "./content-type"; import { DocumentationError } from "./errors"; import { getInputSources, makeCleanId } from "./common-helpers"; -import { CommonConfig } from "./config-type"; +import type { CommonConfig } from "./config-type"; import { processContainers } from "./logical-container"; -import { ClientMethod } from "./method"; +import type { ClientMethod } from "./method"; import { depictBody, depictRequestParams, @@ -22,15 +22,15 @@ import { depictSecurity, depictSecurityRefs, depictTags, - ensureShortDescription, + trimSummary, reformatParamsInPath, - IsHeader, nonEmpty, - BrandHandling, depictRequest, + type IsHeader, + type BrandHandling, } from "./documentation-helpers"; -import { Routing } from "./routing"; -import { OnEndpoint, walkRouting, withHead } from "./routing-walker"; +import type { Routing } from "./routing"; +import { walkRouting, withHead, type OnEndpoint } from "./routing-walker"; type Component = | "positiveResponse" @@ -45,6 +45,19 @@ type Descriptor = ( }, ) => string; +type Summarizer = (params: { + summary?: string; + description?: string; + trim: typeof trimSummary; +}) => string | undefined; + +/** @desc Uses description as a fallback */ +const defaultSummarizer: Summarizer = ({ + description, + summary = description, + trim, +}) => trim(summary); + interface DocumentationParams { title: string; version: string; @@ -57,8 +70,12 @@ interface DocumentationParams { * @default () => `${method} ${path} ${component}` * */ descriptions?: Partial>; - /** @default true */ - hasSummaryFromDescription?: boolean; + /** + * @desc The function that ensures the maximum length for summary fields. Can optionally make them from descriptions. + * @see defaultSummarizer + * @see trimSummary + * */ + summarizer?: Summarizer; /** * @desc Depict the HEAD method for each Endpoint supporting the GET method (feature of Express) * @default true @@ -67,10 +84,11 @@ interface DocumentationParams { /** @default inline */ composition?: "inline" | "components"; /** - * @desc Handling rules for your own branded schemas. + * @desc Handling rules for your own schemas branded with `x-brand` metadata. * @desc Keys: brands (recommended to use unique symbols). * @desc Values: functions having Zod context as first argument, second one is the framework context. - * @example { MyBrand: ( { zodSchema, jsonSchema } ) => ({ type: "object" }) + * @example { MyBrand: ({ zodSchema, jsonSchema }) => ({ type: "object" }) + * @link https://www.npmjs.com/package/@express-zod-api/zod-plugin */ brandHandling?: BrandHandling; /** @@ -158,8 +176,8 @@ export class Documentation extends OpenApiBuilder { brandHandling, tags, isHeader, - hasSummaryFromDescription = true, hasHeadMethod = true, + summarizer = defaultSummarizer, composition = "inline", }: DocumentationParams) { super(); @@ -175,12 +193,7 @@ export class Documentation extends OpenApiBuilder { brandHandling, makeRef: this.#makeRef.bind(this), }; - const { description, shortDescription, scopes, inputSchema } = endpoint; - const summary = shortDescription - ? ensureShortDescription(shortDescription) - : hasSummaryFromDescription && description - ? ensureShortDescription(description) - : undefined; + const { description, summary, scopes, inputSchema } = endpoint; const inputSources = getInputSources(method, config.inputSources); const operationId = this.#ensureUniqOperationId( path, @@ -196,7 +209,7 @@ export class Documentation extends OpenApiBuilder { isHeader, security, request, - description: descriptions?.requestParameter?.call(null, { + description: descriptions?.requestParameter?.({ method, path, operationId, @@ -216,7 +229,7 @@ export class Documentation extends OpenApiBuilder { statusCode, hasMultipleStatusCodes: apiResponses.length > 1 || statusCodes.length > 1, - description: descriptions?.[`${variant}Response`]?.call(null, { + description: descriptions?.[`${variant}Response`]?.({ method, path, operationId, @@ -234,7 +247,7 @@ export class Documentation extends OpenApiBuilder { paramNames: R.pluck("name", depictedParams), schema: inputSchema, mimeType: contentTypes[endpoint.requestType], - description: descriptions?.requestBody?.call(null, { + description: descriptions?.requestBody?.({ method, path, operationId, @@ -254,7 +267,7 @@ export class Documentation extends OpenApiBuilder { const operation: OperationObject = { operationId, - summary, + summary: summarizer({ summary, description, trim: trimSummary }), description, deprecated: endpoint.isDeprecated || undefined, tags: nonEmpty(endpoint.tags), diff --git a/express-zod-api/src/endpoint.ts b/express-zod-api/src/endpoint.ts index eaf2f4ce4e..50bcff0e5c 100644 --- a/express-zod-api/src/endpoint.ts +++ b/express-zod-api/src/endpoint.ts @@ -1,34 +1,37 @@ -import { Request, Response } from "express"; +import type { Request, Response } from "express"; import * as R from "ramda"; import { z, globalRegistry } from "zod"; -import { NormalizedResponse, ResponseVariant } from "./api-response"; +import type { NormalizedResponse, ResponseVariant } from "./api-response"; import { findRequestTypeDefiningSchema } from "./deep-checks"; import { - FlatObject, + type FlatObject, getActualMethod, getInput, ensureError, isSchema, } from "./common-helpers"; -import { CommonConfig } from "./config-type"; +import type { CommonConfig } from "./config-type"; import { InputValidationError, OutputValidationError, ResultHandlerError, } from "./errors"; import { ezFormBrand } from "./form-schema"; -import { IOSchema } from "./io-schema"; +import type { IOSchema } from "./io-schema"; import { lastResortHandler } from "./last-resort"; -import { ActualLogger } from "./logger-helpers"; -import { LogicalContainer } from "./logical-container"; -import { getBrand } from "@express-zod-api/zod-plugin"; -import { ClientMethod, CORSMethod, Method, SomeMethod } from "./method"; +import type { ActualLogger } from "./logger-helpers"; +import type { LogicalContainer } from "./logical-container"; +import { getBrand, getExamples } from "./metadata"; +import type { ClientMethod, CORSMethod, Method, SomeMethod } from "./method"; import { AbstractMiddleware, ExpressMiddleware } from "./middleware"; -import { ContentType } from "./content-type"; +import type { ContentType } from "./content-type"; import { ezRawBrand } from "./raw-schema"; -import { DiscriminatedResult, pullResponseExamples } from "./result-helpers"; -import { AbstractResultHandler } from "./result-handler"; -import { Security } from "./security"; +import { + type DiscriminatedResult, + pullResponseExamples, +} from "./result-helpers"; +import type { AbstractResultHandler } from "./result-handler"; +import type { Security } from "./security"; import { ezUploadBrand } from "./upload-schema"; import type { Routing } from "./routing"; @@ -63,7 +66,7 @@ export abstract class AbstractEndpoint { /** @internal */ public abstract get description(): string | undefined; /** @internal */ - public abstract get shortDescription(): string | undefined; + public abstract get summary(): string | undefined; /** @internal */ public abstract get methods(): ReadonlyArray | undefined; /** @internal */ @@ -89,9 +92,9 @@ export class Endpoint< > extends AbstractEndpoint { readonly #def: ConstructorParameters>[0]; - /** considered expensive operation, only required for generators */ + /** considered an expensive operation, only required for generators */ #ensureOutputExamples = R.once(() => { - if (globalRegistry.get(this.#def.outputSchema)?.examples?.length) return; // examples on output schema, or pull up: + if (getExamples(this.#def.outputSchema).length) return; // examples on output schema, or pull up: if (!isSchema(this.#def.outputSchema, "object")) return; const examples = pullResponseExamples(this.#def.outputSchema); if (!examples.length) return; @@ -109,7 +112,7 @@ export class Endpoint< handler: Handler, z.input, CTX>; resultHandler: AbstractResultHandler; description?: string; - shortDescription?: string; + summary?: string; getOperationId?: (method: ClientMethod) => string | undefined; methods?: Method[]; scopes?: string[]; @@ -140,8 +143,8 @@ export class Endpoint< } /** @internal */ - public override get shortDescription() { - return this.#def.shortDescription; + public override get summary() { + return this.#def.summary; } /** @internal */ @@ -295,7 +298,7 @@ export class Endpoint< }) { const method = getActualMethod(request); const ctx: Partial = {}; - let result: DiscriminatedResult = { output: {}, error: null }; + let result: DiscriminatedResult; const input = getInput(request, config.inputSources); try { await this.#runMiddlewares({ diff --git a/express-zod-api/src/endpoints-factory.ts b/express-zod-api/src/endpoints-factory.ts index 99443e7615..d470ca8b76 100644 --- a/express-zod-api/src/endpoints-factory.ts +++ b/express-zod-api/src/endpoints-factory.ts @@ -1,21 +1,21 @@ -import { Request, Response } from "express"; +import type { Request, Response } from "express"; import { z } from "zod"; import { - EmptyObject, emptySchema, - EmptySchema, - FlatObject, - Tag, + type EmptyObject, + type EmptySchema, + type FlatObject, + type Tag, } from "./common-helpers"; -import { Endpoint, Handler } from "./endpoint"; +import { Endpoint, type Handler } from "./endpoint"; import { - IOSchema, - FinalInputSchema, - Extension, ensureExtension, makeFinalInputSchema, + type IOSchema, + type FinalInputSchema, + type Extension, } from "./io-schema"; -import { ClientMethod, Method } from "./method"; +import type { ClientMethod, Method } from "./method"; import { AbstractMiddleware, ExpressMiddleware, @@ -44,10 +44,10 @@ interface BuildProps< output: OUT; /** @desc The Endpoint handler receiving the validated inputs, returns of added Middlewares (ctx) and a logger */ handler: Handler>, z.input, CTX>; - /** @desc The operation description for the generated Documentation */ + /** @desc The operation description for the generated Documentation (may use Markdown) */ description?: string; - /** @desc The operation summary for the generated Documentation (50 symbols max) */ - shortDescription?: string; + /** @desc The operation summary for the generated Documentation (short plain string) */ + summary?: string; /** @desc The operation ID for the generated Documentation (must be unique) */ operationId?: string | ((method: ClientMethod) => string); /** diff --git a/express-zod-api/src/errors.ts b/express-zod-api/src/errors.ts index 3da95acea6..65b50c46d8 100644 --- a/express-zod-api/src/errors.ts +++ b/express-zod-api/src/errors.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { getMessageFromError } from "./common-helpers"; -import { OpenAPIContext } from "./documentation-helpers"; +import type { OpenAPIContext } from "./documentation-helpers"; import type { Method } from "./method"; /** @desc An error related to the wrong Routing declaration */ diff --git a/express-zod-api/src/form-schema.ts b/express-zod-api/src/form-schema.ts index eda1d287be..466c5725d8 100644 --- a/express-zod-api/src/form-schema.ts +++ b/express-zod-api/src/form-schema.ts @@ -1,9 +1,10 @@ import { z } from "zod"; +import { brandProperty } from "./metadata"; export const ezFormBrand = Symbol("Form"); /** @desc Accepts an object shape or a custom object schema */ export const form = (base: S | z.ZodObject) => - (base instanceof z.ZodObject ? base : z.object(base)).brand( - ezFormBrand as symbol, - ); + (base instanceof z.ZodObject ? base : z.object(base)).meta({ + [brandProperty]: ezFormBrand, + }); diff --git a/express-zod-api/src/graceful-helpers.ts b/express-zod-api/src/graceful-helpers.ts index 02b41da534..717dc36d11 100644 --- a/express-zod-api/src/graceful-helpers.ts +++ b/express-zod-api/src/graceful-helpers.ts @@ -32,8 +32,8 @@ export const isEncrypted = (socket: Socket): boolean => export const weAreClosed: http.RequestListener = ({}, res) => void (!res.headersSent && res.setHeader("connection", "close")); -export const closeAsync = (server: Server) => - new Promise( - (resolve, reject) => - void server.close((error) => (error ? reject(error) : resolve())), - ); +export const closeAsync = (server: Server) => { + const { promise, resolve, reject } = Promise.withResolvers(); + server.close((error) => (error ? reject(error) : resolve())); + return promise; +}; diff --git a/express-zod-api/src/index.ts b/express-zod-api/src/index.ts index 19ff1715a4..986cecf0ce 100644 --- a/express-zod-api/src/index.ts +++ b/express-zod-api/src/index.ts @@ -1,4 +1,3 @@ -import "@express-zod-api/zod-plugin"; // side effects here export { createConfig } from "./config-type"; export { EndpointsFactory, diff --git a/express-zod-api/src/integration-base.ts b/express-zod-api/src/integration-base.ts index 744439dd6e..f48359b27f 100644 --- a/express-zod-api/src/integration-base.ts +++ b/express-zod-api/src/integration-base.ts @@ -1,10 +1,10 @@ import * as R from "ramda"; import type ts from "typescript"; -import { ResponseVariant } from "./api-response"; +import type { ResponseVariant } from "./api-response"; import { contentTypes } from "./content-type"; -import { ClientMethod, clientMethods } from "./method"; +import { clientMethods, type ClientMethod } from "./method"; import type { makeEventSchema } from "./sse"; -import { propOf, Typeable, TypescriptAPI } from "./typescript-api"; +import { propOf, TypescriptAPI, type Typeable } from "./typescript-api"; import type { CursorPaginatedResult, OffsetPaginatedResult, diff --git a/express-zod-api/src/integration.ts b/express-zod-api/src/integration.ts index 46f50438cb..fde70a8f01 100644 --- a/express-zod-api/src/integration.ts +++ b/express-zod-api/src/integration.ts @@ -1,17 +1,17 @@ import * as R from "ramda"; import type ts from "typescript"; import { z } from "zod"; -import { ResponseVariant, responseVariants } from "./api-response"; +import { responseVariants, type ResponseVariant } from "./api-response"; import { IntegrationBase } from "./integration-base"; import { shouldHaveContent, makeCleanId } from "./common-helpers"; import { loadPeer } from "./peer-helpers"; -import { Routing } from "./routing"; -import { OnEndpoint, walkRouting, withHead } from "./routing-walker"; -import { HandlingRules } from "./schema-walker"; +import type { Routing } from "./routing"; +import { walkRouting, withHead, type OnEndpoint } from "./routing-walker"; +import type { HandlingRules } from "./schema-walker"; import { zodToTs } from "./zts"; -import { ZTSContext } from "./zts-helpers"; +import type { ZTSContext } from "./zts-helpers"; import type Prettier from "prettier"; -import { ClientMethod } from "./method"; +import type { ClientMethod } from "./method"; import type { CommonConfig } from "./config-type"; interface IntegrationParams { @@ -38,17 +38,18 @@ interface IntegrationParams { * @desc The schema to use for responses without body such as 204 * @default z.undefined() * */ - noContent?: z.ZodType; + noBodySchema?: z.ZodType; /** * @desc Depict the HEAD method for each Endpoint supporting the GET method (feature of Express) * @default true * */ hasHeadMethod?: boolean; /** - * @desc Handling rules for your own branded schemas. + * @desc Handling rules for your own schemas branded with `x-brand` metadata. * @desc Keys: brands (recommended to use unique symbols). * @desc Values: functions having schema as first argument that you should assign type to, second one is a context. - * @example { MyBrand: ( schema: typeof myBrandSchema, { next } ) => createKeywordTypeNode(SyntaxKind.AnyKeyword) + * @example { MyBrand: (schema: typeof myBrandSchema, { next }) => createKeywordTypeNode(SyntaxKind.AnyKeyword) + * @link https://www.npmjs.com/package/@express-zod-api/zod-plugin */ brandHandling?: HandlingRules; } @@ -88,7 +89,7 @@ export class Integration extends IntegrationBase { clientClassName = "Client", subscriptionClassName = "Subscription", serverUrl = "https://example.com", - noContent = z.undefined(), + noBodySchema = z.undefined(), hasHeadMethod = true, }: IntegrationParams) { super(typescript, serverUrl); @@ -109,10 +110,10 @@ export class Integration extends IntegrationBase { (agg, responseVariant) => { const responses = endpoint.getResponses(responseVariant); const props = R.chain(([idx, { schema, mimeTypes, statusCodes }]) => { - const hasContent = shouldHaveContent(method, mimeTypes); + const hasBody = shouldHaveContent(method, mimeTypes); const variantType = this.api.makeType( entitle(responseVariant, "variant", `${idx + 1}`), - zodToTs(hasContent ? schema : noContent, ctxOut), + zodToTs(hasBody ? schema : noBodySchema, ctxOut), { comment: request }, ); this.#program.push(variantType); diff --git a/express-zod-api/src/json-schema-helpers.ts b/express-zod-api/src/json-schema-helpers.ts index 3eceb01a1f..3b733be227 100644 --- a/express-zod-api/src/json-schema-helpers.ts +++ b/express-zod-api/src/json-schema-helpers.ts @@ -1,5 +1,5 @@ import * as R from "ramda"; -import { combinations, FlatObject, isObject } from "./common-helpers"; +import { combinations, isObject, type FlatObject } from "./common-helpers"; import type { z } from "zod"; import type { SchemaObject } from "openapi3-ts/oas31"; diff --git a/express-zod-api/src/last-resort.ts b/express-zod-api/src/last-resort.ts index a37b81151d..2f62dab97e 100644 --- a/express-zod-api/src/last-resort.ts +++ b/express-zod-api/src/last-resort.ts @@ -1,7 +1,7 @@ -import { Response } from "express"; +import type { Response } from "express"; import createHttpError, { isHttpError } from "http-errors"; import { ResultHandlerError } from "./errors"; -import { ActualLogger } from "./logger-helpers"; +import type { ActualLogger } from "./logger-helpers"; import { getPublicErrorMessage } from "./result-helpers"; interface LastResortHandlerParams { diff --git a/express-zod-api/src/metadata.ts b/express-zod-api/src/metadata.ts new file mode 100644 index 0000000000..6b6d4584e0 --- /dev/null +++ b/express-zod-api/src/metadata.ts @@ -0,0 +1,22 @@ +import type { brandProperty as brandProp } from "../../zod-plugin/src/brand.ts"; +import { globalRegistry, type z } from "zod"; + +export const brandProperty = "x-brand" satisfies typeof brandProp; + +export const getBrand = (subject: z.core.$ZodType) => { + const { [brandProperty]: brand } = globalRegistry.get(subject) || {}; + if ( + typeof brand === "symbol" || + typeof brand === "string" || + typeof brand === "number" + ) + return brand; + return undefined; +}; + +/** @desc Returns examples from the schema metadata always as an array */ +export const getExamples = (subject: z.core.$ZodType): unknown[] => { + const { examples } = globalRegistry.get(subject) || {}; + if (Array.isArray(examples)) return examples; + return []; +}; diff --git a/express-zod-api/src/middleware.ts b/express-zod-api/src/middleware.ts index 68380778aa..e83919457f 100644 --- a/express-zod-api/src/middleware.ts +++ b/express-zod-api/src/middleware.ts @@ -1,11 +1,11 @@ -import { NextFunction, Request, Response } from "express"; +import type { NextFunction, Request, Response } from "express"; import { z } from "zod"; -import { emptySchema, FlatObject } from "./common-helpers"; +import { emptySchema, type FlatObject } from "./common-helpers"; import { InputValidationError } from "./errors"; -import { IOSchema } from "./io-schema"; -import { LogicalContainer } from "./logical-container"; -import { Security } from "./security"; -import { ActualLogger } from "./logger-helpers"; +import type { IOSchema } from "./io-schema"; +import type { LogicalContainer } from "./logical-container"; +import type { Security } from "./security"; +import type { ActualLogger } from "./logger-helpers"; import { isPromise } from "node:util/types"; type Handler = (params: { @@ -123,15 +123,16 @@ export class ExpressMiddleware< } = {}, ) { super({ - handler: async ({ request, response }) => - new Promise((resolve, reject) => { - const next = (err?: unknown) => { - if (err && err instanceof Error) return reject(transformer(err)); - resolve(provider(request as R, response as S)); - }; - const returns = nativeMw(request as R, response as S, next); - if (isPromise(returns)) returns.catch(next); - }), + handler: async ({ request, response }) => { + const { promise, resolve, reject } = Promise.withResolvers(); + const next = (err?: unknown) => { + if (err && err instanceof Error) return reject(transformer(err)); + resolve(provider(request as R, response as S)); + }; + const returns = nativeMw(request as R, response as S, next); + if (isPromise(returns)) returns.catch(next); + return promise; + }, }); } } diff --git a/express-zod-api/src/raw-schema.ts b/express-zod-api/src/raw-schema.ts index d779b85b81..56f02a518c 100644 --- a/express-zod-api/src/raw-schema.ts +++ b/express-zod-api/src/raw-schema.ts @@ -1,20 +1,21 @@ import { z } from "zod"; import { buffer } from "./buffer-schema"; +import { brandProperty } from "./metadata"; export const ezRawBrand = Symbol("Raw"); const base = z.object({ raw: buffer() }); -type Base = ReturnType>; +type Base = typeof base; const extended = (extra: S) => - base.extend(extra).brand(ezRawBrand as symbol); + base.extend(extra).meta({ [brandProperty]: ezRawBrand }); export function raw(): Base; export function raw( extra: S, ): ReturnType>; export function raw(extra?: z.core.$ZodShape) { - return extra ? extended(extra) : base.brand(ezRawBrand as symbol); + return extra ? extended(extra) : base.meta({ [brandProperty]: ezRawBrand }); } export type RawSchema = Base; diff --git a/express-zod-api/src/result-handler.ts b/express-zod-api/src/result-handler.ts index 937b4e6079..3b60dc5ffa 100644 --- a/express-zod-api/src/result-handler.ts +++ b/express-zod-api/src/result-handler.ts @@ -1,22 +1,23 @@ -import { Request, Response } from "express"; +import type { Request, Response } from "express"; import { globalRegistry, z } from "zod"; import { - ApiResponse, defaultStatusCodes, - NormalizedResponse, + type ApiResponse, + type NormalizedResponse, } from "./api-response"; -import { FlatObject, isObject } from "./common-helpers"; +import { isObject, type FlatObject } from "./common-helpers"; import { contentTypes } from "./content-type"; -import { IOSchema } from "./io-schema"; -import { ActualLogger } from "./logger-helpers"; +import type { IOSchema } from "./io-schema"; +import type { ActualLogger } from "./logger-helpers"; import { - DiscriminatedResult, + type DiscriminatedResult, + type ResultSchema, ensureHttpError, getPublicErrorMessage, logServerError, normalize, - ResultSchema, } from "./result-helpers"; +import { getExamples } from "./metadata"; type Handler = ( params: DiscriminatedResult & { @@ -110,8 +111,8 @@ export const defaultResultHandler = new ResultHandler({ status: z.literal("success"), data: output, }); - const { examples } = globalRegistry.get(output) || {}; // pulling down: - if (examples?.length) { + const examples = getExamples(output); // pulling down: + if (examples.length) { globalRegistry.add(responseSchema, { examples: examples.map((data) => ({ status: "success" as const, @@ -160,11 +161,9 @@ export const arrayResultHandler = new ResultHandler({ output.shape.items instanceof z.ZodArray ? output.shape.items : z.array(z.any()); - if (globalRegistry.get(responseSchema)?.examples?.length) - return responseSchema; // has examples on the items, or pull down: - const examples = globalRegistry - .get(output) - ?.examples?.filter( + if (getExamples(responseSchema).length) return responseSchema; // has examples on the items, or pull down: + const examples = getExamples(output) + .filter( (example): example is { items: unknown[] } => isObject(example) && "items" in example && diff --git a/express-zod-api/src/result-helpers.ts b/express-zod-api/src/result-helpers.ts index 0f7cd11627..fb6ae0c6e0 100644 --- a/express-zod-api/src/result-helpers.ts +++ b/express-zod-api/src/result-helpers.ts @@ -1,17 +1,18 @@ -import { Request } from "express"; +import type { Request } from "express"; import createHttpError, { HttpError, isHttpError } from "http-errors"; import * as R from "ramda"; -import { globalRegistry, z } from "zod"; -import { NormalizedResponse, ResponseVariant } from "./api-response"; +import { z } from "zod"; +import type { NormalizedResponse, ResponseVariant } from "./api-response"; import { combinations, - FlatObject, getMessageFromError, isProduction, + type FlatObject, } from "./common-helpers"; import { InputValidationError, ResultHandlerError } from "./errors"; -import { ActualLogger } from "./logger-helpers"; +import type { ActualLogger } from "./logger-helpers"; import type { LazyResult, Result } from "./result-handler"; +import { getExamples } from "./metadata"; export type ResultSchema = R extends Result ? S : never; @@ -90,12 +91,11 @@ export const getPublicErrorMessage = (error: HttpError): string => /** @see pullRequestExamples */ export const pullResponseExamples = (subject: T) => Object.entries(subject._zod.def.shape).reduce( - (acc, [key, schema]) => { - const { examples = [] } = globalRegistry.get(schema) || {}; - return combinations(acc, examples.map(R.objOf(key)), ([left, right]) => ({ - ...left, - ...right, - })); - }, + (acc, [key, schema]) => + combinations( + acc, + getExamples(schema).map(R.objOf(key)), + ([left, right]) => ({ ...left, ...right }), + ), [], ); diff --git a/express-zod-api/src/routing-walker.ts b/express-zod-api/src/routing-walker.ts index 27f1177b81..6988d91ee4 100644 --- a/express-zod-api/src/routing-walker.ts +++ b/express-zod-api/src/routing-walker.ts @@ -1,8 +1,8 @@ import { AbstractEndpoint } from "./endpoint"; import { RoutingError } from "./errors"; -import { ClientMethod, isMethod, Method } from "./method"; -import { Routing } from "./routing"; -import { ServeStatic, StaticHandler } from "./serve-static"; +import { isMethod, type ClientMethod, type Method } from "./method"; +import type { Routing } from "./routing"; +import { ServeStatic, type StaticHandler } from "./serve-static"; import type { CommonConfig } from "./config-type"; export type OnEndpoint = ( @@ -38,11 +38,10 @@ const trimPath = (path: string) => path.trim().split("/").filter(Boolean).join("/"); const processEntries = ( - { methodLikeRouteBehavior = "method" }: CommonConfig, + { recognizeMethodDependentRoutes: preferMethod = true }: CommonConfig, subject: Routing, parent?: string, ) => { - const preferMethod = methodLikeRouteBehavior === "method"; return Object.entries(subject).map<[string, Routing[string], Method?]>( ([_key, item]) => { const [segment, method] = diff --git a/express-zod-api/src/routing.ts b/express-zod-api/src/routing.ts index 7011b9ab97..5a728adc78 100644 --- a/express-zod-api/src/routing.ts +++ b/express-zod-api/src/routing.ts @@ -1,14 +1,14 @@ -import { IRouter, RequestHandler } from "express"; +import type { IRouter, RequestHandler } from "express"; import createHttpError from "http-errors"; import { isProduction } from "./common-helpers"; -import { CommonConfig } from "./config-type"; -import { ContentType } from "./content-type"; +import type { CommonConfig } from "./config-type"; +import type { ContentType } from "./content-type"; import { Diagnostics } from "./diagnostics"; -import { AbstractEndpoint } from "./endpoint"; -import { CORSMethod, isMethod } from "./method"; -import { OnEndpoint, walkRouting } from "./routing-walker"; +import type { AbstractEndpoint } from "./endpoint"; +import { isMethod, type CORSMethod } from "./method"; +import { walkRouting, type OnEndpoint } from "./routing-walker"; import { ServeStatic } from "./serve-static"; -import { GetLogger } from "./server-helpers"; +import type { GetLogger } from "./server-helpers"; import * as R from "ramda"; /** @@ -17,7 +17,7 @@ import * as R from "ramda"; * @example { "get /v1/books/:bookId": getBookEndpoint } * @example { v1: { "patch /books/:bookId": changeBookEndpoint } } * @example { dependsOnMethod: { get: retrieveEndpoint, post: createEndpoint } } - * @see CommonConfig.methodLikeRouteBehavior + * @see CommonConfig.recognizeMethodDependentRoutes * */ export interface Routing { [K: string]: Routing | AbstractEndpoint | ServeStatic; @@ -108,7 +108,7 @@ export const initRouting = ({ app, config, getLogger, ...rest }: InitProps) => { }); app[method](path, ...handlers); } - if (config.wrongMethodBehavior === 404) continue; + if (config.hintAllowedMethods === false) continue; deprioritized.set(path, createWrongMethodHandler(accessMethods)); } for (const [path, handler] of deprioritized) app.all(path, handler); diff --git a/express-zod-api/src/schema-walker.ts b/express-zod-api/src/schema-walker.ts index ef107d54af..3273ea4d54 100644 --- a/express-zod-api/src/schema-walker.ts +++ b/express-zod-api/src/schema-walker.ts @@ -1,5 +1,5 @@ import type { EmptyObject, FlatObject } from "./common-helpers"; -import { getBrand } from "@express-zod-api/zod-plugin"; +import { getBrand } from "./metadata"; import type { z } from "zod"; export type FirstPartyKind = z.core.$ZodTypeDef["type"]; diff --git a/express-zod-api/src/server-helpers.ts b/express-zod-api/src/server-helpers.ts index 2949740d37..7be3b08127 100644 --- a/express-zod-api/src/server-helpers.ts +++ b/express-zod-api/src/server-helpers.ts @@ -1,9 +1,9 @@ import type fileUpload from "express-fileupload"; import { loadPeer } from "./peer-helpers"; -import { AbstractResultHandler } from "./result-handler"; -import { ActualLogger } from "./logger-helpers"; -import { CommonConfig, ServerConfig } from "./config-type"; -import { ErrorRequestHandler, RequestHandler, Request } from "express"; +import type { AbstractResultHandler } from "./result-handler"; +import type { ActualLogger } from "./logger-helpers"; +import type { CommonConfig, ServerConfig } from "./config-type"; +import type { ErrorRequestHandler, RequestHandler, Request } from "express"; import createHttpError from "http-errors"; import { lastResortHandler } from "./last-resort"; import { ResultHandlerError } from "./errors"; diff --git a/express-zod-api/src/server.ts b/express-zod-api/src/server.ts index 76ad38959d..c74ba07bfd 100644 --- a/express-zod-api/src/server.ts +++ b/express-zod-api/src/server.ts @@ -3,7 +3,7 @@ import type compression from "compression"; import http from "node:http"; import https from "node:https"; import { BuiltinLogger } from "./builtin-logger"; -import { +import type { AppConfig, CommonConfig, HttpConfig, @@ -12,7 +12,7 @@ import { import { isLoggerInstance } from "./logger-helpers"; import { loadPeer } from "./peer-helpers"; import { defaultResultHandler } from "./result-handler"; -import { Parsers, Routing, initRouting } from "./routing"; +import { initRouting, type Parsers, type Routing } from "./routing"; import { createLoggingMiddleware, createNotFoundHandler, diff --git a/express-zod-api/src/sse.ts b/express-zod-api/src/sse.ts index 273e12fc0b..152a3a0333 100644 --- a/express-zod-api/src/sse.ts +++ b/express-zod-api/src/sse.ts @@ -1,6 +1,6 @@ -import { Response } from "express"; +import type { Response } from "express"; import { z } from "zod"; -import { FlatObject } from "./common-helpers"; +import type { FlatObject } from "./common-helpers"; import { contentTypes } from "./content-type"; import { EndpointsFactory } from "./endpoints-factory"; import { Middleware } from "./middleware"; diff --git a/express-zod-api/src/startup-logo.ts b/express-zod-api/src/startup-logo.ts index 031bc4039f..88ec8f3694 100644 --- a/express-zod-api/src/startup-logo.ts +++ b/express-zod-api/src/startup-logo.ts @@ -12,7 +12,7 @@ export const printStartupLogo = (stream: WriteStream) => { const thanks = italic( "Thank you for choosing Express Zod API for your project.".padStart(132), ); - const dedicationMessage = italic("for Nikki".padEnd(20)); + const dedicationMessage = italic("for Koko".padEnd(20)); const pink = hex("#F5A9B8"); const blue = hex("#5BCEFA"); diff --git a/express-zod-api/src/testing.ts b/express-zod-api/src/testing.ts index c2d2ec2a84..b6ce76ab5c 100644 --- a/express-zod-api/src/testing.ts +++ b/express-zod-api/src/testing.ts @@ -1,19 +1,19 @@ -import { Request, Response } from "express"; -import { ensureError, FlatObject, getInput } from "./common-helpers"; -import { CommonConfig } from "./config-type"; -import { AbstractEndpoint } from "./endpoint"; +import type { Request, Response } from "express"; +import { ensureError, getInput, type FlatObject } from "./common-helpers"; +import type { CommonConfig } from "./config-type"; +import type { AbstractEndpoint } from "./endpoint"; import { - AbstractLogger, - ActualLogger, isSeverity, - Severity, + type AbstractLogger, + type ActualLogger, + type Severity, } from "./logger-helpers"; import { contentTypes } from "./content-type"; import { createRequest, - RequestOptions, createResponse, - ResponseOptions, + type RequestOptions, + type ResponseOptions, } from "node-mocks-http"; import { AbstractMiddleware } from "./middleware"; import { defaultResultHandler } from "./result-handler"; diff --git a/express-zod-api/src/upload-schema.ts b/express-zod-api/src/upload-schema.ts index 0943b8af17..75181118e8 100644 --- a/express-zod-api/src/upload-schema.ts +++ b/express-zod-api/src/upload-schema.ts @@ -1,6 +1,7 @@ import type { UploadedFile } from "express-fileupload"; import { z } from "zod"; import { isObject } from "./common-helpers"; +import { brandProperty } from "./metadata"; export const ezUploadBrand = Symbol("Upload"); @@ -37,4 +38,4 @@ export const upload = () => }), }, ) - .brand(ezUploadBrand as symbol); + .meta({ [brandProperty]: ezUploadBrand }); diff --git a/express-zod-api/src/zts-helpers.ts b/express-zod-api/src/zts-helpers.ts index 4778b8deac..07bfa8e087 100644 --- a/express-zod-api/src/zts-helpers.ts +++ b/express-zod-api/src/zts-helpers.ts @@ -1,6 +1,6 @@ import type ts from "typescript"; -import { FlatObject } from "./common-helpers"; -import { SchemaHandler } from "./schema-walker"; +import type { FlatObject } from "./common-helpers"; +import type { SchemaHandler } from "./schema-walker"; import type { TypescriptAPI } from "./typescript-api"; export interface ZTSContext extends FlatObject { diff --git a/express-zod-api/src/zts.ts b/express-zod-api/src/zts.ts index 0b82f8dc2a..e1ad60b109 100644 --- a/express-zod-api/src/zts.ts +++ b/express-zod-api/src/zts.ts @@ -6,11 +6,15 @@ import { getTransformedType, isSchema } from "./common-helpers"; import { ezDateInBrand } from "./date-in-schema"; import { ezDateOutBrand } from "./date-out-schema"; import { hasCycle } from "./deep-checks"; -import { ProprietaryBrand } from "./proprietary-schemas"; -import { ezRawBrand, RawSchema } from "./raw-schema"; -import { FirstPartyKind, HandlingRules, walkSchema } from "./schema-walker"; +import type { ProprietaryBrand } from "./proprietary-schemas"; +import { ezRawBrand, type RawSchema } from "./raw-schema"; +import { + walkSchema, + type FirstPartyKind, + type HandlingRules, +} from "./schema-walker"; import type { TypescriptAPI } from "./typescript-api"; -import { Producer, ZTSContext } from "./zts-helpers"; +import type { Producer, ZTSContext } from "./zts-helpers"; const nodePath = { name: R.path([ diff --git a/express-zod-api/tests/__snapshots__/documentation.spec.ts.snap b/express-zod-api/tests/__snapshots__/documentation.spec.ts.snap index 445c6b8dbf..4c291070d8 100644 --- a/express-zod-api/tests/__snapshots__/documentation.spec.ts.snap +++ b/express-zod-api/tests/__snapshots__/documentation.spec.ts.snap @@ -1804,7 +1804,7 @@ paths: cuid: type: string format: cuid - pattern: ^[cC][^\\s-]{8,}$ + pattern: ^[cC][0-9a-z]{6,}$ cuid2: type: string format: cuid2 @@ -5042,7 +5042,6 @@ components: const: John - type: string const: Jane - id: NameParam responses: {} parameters: {} examples: {} diff --git a/express-zod-api/tests/__snapshots__/env.spec.ts.snap b/express-zod-api/tests/__snapshots__/env.spec.ts.snap index e0390176d1..208efdfbd8 100644 --- a/express-zod-api/tests/__snapshots__/env.spec.ts.snap +++ b/express-zod-api/tests/__snapshots__/env.spec.ts.snap @@ -286,14 +286,6 @@ exports[`Environment checks > Zod new features > input examples of transformatio } `; -exports[`Environment checks > Zod new features > meta id goes directly to depiction 1`] = ` -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "id": "uniq", - "type": "string", -} -`; - exports[`Environment checks > Zod new features > meta() merge, not just overrides 1`] = ` { "description": "some", diff --git a/express-zod-api/tests/__snapshots__/zts.spec.ts.snap b/express-zod-api/tests/__snapshots__/zts.spec.ts.snap index 57a0f1d239..613b58f79a 100644 --- a/express-zod-api/tests/__snapshots__/zts.spec.ts.snap +++ b/express-zod-api/tests/__snapshots__/zts.spec.ts.snap @@ -16,7 +16,7 @@ exports[`zod-to-ts > Example > should produce the expected results 1`] = ` enum: "hi" | "bye"; intersectionWithTransform: (number & bigint) & (number & string); date: any; - undefined?: undefined; + undefined: undefined; null: null; void: undefined; any: any; @@ -153,7 +153,7 @@ exports[`zod-to-ts > PrimitiveSchema (isResponse=false) > outputs correct typesc number: number; boolean: boolean; date: any; - undefined?: undefined; + undefined: undefined; null: null; void: undefined; any: any; @@ -168,7 +168,7 @@ exports[`zod-to-ts > PrimitiveSchema (isResponse=true) > outputs correct typescr number: number; boolean: boolean; date: unknown; - undefined?: undefined; + undefined: undefined; null: null; void: undefined; any: any; @@ -242,7 +242,7 @@ exports[`zod-to-ts > z.object() > escapes correctly 1`] = ` $e: any; "4t": any; _r: any; - "-r"?: undefined; + "-r": undefined; }" `; @@ -252,14 +252,7 @@ exports[`zod-to-ts > z.object() > specially handles coercive schema in response }" `; -exports[`zod-to-ts > z.object() > supports string literal properties 1`] = ` -"{ - "5": number; - "string-literal": string; -}" -`; - -exports[`zod-to-ts > z.object() > supports zod.deprecated() 1`] = ` +exports[`zod-to-ts > z.object() > supports deprecated metadata 1`] = ` "{ /** @deprecated */ one: string; @@ -268,6 +261,13 @@ exports[`zod-to-ts > z.object() > supports zod.deprecated() 1`] = ` }" `; +exports[`zod-to-ts > z.object() > supports string literal properties 1`] = ` +"{ + "5": number; + "string-literal": string; +}" +`; + exports[`zod-to-ts > z.object() > supports zod.describe() 1`] = ` "{ /** The name of the item */ diff --git a/express-zod-api/tests/buffer-schema.spec.ts b/express-zod-api/tests/buffer-schema.spec.ts index cdb8bcaf93..02de1f18b3 100644 --- a/express-zod-api/tests/buffer-schema.spec.ts +++ b/express-zod-api/tests/buffer-schema.spec.ts @@ -1,5 +1,5 @@ import { readFile } from "node:fs/promises"; -import { z, $brand } from "zod"; +import { z } from "zod"; import { ez } from "../src"; describe("ez.buffer()", () => { @@ -7,7 +7,7 @@ describe("ez.buffer()", () => { test("should create a Buffer", () => { const schema = ez.buffer(); expect(schema).toBeInstanceOf(z.ZodCustom); - expectTypeOf(schema._zod.output).toEqualTypeOf>(); + expectTypeOf(schema._zod.output).toEqualTypeOf(); }); }); diff --git a/express-zod-api/tests/builtin-logger.spec.ts b/express-zod-api/tests/builtin-logger.spec.ts index 79f7373c1e..974e333723 100644 --- a/express-zod-api/tests/builtin-logger.spec.ts +++ b/express-zod-api/tests/builtin-logger.spec.ts @@ -8,7 +8,7 @@ import { cyanMock, } from "./ansis-mock"; import * as R from "ramda"; -import { BuiltinLogger, BuiltinLoggerConfig } from "../src/builtin-logger"; +import { BuiltinLogger, type BuiltinLoggerConfig } from "../src/builtin-logger"; vi.mock("node:util", { spy: true }); diff --git a/express-zod-api/tests/common-helpers.spec.ts b/express-zod-api/tests/common-helpers.spec.ts index 96b24cc2da..98b401b085 100644 --- a/express-zod-api/tests/common-helpers.spec.ts +++ b/express-zod-api/tests/common-helpers.spec.ts @@ -10,13 +10,13 @@ import { shouldHaveContent, getInputSources, emptySchema, - EmptySchema, - EmptyObject, + type EmptySchema, + type EmptyObject, } from "../src/common-helpers"; import { z } from "zod"; import { makeRequestMock } from "../src/testing"; import { methods } from "../src/method"; -import { CommonConfig, InputSources } from "../src/config-type"; +import type { CommonConfig, InputSources } from "../src/config-type"; describe("Common Helpers", () => { describe("emptySchema", () => { diff --git a/express-zod-api/tests/config-type.spec.ts b/express-zod-api/tests/config-type.spec.ts index ed45f6d90c..3777089209 100644 --- a/express-zod-api/tests/config-type.spec.ts +++ b/express-zod-api/tests/config-type.spec.ts @@ -1,4 +1,4 @@ -import { Express, IRouter } from "express"; +import type { Express, IRouter } from "express"; import { createConfig } from "../src"; describe("ConfigType", () => { diff --git a/express-zod-api/tests/date-in-schema.spec.ts b/express-zod-api/tests/date-in-schema.spec.ts index 57d0d8dac4..aa337cfcc2 100644 --- a/express-zod-api/tests/date-in-schema.spec.ts +++ b/express-zod-api/tests/date-in-schema.spec.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { ezDateInBrand } from "../src/date-in-schema"; import { ez } from "../src"; -import { getBrand } from "@express-zod-api/zod-plugin"; +import { getBrand } from "../src/metadata"; describe("ez.dateIn()", () => { describe("creation", () => { diff --git a/express-zod-api/tests/date-out-schema.spec.ts b/express-zod-api/tests/date-out-schema.spec.ts index fab72dc78f..0aa44a7769 100644 --- a/express-zod-api/tests/date-out-schema.spec.ts +++ b/express-zod-api/tests/date-out-schema.spec.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { ezDateOutBrand } from "../src/date-out-schema"; import { ez } from "../src"; -import { getBrand } from "@express-zod-api/zod-plugin"; +import { getBrand } from "../src/metadata"; describe("ez.dateOut()", () => { describe("creation", () => { diff --git a/express-zod-api/tests/deep-checks.spec.ts b/express-zod-api/tests/deep-checks.spec.ts index 8308daf0b1..26d44f1d9e 100644 --- a/express-zod-api/tests/deep-checks.spec.ts +++ b/express-zod-api/tests/deep-checks.spec.ts @@ -1,8 +1,8 @@ -import { UploadedFile } from "express-fileupload"; +import type { UploadedFile } from "express-fileupload"; import { z } from "zod"; import { ez } from "../src"; import { findNestedSchema, hasCycle } from "../src/deep-checks"; -import { getBrand } from "@express-zod-api/zod-plugin"; +import { getBrand } from "../src/metadata"; import { ezUploadBrand } from "../src/upload-schema"; describe("Checks", () => { @@ -22,7 +22,7 @@ describe("Checks", () => { z.object({ test: z.boolean() }).and(z.object({ test2: ez.upload() })), z.optional(ez.upload()), ez.upload().nullable(), - ez.upload().default({} as UploadedFile & z.core.$brand), + ez.upload().default({} as UploadedFile), z.record(z.string(), ez.upload()), ez.upload().refine(() => true), z.array(ez.upload()), diff --git a/express-zod-api/tests/documentation-helpers.spec.ts b/express-zod-api/tests/documentation-helpers.spec.ts index 9f5d45d373..4cf04583c5 100644 --- a/express-zod-api/tests/documentation-helpers.spec.ts +++ b/express-zod-api/tests/documentation-helpers.spec.ts @@ -1,14 +1,14 @@ -import { SchemaObject } from "openapi3-ts/oas31"; +import type { SchemaObject } from "openapi3-ts/oas31"; import * as R from "ramda"; import { z } from "zod"; import { ez } from "../src"; import { - OpenAPIContext, + type OpenAPIContext, depictRequestParams, depictSecurity, depictSecurityRefs, depictTags, - ensureShortDescription, + trimSummary, excludeParamsFromDepiction, defaultIsHeader, reformatParamsInPath, @@ -694,20 +694,22 @@ describe("Documentation helpers", () => { }); }); - describe("ensureShortDescription()", () => { - test("keeps the short text as it is", () => { - expect(ensureShortDescription("here is a short text")).toBe( - "here is a short text", - ); - expect(ensureShortDescription(" ")).toBe(" "); - expect(ensureShortDescription("")).toBe(""); - }); + describe("trimSummary()", () => { + test.each(["here is a short text", " ", ""])( + "keeps the short text as it is %#", + (summary) => { + expect(trimSummary(summary)).toBe(summary); + }, + ); test("trims the long text", () => { expect( - ensureShortDescription( + trimSummary( "this text is definitely too long for the short description", ), ).toBe("this text is definitely too long for the short de…"); }); + test("accepts undefined as is", () => { + expect(trimSummary()).toBeUndefined(); + }); }); }); diff --git a/express-zod-api/tests/documentation.spec.ts b/express-zod-api/tests/documentation.spec.ts index 136a21addf..2972721ad0 100644 --- a/express-zod-api/tests/documentation.spec.ts +++ b/express-zod-api/tests/documentation.spec.ts @@ -9,13 +9,14 @@ import { defaultEndpointsFactory, ez, ResultHandler, - Depicter, - Method, + type Depicter, + type Method, } from "../src"; import { contentTypes } from "../src/content-type"; import { z } from "zod"; import { givePort } from "../../tools/ports"; import * as R from "ramda"; +import { brandProperty } from "../src/metadata"; describe("Documentation", () => { const sampleConfig = createConfig({ @@ -93,7 +94,7 @@ describe("Documentation", () => { labeledDate: z.iso .datetime() .default(() => new Date().toISOString()) - .label("Today"), // Feature #1706 + .meta({ default: "Today" }), // Feature #1706 }), output: z.object({ nullable: z.string().nullable(), @@ -588,7 +589,7 @@ describe("Documentation", () => { getSome: { thing: defaultEndpointsFactory.build({ description: "thing is the path segment", - shortDescription: "operationIdEndpoint", + summary: "operationIdEndpoint", output: z.object({}), handler: async () => ({}), }), @@ -991,7 +992,7 @@ describe("Documentation", () => { test("Feature #2390: should support deprecations", () => { const endpoint = defaultEndpointsFactory.build({ input: z.object({ - str: z.string().deprecated(), + str: z.string().meta({ deprecated: true }), }), output: z.object({}), handler: vi.fn(), @@ -1046,14 +1047,14 @@ describe("Documentation", () => { input: z.object({ strNum: z .string() - .example("123") // example for the input side of the transformation + .meta({ examples: ["123"] }) // example for the input side of the transformation .transform((v) => parseInt(v, 10)), }), output: z.object({ numericStr: z .number() .transform((v) => `${v}`) - .example("456"), // example for the output side of the transformation + .meta({ examples: ["456"] }), // example for the output side of the transformation }), handler: async () => ({ numericStr: 123 }), }), @@ -1078,15 +1079,13 @@ describe("Documentation", () => { method, input: z .object({ strNum: z.string() }) - .example({ strNum: "123" }) // example is for input side of the transformation + .meta({ examples: [{ strNum: "123" }] }) // example is for input side of the transformation .transform(R.mapObjIndexed(Number)), output: z .object({ numericStr: z.number().transform((v) => `${v}`), }) - .example({ - numericStr: "123", // example is for output side of the transformation - }), + .meta({ examples: [{ numericStr: "123" }] }), // example is for output side of the transformation handler: async () => ({ numericStr: 123 }), }), }, @@ -1108,13 +1107,17 @@ describe("Documentation", () => { .addMiddleware({ input: z .object({ key: z.string() }) - .example({ key: "1234-56789-01" }), + .meta({ examples: [{ key: "1234-56789-01" }] }), handler: vi.fn(), }) .build({ method: "post", - input: z.object({ str: z.string() }).example({ str: "test" }), - output: z.object({ num: z.number() }).example({ num: 123 }), + input: z + .object({ str: z.string() }) + .meta({ examples: [{ str: "test" }] }), + output: z + .object({ num: z.number() }) + .meta({ examples: [{ num: 123 }] }), handler: async () => ({ num: 123 }), }), }, @@ -1133,13 +1136,17 @@ describe("Documentation", () => { v1: { getSomething: defaultEndpointsFactory .addMiddleware({ - input: z.object({ key: z.string().example("1234-56789-01") }), + input: z.object({ + key: z.string().meta({ examples: ["1234-56789-01"] }), + }), handler: vi.fn(), }) .build({ method: "post", - input: z.object({ str: z.string().example("test") }), - output: z.object({ num: z.number().example(123) }), + input: z.object({ + str: z.string().meta({ examples: ["test"] }), + }), + output: z.object({ num: z.number().meta({ examples: [123] }) }), handler: async () => ({ num: 123 }), }), }, @@ -1159,11 +1166,13 @@ describe("Documentation", () => { v1: { addSomething: defaultEndpointsFactory.build({ method: "post", - input: zodSchema.example({ a: "first" }), - output: zodSchema - .extend({ b: z.string() }) - .example({ a: "first", b: "prefix_first" }) - .example({ a: "second", b: "prefix_second" }), + input: zodSchema.meta({ examples: [{ a: "first" }] }), + output: zodSchema.extend({ b: z.string() }).meta({ + examples: [ + { a: "first", b: "prefix_first" }, + { a: "second", b: "prefix_second" }, + ], + }), handler: async ({ input: { a } }) => ({ a, b: `prefix_${a}` }), }), }, @@ -1186,12 +1195,12 @@ describe("Documentation", () => { v1: { ":name": defaultEndpointsFactory.build({ input: z.object({ - name: z.string().brand("CUSTOM"), - other: z.boolean().brand("CUSTOM"), - regular: z.boolean().brand(deep), + name: z.string().meta({ [brandProperty]: "CUSTOM" }), + other: z.boolean().meta({ [brandProperty]: "CUSTOM" }), + regular: z.boolean().meta({ [brandProperty]: deep }), }), output: z.object({ - number: z.number().brand("CUSTOM"), + number: z.number().meta({ [brandProperty]: "CUSTOM" }), }), handler: vi.fn(), }), @@ -1223,7 +1232,8 @@ describe("Documentation", () => { .transform((inputs) => camelize(inputs, true)), output: z .object({ userName: z.string() }) - .remap((outputs) => snakify(outputs, true)), + .transform((outputs) => snakify(outputs, true)) + .pipe(z.object({ user_name: z.string() })), // zod plugin's remap emulation handler: async ({ input: { userId } }) => ({ userName: `User ${userId}`, }), @@ -1245,10 +1255,15 @@ describe("Documentation", () => { test: defaultEndpointsFactory.build({ input: z .object({ user_id: z.string(), at: ez.dateIn() }) - .remap({ user_id: "userId" }), // partial mapping + .transform(({ user_id: userId, ...rest }) => ({ + ...rest, + userId, // partial mapping + })) + .pipe(z.object({ userId: z.string(), at: z.date() })), output: z .object({ userName: z.string() }) - .remap({ userName: "user_name" }), + .transform(({ userName: user_name }) => ({ user_name })) + .pipe(z.object({ user_name: z.string() })), handler: async ({ input: { userId, at } }) => ({ userName: `User ${userId} ${at}`, }), diff --git a/express-zod-api/tests/endpoints-factory.spec.ts b/express-zod-api/tests/endpoints-factory.spec.ts index 96f6120cd5..1c5b636dec 100644 --- a/express-zod-api/tests/endpoints-factory.spec.ts +++ b/express-zod-api/tests/endpoints-factory.spec.ts @@ -1,4 +1,4 @@ -import { RequestHandler } from "express"; +import type { RequestHandler } from "express"; import createHttpError from "http-errors"; import { expectTypeOf } from "vitest"; import { @@ -8,7 +8,7 @@ import { ResultHandler, testMiddleware, } from "../src"; -import { EmptyObject } from "../src/common-helpers"; +import type { EmptyObject } from "../src/common-helpers"; import { Endpoint } from "../src/endpoint"; import { z } from "zod"; diff --git a/express-zod-api/tests/env.spec.ts b/express-zod-api/tests/env.spec.ts index 49f7c923d3..2e2f2f7c7c 100644 --- a/express-zod-api/tests/env.spec.ts +++ b/express-zod-api/tests/env.spec.ts @@ -194,8 +194,10 @@ describe("Environment checks", () => { }, ); - test("meta id goes directly to depiction", () => { - expect(z.toJSONSchema(z.string().meta({ id: "uniq" }))).toMatchSnapshot(); + test("meta id does NOT go into depiction", () => { + expect( + z.toJSONSchema(z.string().meta({ id: "uniq" })), + ).not.toHaveProperty("id"); }); }); diff --git a/express-zod-api/tests/form-schema.spec.ts b/express-zod-api/tests/form-schema.spec.ts index fe4ac34fbd..53d31b03eb 100644 --- a/express-zod-api/tests/form-schema.spec.ts +++ b/express-zod-api/tests/form-schema.spec.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { ez } from "../src"; import { ezFormBrand } from "../src/form-schema"; -import { getBrand } from "@express-zod-api/zod-plugin"; +import { getBrand } from "../src/metadata"; describe("ez.form()", () => { describe("creation", () => { diff --git a/express-zod-api/tests/graceful-shutdown.spec.ts b/express-zod-api/tests/graceful-shutdown.spec.ts index e8824f2a9c..b8b5a275dc 100644 --- a/express-zod-api/tests/graceful-shutdown.spec.ts +++ b/express-zod-api/tests/graceful-shutdown.spec.ts @@ -8,26 +8,28 @@ import { givePort } from "../../tools/ports"; import { signCert } from "./ssl-helpers"; describe("monitor()", () => { - const makeHttpServer = (handler: http.RequestListener) => - new Promise<[http.Server, number]>((resolve) => { - const subject = http.createServer(handler); - const port = givePort(); - subject.listen(port, () => resolve([subject, port])); - }); + const makeHttpServer = (handler: http.RequestListener) => { + const { promise, resolve } = Promise.withResolvers<[http.Server, number]>(); + const subject = http.createServer(handler); + const port = givePort(); + subject.listen(port, () => resolve([subject, port])); + return promise; + }; - const makeHttpsServer = (handler: http.RequestListener) => - new Promise<[https.Server, number]>((resolve) => { - const subject = https.createServer(signCert(), handler); - const port = givePort(); - subject.listen(port, () => resolve([subject, port])); - }); + const makeHttpsServer = (handler: http.RequestListener) => { + const { promise, resolve } = + Promise.withResolvers<[https.Server, number]>(); + const subject = https.createServer(signCert(), handler); + const port = givePort(); + subject.listen(port, () => resolve([subject, port])); + return promise; + }; - const getConnections = (server: http.Server) => - new Promise((resolve, reject) => { - server.getConnections((err, count) => - err ? reject(err) : resolve(count), - ); - }); + const getConnections = (server: http.Server) => { + const { promise, resolve, reject } = Promise.withResolvers(); + server.getConnections((err, count) => (err ? reject(err) : resolve(count))); + return promise; + }; test( "shuts down HTTP server with no connections", diff --git a/express-zod-api/tests/index.spec.ts b/express-zod-api/tests/index.spec.ts index 0ebd4cba06..e0c9920459 100644 --- a/express-zod-api/tests/index.spec.ts +++ b/express-zod-api/tests/index.spec.ts @@ -1,8 +1,8 @@ -import { IRouter } from "express"; +import type { IRouter } from "express"; import ts from "typescript"; import { z } from "zod"; import * as entrypoint from "../src"; -import { +import type { ApiResponse, AppConfig, BasicSecurity, diff --git a/express-zod-api/tests/integration.spec.ts b/express-zod-api/tests/integration.spec.ts index 9edc16d10d..014081ae96 100644 --- a/express-zod-api/tests/integration.spec.ts +++ b/express-zod-api/tests/integration.spec.ts @@ -3,10 +3,11 @@ import { globalRegistry, z } from "zod"; import { EndpointsFactory, Integration, - Producer, defaultEndpointsFactory, ResultHandler, + type Producer, } from "../src"; +import { brandProperty } from "../src/metadata"; describe("Integration", () => { const recursive1: z.ZodType = z.lazy(() => @@ -149,11 +150,11 @@ describe("Integration", () => { custom: defaultEndpointsFactory.build({ method: "post", input: z.object({ - string: z.string().brand("CUSTOM"), - regular: z.string().brand("DEEP"), + string: z.string().meta({ [brandProperty]: "CUSTOM" }), + regular: z.string().meta({ [brandProperty]: "DEEP" }), }), output: z.object({ - number: z.number().brand("CUSTOM"), + number: z.number().meta({ [brandProperty]: "CUSTOM" }), }), handler: vi.fn(), }), diff --git a/express-zod-api/tests/io-schema.spec.ts b/express-zod-api/tests/io-schema.spec.ts index dde7e5600b..11ab90bc4d 100644 --- a/express-zod-api/tests/io-schema.spec.ts +++ b/express-zod-api/tests/io-schema.spec.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { IOSchema, ez } from "../src"; +import { ez, type IOSchema } from "../src"; import { makeFinalInputSchema, ensureExtension } from "../src/io-schema"; describe("I/O Schema and related helpers", () => { @@ -106,9 +106,6 @@ describe("I/O Schema and related helpers", () => { .transform(() => ({ n: 123 })) .pipe(z.object({ n: z.number() })), ).toExtend(); - expectTypeOf( - z.object({ user_id: z.string() }).remap({ user_id: "userId" }), - ).toExtend(); }); test("does not accept transformation to another type", () => { expectTypeOf( diff --git a/express-zod-api/tests/logger-helpers.spec.ts b/express-zod-api/tests/logger-helpers.spec.ts index 4529f2cb56..2ae7faadb3 100644 --- a/express-zod-api/tests/logger-helpers.spec.ts +++ b/express-zod-api/tests/logger-helpers.spec.ts @@ -1,12 +1,12 @@ import { BuiltinLogger } from "../src"; -import { BuiltinLoggerConfig } from "../src/builtin-logger"; +import type { BuiltinLoggerConfig } from "../src/builtin-logger"; import { - AbstractLogger, isLoggerInstance, isSeverity, isHidden, makeNumberFormat, formatDuration, + type AbstractLogger, } from "../src/logger-helpers"; describe("Logger helpers", () => { diff --git a/express-zod-api/tests/metadata.spec.ts b/express-zod-api/tests/metadata.spec.ts new file mode 100644 index 0000000000..9b99243664 --- /dev/null +++ b/express-zod-api/tests/metadata.spec.ts @@ -0,0 +1,45 @@ +import { globalRegistry, z } from "zod"; +import { brandProperty, getBrand, getExamples } from "../src/metadata"; + +describe("Metadata helpers", () => { + describe("getBrand()", () => { + test.each([{ [brandProperty]: "test" }, {}, undefined])( + "should take it from metadata in globalRegistry %#", + (metadata) => { + const subject = z.string(); + if (metadata) globalRegistry.add(subject, metadata); + expect(getBrand(subject)).toBe(metadata?.[brandProperty]); + }, + ); + test.each([true, null, new Date()])( + "should ignore invalid values %#", + (brand) => { + const subject = z.string(); + globalRegistry.add(subject, { [brandProperty]: brand }); + expect(getBrand(subject)).toBeUndefined(); + }, + ); + }); + + describe("getExamples()", () => { + test.each([ + { examples: [123, 456] }, + { examples: [] }, + { examples: undefined }, + {}, + ])("always returns an array %#", (metadata) => { + const subject = z.number(); + globalRegistry.add(subject, metadata); + expect(getExamples(subject)).toEqual(metadata.examples ?? []); + }); + + test.each([{}, 123, true, "test"])( + "should ignore invalid values %#", + (examples) => { + const subject = z.number(); + globalRegistry.add(subject, { examples }); + expect(getExamples(subject)).toEqual([]); + }, + ); + }); +}); diff --git a/express-zod-api/tests/method.spec.ts b/express-zod-api/tests/method.spec.ts index 7bab93da7a..6ed97cbd57 100644 --- a/express-zod-api/tests/method.spec.ts +++ b/express-zod-api/tests/method.spec.ts @@ -2,11 +2,11 @@ import * as R from "ramda"; import { isMethod, methods, - Method, clientMethods, - ClientMethod, - SomeMethod, - CORSMethod, + type Method, + type ClientMethod, + type SomeMethod, + type CORSMethod, } from "../src/method"; describe("Method", () => { diff --git a/express-zod-api/tests/raw-schema.spec.ts b/express-zod-api/tests/raw-schema.spec.ts index f0104ffad4..7f648deab5 100644 --- a/express-zod-api/tests/raw-schema.spec.ts +++ b/express-zod-api/tests/raw-schema.spec.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { ez } from "../src"; -import { getBrand } from "@express-zod-api/zod-plugin"; +import { getBrand } from "../src/metadata"; import { ezRawBrand } from "../src/raw-schema"; describe("ez.raw()", () => { diff --git a/express-zod-api/tests/result-handler.spec.ts b/express-zod-api/tests/result-handler.spec.ts index a134ee3cab..039e425f31 100644 --- a/express-zod-api/tests/result-handler.spec.ts +++ b/express-zod-api/tests/result-handler.spec.ts @@ -1,4 +1,4 @@ -import { Response } from "express"; +import type { Response } from "express"; import createHttpError from "http-errors"; import { z } from "zod"; import { @@ -8,7 +8,7 @@ import { ResultHandler, } from "../src"; import { ResultHandlerError } from "../src/errors"; -import { AbstractResultHandler, Result } from "../src/result-handler"; +import { AbstractResultHandler, type Result } from "../src/result-handler"; import { makeLoggerMock, makeRequestMock, @@ -185,9 +185,9 @@ describe("ResultHandler", () => { test("should forward output schema examples", () => { const apiResponse = subject.getPositiveResponse( - z - .object({ str: z.string(), items: z.array(z.string()) }) - .example({ str: "test", items: ["One", "Two", "Three"] }), + z.object({ str: z.string(), items: z.array(z.string()) }).meta({ + examples: [{ str: "test", items: ["One", "Two", "Three"] }], + }), ); expect(apiResponse).toHaveLength(1); expect(apiResponse[0].schema.meta()).toMatchSnapshot(); @@ -202,7 +202,11 @@ describe("ResultHandler", () => { test("arrayResultHandler should attempt to take examples from the items prop", () => { const apiResponse = arrayResultHandler.getPositiveResponse( - z.object({ items: z.array(z.string()).example(["One", "Two", "Three"]) }), + z.object({ + items: z + .array(z.string()) + .meta({ examples: [["One", "Two", "Three"]] }), + }), ); expect(apiResponse).toHaveLength(1); expect(apiResponse[0].schema.meta()).toMatchSnapshot(); @@ -213,7 +217,9 @@ describe("ResultHandler", () => { const loggerMock = makeLoggerMock(); const positiveSchema = arrayResultHandler .getPositiveResponse( - z.object({ anything: z.number() }).example({ anything: 118 }), + z + .object({ anything: z.number() }) + .meta({ examples: [{ anything: 118 }] }), ) .pop()?.schema; expect(positiveSchema).toHaveProperty(["_zod", "def", "type"], "array"); diff --git a/express-zod-api/tests/result-helpers.spec.ts b/express-zod-api/tests/result-helpers.spec.ts index 50526c067d..099c9cd9cd 100644 --- a/express-zod-api/tests/result-helpers.spec.ts +++ b/express-zod-api/tests/result-helpers.spec.ts @@ -97,9 +97,9 @@ describe("Result helpers", () => { describe("pullResponseExamples()", () => { test("handles multiple examples per property", () => { const schema = z.object({ - a: z.string().example("one").example("two").example("three"), - b: z.number().example(1).example(2), - c: z.boolean().example(false), + a: z.string().meta({ examples: ["one", "two", "three"] }), + b: z.number().meta({ examples: [1, 2] }), + c: z.boolean().meta({ examples: [false] }), }); expect(pullResponseExamples(schema)).toEqual([ { a: "one", b: 1, c: false }, diff --git a/express-zod-api/tests/routing-walker.spec.ts b/express-zod-api/tests/routing-walker.spec.ts index c10479ddcd..3322d2b47f 100644 --- a/express-zod-api/tests/routing-walker.spec.ts +++ b/express-zod-api/tests/routing-walker.spec.ts @@ -1,4 +1,4 @@ -import { defaultEndpointsFactory, Routing } from "../src"; +import { defaultEndpointsFactory, type Routing } from "../src"; import { walkRouting } from "../src/routing-walker"; describe("walkRouting()", () => { diff --git a/express-zod-api/tests/routing.spec.ts b/express-zod-api/tests/routing.spec.ts index 50b985a0f7..d4bfab7d5f 100644 --- a/express-zod-api/tests/routing.spec.ts +++ b/express-zod-api/tests/routing.spec.ts @@ -7,10 +7,10 @@ import { import { z } from "zod"; import { EndpointsFactory, - Routing, ServeStatic, defaultResultHandler, ez, + type Routing, } from "../src"; import { makeLoggerMock, @@ -31,15 +31,15 @@ describe("Routing", () => { vi.clearAllMocks(); // resets call counters on mocked methods }); - test.each([404, 405] as const)( + test.each([true, false, undefined])( "Should set right methods %#", - (wrongMethodBehavior) => { + (hintAllowedMethods) => { const handlerMock = vi.fn(); const configMock = { cors: true, startupLogo: false, - wrongMethodBehavior, - methodLikeRouteBehavior: "path" as const, + hintAllowedMethods, + recognizeMethodDependentRoutes: false, }; const factory = new EndpointsFactory(defaultResultHandler); const getEndpoint = factory.build({ @@ -85,7 +85,7 @@ describe("Routing", () => { expect(appMock.options.mock.calls[0][0]).toBe("/v1/user/get"); expect(appMock.options.mock.calls[1][0]).toBe("/v1/user/set"); expect(appMock.options.mock.calls[2][0]).toBe("/v1/user/universal"); - if (wrongMethodBehavior !== 405) return; + if (hintAllowedMethods === false) return; expect(appMock.all).toHaveBeenCalledTimes(3); expect(appMock.all.mock.calls[0][0]).toBe("/v1/user/get"); expect(appMock.all.mock.calls[1][0]).toBe("/v1/user/set"); diff --git a/express-zod-api/tests/server-helpers.spec.ts b/express-zod-api/tests/server-helpers.spec.ts index 4beda60abc..3bf7a55a27 100644 --- a/express-zod-api/tests/server-helpers.spec.ts +++ b/express-zod-api/tests/server-helpers.spec.ts @@ -13,8 +13,8 @@ import { installTerminationListener, localsID, } from "../src/server-helpers"; -import { CommonConfig, defaultResultHandler, ResultHandler } from "../src"; -import { Request } from "express"; +import { defaultResultHandler, ResultHandler, type CommonConfig } from "../src"; +import type { Request } from "express"; import { makeLoggerMock, makeRequestMock, diff --git a/express-zod-api/tests/server.spec.ts b/express-zod-api/tests/server.spec.ts index 72494eb662..07b80bd0aa 100644 --- a/express-zod-api/tests/server.spec.ts +++ b/express-zod-api/tests/server.spec.ts @@ -15,14 +15,14 @@ import { } from "./http-mock"; import { z } from "zod"; import { - AppConfig, BuiltinLogger, EndpointsFactory, - ServerConfig, attachRouting, createServer, defaultResultHandler, ez, + type AppConfig, + type ServerConfig, } from "../src"; import express from "express"; diff --git a/express-zod-api/tests/sse.spec.ts b/express-zod-api/tests/sse.spec.ts index 1a7dd3aaec..3fd64c5412 100644 --- a/express-zod-api/tests/sse.spec.ts +++ b/express-zod-api/tests/sse.spec.ts @@ -1,15 +1,15 @@ import { z } from "zod"; import { - FlatObject, Middleware, ResultHandler, testEndpoint, testMiddleware, EventStreamFactory, EndpointsFactory, + type FlatObject, } from "../src"; import { - Emitter, + type Emitter, ensureStream, formatEvent, makeEventSchema, diff --git a/express-zod-api/tests/system.spec.ts b/express-zod-api/tests/system.spec.ts index 876f8638e5..0c0c44182e 100644 --- a/express-zod-api/tests/system.spec.ts +++ b/express-zod-api/tests/system.spec.ts @@ -5,7 +5,6 @@ import { readFile } from "node:fs/promises"; import { z } from "zod"; import { EndpointsFactory, - Method, createConfig, createServer, defaultResultHandler, @@ -13,6 +12,7 @@ import { BuiltinLogger, Middleware, ez, + type Method, } from "../src"; import { givePort } from "../../tools/ports"; import { setTimeout } from "node:timers/promises"; diff --git a/express-zod-api/tests/testing.spec.ts b/express-zod-api/tests/testing.spec.ts index 8c2887d0a7..89e184c73d 100644 --- a/express-zod-api/tests/testing.spec.ts +++ b/express-zod-api/tests/testing.spec.ts @@ -1,11 +1,11 @@ import { z } from "zod"; import { - CommonConfig, defaultEndpointsFactory, Middleware, ResultHandler, testEndpoint, testMiddleware, + type CommonConfig, } from "../src"; import type { Mock } from "vitest"; diff --git a/express-zod-api/tests/upload-schema.spec.ts b/express-zod-api/tests/upload-schema.spec.ts index f5c81a5d87..bf9790608e 100644 --- a/express-zod-api/tests/upload-schema.spec.ts +++ b/express-zod-api/tests/upload-schema.spec.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { ez } from "../src"; -import { getBrand } from "@express-zod-api/zod-plugin"; +import { getBrand } from "../src/metadata"; import { ezUploadBrand, isObjectOfUploadShape } from "../src/upload-schema"; describe("ez.upload()", () => { diff --git a/express-zod-api/tests/zts.spec.ts b/express-zod-api/tests/zts.spec.ts index ead5443012..78c873fd10 100644 --- a/express-zod-api/tests/zts.spec.ts +++ b/express-zod-api/tests/zts.spec.ts @@ -3,7 +3,7 @@ import { z } from "zod"; import { ez } from "../src"; import { TypescriptAPI } from "../src/typescript-api"; import { zodToTs } from "../src/zts"; -import { ZTSContext } from "../src/zts-helpers"; +import type { ZTSContext } from "../src/zts-helpers"; describe("zod-to-ts", () => { const api = new TypescriptAPI(ts); @@ -277,10 +277,10 @@ describe("zod-to-ts", () => { expect(printNodeTest(node)).toMatchSnapshot(); }); - test("supports zod.deprecated()", () => { + test("supports deprecated metadata", () => { const schema = z.object({ - one: z.string().deprecated(), - two: z.string().deprecated().describe("with description"), + one: z.string().meta({ deprecated: true }), + two: z.string().meta({ deprecated: true }).describe("with description"), }); const node = zodToTs(schema, { ctx }); expect(printNodeTest(node)).toMatchSnapshot(); diff --git a/express-zod-api/tsdown.config.ts b/express-zod-api/tsdown.config.ts index d43e30afa4..03b52d5688 100644 --- a/express-zod-api/tsdown.config.ts +++ b/express-zod-api/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from "tsdown"; import manifest from "./package.json" with { type: "json" }; -import { fixDtsPlugin } from "../tools/fixDts"; +import { fixDtsPlugin } from "../tools/fixDts.ts"; export default defineConfig({ entry: "src/index.ts", @@ -10,10 +10,6 @@ export default defineConfig({ deps: { neverBundle: ["express-serve-static-core", "qs"], }, - banner: { - /** @since tsdown 0.21 it shakes the unused import */ - dts: `import "@express-zod-api/zod-plugin";`, - }, plugins: [fixDtsPlugin()], define: { "process.env.TSDOWN_SELF": `"${manifest.name}"`, // used by localsID diff --git a/issue952-test/symbols.ts b/issue952-test/symbols.ts deleted file mode 100644 index a3a9f25748..0000000000 --- a/issue952-test/symbols.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ez } from "express-zod-api"; - -export const schemas = { - raw: ez.raw(), - file: ez.buffer(), - dateIn: ez.dateIn(), - dateOut: ez.dateOut(), - upload: ez.upload(), - form: ez.form({}), -}; diff --git a/issue952-test/tags.ts b/issue952-test/tags.ts index ff08fa0dcd..5db4034954 100644 --- a/issue952-test/tags.ts +++ b/issue952-test/tags.ts @@ -1,7 +1,7 @@ import { defaultEndpointsFactory, - TagOverrides, Documentation, + type TagOverrides, } from "express-zod-api"; declare module "express-zod-api" { diff --git a/migration/README.md b/migration/README.md index 7ad851225e..28943335ec 100644 --- a/migration/README.md +++ b/migration/README.md @@ -4,8 +4,8 @@ ESLint plugin for migrating Express Zod API to its next major version. ## Requirements -- `eslint` v9; -- `typescript-eslint` v8. +- `eslint@^10.0.0`; +- `typescript-eslint@^8.58.0` ## Usage @@ -18,6 +18,6 @@ import migration from "@express-zod-api/migration"; export default [ { languageOptions: { parser }, plugins: { migration } }, - { files: ["**/*.ts"], rules: { "migration/v27": "error" } }, + { files: ["**/*.ts"], rules: { "migration/v28": "error" } }, ]; ``` diff --git a/migration/__snapshots__/index.spec.ts.snap b/migration/__snapshots__/index.spec.ts.snap index 0a5315d7e1..70e14e2b6a 100644 --- a/migration/__snapshots__/index.spec.ts.snap +++ b/migration/__snapshots__/index.spec.ts.snap @@ -3,10 +3,11 @@ exports[`Migration > should consist of one rule being the major version of the package 1`] = ` { "rules": { - "v27": { + "v28": { "create": [Function], - "defaultOptions": [], + "defaultOptions": undefined, "meta": { + "defaultOptions": [], "fixable": "code", "messages": { "add": "add {{ subject }} to {{ to }}", @@ -17,7 +18,7 @@ exports[`Migration > should consist of one rule being the major version of the p "schema": [], "type": "problem", }, - "name": "v27", + "name": "v28", }, }, } diff --git a/migration/helpers.ts b/migration/helpers.ts new file mode 100644 index 0000000000..30aced0da2 --- /dev/null +++ b/migration/helpers.ts @@ -0,0 +1,37 @@ +import type { TSESLint, TSESTree } from "@typescript-eslint/utils"; + +export type NamedProp = TSESTree.PropertyNonComputedName & { + key: TSESTree.Identifier | TSESTree.StringLiteral; +}; + +export const queryNamedProp = (name: string) => + `Property[key.name="${name}"],Property[key.value="${name}"]`; + +export const getPropName = (prop: NamedProp): string => + "name" in prop.key ? prop.key.name : prop.key.value; + +export const changeProp = ({ + ctx, + node, + to, + assign, +}: { + ctx: TSESLint.RuleContext<"change", unknown[]>; + node: NamedProp; + to: string; + assign?: (value: typeof node.value) => string | null; +}) => + ctx.report({ + node, + messageId: "change", + data: { subject: "property", from: getPropName(node), to }, + fix: (fixer) => { + const changes = [fixer.replaceText(node.key, to)]; + if (assign) { + const newValue = assign(node.value); + if (!newValue) return null; // unclear fix + changes.push(fixer.replaceText(node.value, newValue)); + } + return changes; + }, + }); diff --git a/migration/index.spec.ts b/migration/index.spec.ts index aa19262f5c..d719645d52 100644 --- a/migration/index.spec.ts +++ b/migration/index.spec.ts @@ -22,77 +22,286 @@ describe("Migration", async () => { }); tester.run(ruleName, theRule, { - valid: [`new Integration({ typescript, config, routing });`], + valid: [ + `createConfig({ hintAllowedMethods: false });`, + `createConfig({ recognizeMethodDependentRoutes: true });`, + `new Documentation({ summarizer: ({summary, trim}) => trim(summary) });`, + `new Integration({ noBodySchema: z.undefined() });`, + `factory.build({ summary: "hello" });`, + `factory.buildVoid({ summary: "hello" });`, + ], invalid: [ { - name: "should import typescript and add it as a property to constructor argument", - code: `new Integration({ config, routing });`, - output: `import typescript from "typescript";\n\nnew Integration({ typescript, config, routing });`, + name: "wrongMethodBehavior=404", + code: `createConfig({ wrongMethodBehavior: 404 });`, + output: `createConfig({ hintAllowedMethods: false });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "wrongMethodBehavior", + to: "hintAllowedMethods", + }, + }, + ], + }, + { + name: "wrongMethodBehavior=405", + code: `createConfig({ wrongMethodBehavior: 405 });`, + output: `createConfig({ hintAllowedMethods: true });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "wrongMethodBehavior", + to: "hintAllowedMethods", + }, + }, + ], + }, + { + name: "wrongMethodBehavior=undefined", + code: `createConfig({ wrongMethodBehavior: undefined });`, + output: `createConfig({ hintAllowedMethods: undefined });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "wrongMethodBehavior", + to: "hintAllowedMethods", + }, + }, + ], + }, + { + name: "wrongMethodBehavior is wrong", + code: `createConfig({ wrongMethodBehavior: "wrong" });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "wrongMethodBehavior", + to: "hintAllowedMethods", + }, + }, + ], + }, + { + name: "methodLikeRouteBehavior=method", + code: `createConfig({ methodLikeRouteBehavior: "method" });`, + output: `createConfig({ recognizeMethodDependentRoutes: true });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "methodLikeRouteBehavior", + to: "recognizeMethodDependentRoutes", + }, + }, + ], + }, + { + name: "methodLikeRouteBehavior=path", + code: `createConfig({ methodLikeRouteBehavior: "path" });`, + output: `createConfig({ recognizeMethodDependentRoutes: false });`, errors: [ { - messageId: "add", + messageId: "change", data: { - subject: "typescript property", - to: "constructor argument", + subject: "property", + from: "methodLikeRouteBehavior", + to: "recognizeMethodDependentRoutes", }, }, ], }, { - name: "should handle no props", - code: `new Integration({});`, - output: `import typescript from "typescript";\n\nnew Integration({ typescript });`, + name: "methodLikeRouteBehavior=undefined", + code: `createConfig({ methodLikeRouteBehavior: undefined });`, + output: `createConfig({ recognizeMethodDependentRoutes: undefined });`, errors: [ { - messageId: "add", + messageId: "change", + data: { + subject: "property", + from: "methodLikeRouteBehavior", + to: "recognizeMethodDependentRoutes", + }, + }, + ], + }, + { + name: "methodLikeRouteBehavior is wrong", + code: `createConfig({ methodLikeRouteBehavior: 123 });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "methodLikeRouteBehavior", + to: "recognizeMethodDependentRoutes", + }, + }, + ], + }, + { + name: "hasSummaryFromDescription=true (first)", + code: `new Documentation({ hasSummaryFromDescription: true, other: 1 });`, + output: `new Documentation({ other: 1 });`, + errors: [ + { + messageId: "remove", + data: { subject: "property" }, + }, + ], + }, + { + name: "hasSummaryFromDescription=undefined (last)", + code: `new Documentation({ other: 1, hasSummaryFromDescription: undefined });`, + output: `new Documentation({ other: 1, });`, + errors: [ + { + messageId: "remove", + data: { subject: "property" }, + }, + ], + }, + { + name: "hasSummaryFromDescription=false", + code: `new Documentation({ hasSummaryFromDescription: false });`, + output: `new Documentation({ summarizer: ({ summary, trim }) => trim(summary) });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "hasSummaryFromDescription", + to: "summarizer", + }, + }, + ], + }, + { + name: "noContent=z.undefined()", + code: `new Integration({ noContent: z.undefined() });`, + output: `new Integration({ noBodySchema: z.undefined() });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "noContent", + to: "noBodySchema", + }, + }, + ], + }, + { + name: "noContent=undefined", + code: `new Integration({ noContent: undefined });`, + output: `new Integration({ noBodySchema: undefined });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "noContent", + to: "noBodySchema", + }, + }, + ], + }, + { + name: "shortDescription in build()", + code: `factory.build({ shortDescription: "Retrieves the user." });`, + output: `factory.build({ summary: "Retrieves the user." });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "shortDescription", + to: "summary", + }, + }, + ], + }, + { + name: "shortDescription in buildVoid()", + code: `factory.buildVoid({ shortDescription: "Retrieves the user." });`, + output: `factory.buildVoid({ summary: "Retrieves the user." });`, + errors: [ + { + messageId: "change", + data: { + subject: "property", + from: "shortDescription", + to: "summary", + }, + }, + ], + }, + { + name: "wrongMethodBehavior with string key", + code: `createConfig({ "wrongMethodBehavior": 405 });`, + output: `createConfig({ hintAllowedMethods: true });`, + errors: [ + { + messageId: "change", data: { - subject: "typescript property", - to: "constructor argument", + subject: "property", + from: "wrongMethodBehavior", + to: "hintAllowedMethods", }, }, ], }, { - name: "should use static create() method when there is 'await' statement", - code: `await new Integration({ config, routing }).printFormatted();`, - output: `await (await Integration.create({ config, routing })).printFormatted();`, + name: "noContent with string key", + code: `new Integration({ "noContent": z.undefined() });`, + output: `new Integration({ noBodySchema: z.undefined() });`, errors: [ { messageId: "change", data: { - subject: "constructor", - from: "new Integration()", - to: "await Integration.create()", + subject: "property", + from: "noContent", + to: "noBodySchema", }, }, ], }, { - name: "should use static create() method when inside async functional expression", - code: `async () => { new Integration({ config, routing }); }`, - output: `async () => { (await Integration.create({ config, routing })); }`, + name: "hasSummaryFromDescription=false with string key", + code: `new Documentation({ "hasSummaryFromDescription": false });`, + output: `new Documentation({ summarizer: ({ summary, trim }) => trim(summary) });`, errors: [ { messageId: "change", data: { - subject: "constructor", - from: "new Integration()", - to: "await Integration.create()", + subject: "property", + from: "hasSummaryFromDescription", + to: "summarizer", }, }, ], }, { - name: "should use static create() method when inside async function declaration", - code: `async function test() { new Integration({ config, routing }); }`, - output: `async function test() { (await Integration.create({ config, routing })); }`, + name: "shortDescription with string key in build()", + code: `factory.build({ "shortDescription": "Retrieves the user." });`, + output: `factory.build({ summary: "Retrieves the user." });`, errors: [ { messageId: "change", data: { - subject: "constructor", - from: "new Integration()", - to: "await Integration.create()", + subject: "property", + from: "shortDescription", + to: "summary", }, }, ], diff --git a/migration/index.ts b/migration/index.ts index fd0f351f3d..45fb92e601 100644 --- a/migration/index.ts +++ b/migration/index.ts @@ -1,33 +1,48 @@ import { - ESLintUtils, AST_NODE_TYPES as NT, + ESLintUtils, type TSESLint, - type TSESTree, } from "@typescript-eslint/utils"; // eslint-disable-line allowed/dependencies -- assumed transitive dependency - -type NamedProp = TSESTree.PropertyNonComputedName & { - key: TSESTree.Identifier | TSESTree.StringLiteral; -}; +import { + queryNamedProp, + type NamedProp, + getPropName, + changeProp, +} from "./helpers.ts"; interface Queries { - integration: TSESTree.ObjectExpression; + wrongMethodBehavior: NamedProp; + methodLikeRouteBehavior: NamedProp; + hasSummaryFromDescription: NamedProp; + noContent: NamedProp; + shortDescription: NamedProp; } type Listener = keyof Queries; const queries: Record = { - integration: `${NT.NewExpression}[callee.name="Integration"] > ${NT.ObjectExpression}`, + wrongMethodBehavior: + `${NT.CallExpression}[callee.name="createConfig"] > ` + + `${NT.ObjectExpression} > ` + + queryNamedProp("wrongMethodBehavior"), + methodLikeRouteBehavior: + `${NT.CallExpression}[callee.name="createConfig"] > ` + + `${NT.ObjectExpression} > ` + + queryNamedProp("methodLikeRouteBehavior"), + hasSummaryFromDescription: + `${NT.NewExpression}[callee.name="Documentation"] > ` + + `${NT.ObjectExpression} > ` + + queryNamedProp("hasSummaryFromDescription"), + noContent: + `${NT.NewExpression}[callee.name="Integration"] > ` + + `${NT.ObjectExpression} > ` + + queryNamedProp("noContent"), + shortDescription: + `${NT.CallExpression}[callee.property.name=/build|buildVoid/] > ` + + `${NT.ObjectExpression} > ` + + queryNamedProp("shortDescription"), }; -const isNamedProp = (prop: TSESTree.ObjectLiteralElement): prop is NamedProp => - prop.type === NT.Property && - !prop.computed && - (prop.key.type === NT.Identifier || - (prop.key.type === NT.Literal && typeof prop.key.value === "string")); - -const getPropName = (prop: NamedProp): string => - prop.key.type === NT.Identifier ? prop.key.name : prop.key.value; - const listen = < S extends { [K in Listener]: TSESLint.RuleFunction }, >( @@ -55,60 +70,67 @@ const theRule = ESLintUtils.RuleCreator.withoutDocs({ move: "move {{ subject }} to {{ to }}", remove: "remove {{ subject }}", }, + defaultOptions: [], }, - defaultOptions: [], create: (ctx) => listen({ - integration: (node) => { - const tsProp = node.properties - .filter(isNamedProp) - .find((one) => getPropName(one) === "typescript"); - if (tsProp) return; - const hasAsyncCtx = ctx.sourceCode - .getAncestors(node) - .some( - (one) => - one.type === NT.AwaitExpression || - ((one.type === NT.ArrowFunctionExpression || - one.type === NT.FunctionExpression || - one.type === NT.FunctionDeclaration) && - one.async), - ); - ctx.report( - hasAsyncCtx - ? { - node: node.parent, - messageId: "change", - data: { - subject: "constructor", - from: "new Integration()", - to: "await Integration.create()", - }, - fix: (fixer) => - fixer.replaceText( - node.parent, - `(await Integration.create(${ctx.sourceCode.getText(node)}))`, - ), - } - : { - node: node, - messageId: "add", - data: { - subject: "typescript property", - to: "constructor argument", - }, - fix: (fixer) => [ - fixer.insertTextBeforeRange( - ctx.sourceCode.ast.range, - `import typescript from "typescript";\n\n`, - ), - node.properties.length - ? fixer.insertTextBefore(node.properties[0], "typescript, ") - : fixer.replaceText(node, `{ typescript }`), - ], - }, - ); + wrongMethodBehavior: (node) => + changeProp({ + ctx, + node, + to: "hintAllowedMethods", + assign: (value) => { + if (value.type === NT.Literal && typeof value.value === "number") + return value.value === 405 ? "true" : "false"; + else if (value.type === NT.Identifier && value.name === "undefined") + return "undefined"; + else return null; + }, + }), + methodLikeRouteBehavior: (node) => + changeProp({ + ctx, + node, + to: "recognizeMethodDependentRoutes", + assign: (value) => { + if (value.type === NT.Identifier && value.name === "undefined") + return "undefined"; + else if ( + value.type === NT.Literal && + typeof value.value === "string" + ) + return value.value === "method" ? "true" : "false"; + else return null; + }, + }), + hasSummaryFromDescription: (node) => { + const value = node.value; + const isDisabled = value.type === NT.Literal && value.value === false; + ctx.report({ + node, + messageId: isDisabled ? "change" : "remove", + data: { + subject: "property", + ...(isDisabled && { + from: getPropName(node), + to: "summarizer", + }), + }, + fix: (fixer) => { + if (isDisabled) { + return fixer.replaceText( + node, + "summarizer: ({ summary, trim }) => trim(summary)", + ); + } + const after = ctx.sourceCode.getTokenAfter(node); + const end = node.range[1] + (after?.value === "," ? 1 : 0); + return fixer.removeRange([node.range[0], end]); + }, + }); }, + noContent: (node) => changeProp({ ctx, node, to: "noBodySchema" }), + shortDescription: (node) => changeProp({ ctx, node, to: "summary" }), }), }); diff --git a/migration/package.json b/migration/package.json index 3b47aa2ec2..6376f963e7 100644 --- a/migration/package.json +++ b/migration/package.json @@ -1,6 +1,6 @@ { "name": "@express-zod-api/migration", - "version": "27.0.0", + "version": "28.0.0-beta.2", "license": "MIT", "description": "Migration scripts for express-zod-api", "repository": { @@ -17,7 +17,7 @@ "scripts": { "pretest": "tsc", "test": "vitest run --globals", - "build": "tsdown --config-loader unrun", + "build": "tsdown", "prepublishOnly": "eslint && pnpm test && pnpm build" }, "type": "module", @@ -35,11 +35,11 @@ "*.md" ], "engines": { - "node": "^20.19.0 || ^22.12.0 || ^24.0.0" + "node": "^22.19.0 || ^24.0.0" }, "peerDependencies": { - "eslint": "^9.0.0", - "typescript-eslint": "^8.0.0" + "eslint": "^10.0.0", + "typescript-eslint": "^8.58.0" }, "devDependencies": { "@typescript-eslint/rule-tester": "catalog:dev", diff --git a/migration/tsdown.config.ts b/migration/tsdown.config.ts index b0b5217fc7..80a30db817 100644 --- a/migration/tsdown.config.ts +++ b/migration/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from "tsdown"; import manifest from "./package.json" with { type: "json" }; -import { fixDtsPlugin } from "../tools/fixDts"; +import { fixDtsPlugin } from "../tools/fixDts.ts"; export default defineConfig({ entry: "index.ts", diff --git a/package.json b/package.json index b89e9d358c..5575c47fed 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,9 @@ "type": "module", "scripts": { "start": "pnpm -F example start", - "prebuild": "unrun tools/contributors.ts && unrun tools/license.ts", + "prebuild": "node tools/contributors.ts && node tools/license.ts", "build": "pnpm -r build", - "pretest": "unrun tools/make-tests.ts", + "pretest": "node tools/make-tests.ts", "test": "pnpm -r test", "bench": "pnpm -F express-zod-api bench", "lint": "eslint && prettier *.md **/*.md --check", @@ -15,18 +15,19 @@ }, "devDependencies": { "@arethetypeswrong/core": "^0.18.2", - "@tsconfig/node20": "^20.1.8", + "@eslint/js": "^10.0.1", + "@tsconfig/node22": "^22.0.5", "@types/node": "^25.5.0", "@vitest/coverage-v8": "^4.1.5", - "eslint": "^9.39.2", + "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-allowed-dependencies": "^2.0.1", - "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-allowed-dependencies": "^2.1.0", + "eslint-plugin-prettier": "^5.5.5", + "globals": "^17.5.0", "husky": "^9.1.7", "prettier": "3.8.3", "tsdown": "^0.21.10", "typescript-eslint": "catalog:dev", - "unrun": "^0.2.37", "vitest": "^4.1.5" }, "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff3da3eb0a..d9d1d2bd03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,8 +23,8 @@ catalogs: specifier: ^0.31.1 version: 0.31.1 '@typescript-eslint/rule-tester': - specifier: ^8.51.0 - version: 8.55.0 + specifier: ^8.59.0 + version: 8.59.0 camelize-ts: specifier: ^3.0.0 version: 3.0.0 @@ -44,11 +44,11 @@ catalogs: specifier: ^6.0.2 version: 6.0.3 typescript-eslint: - specifier: ^8.51.0 - version: 8.55.0 + specifier: ^8.59.0 + version: 8.59.0 zod: - specifier: ~4.3.4 - version: 4.3.6 + specifier: ^4.4.1 + version: 4.4.1 prod: ramda: specifier: ^0.32.0 @@ -65,9 +65,12 @@ importers: '@arethetypeswrong/core': specifier: ^0.18.2 version: 0.18.2 - '@tsconfig/node20': - specifier: ^20.1.8 - version: 20.1.9 + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.2.0) + '@tsconfig/node22': + specifier: ^22.0.5 + version: 22.0.5 '@types/node': specifier: ^25.5.0 version: 25.6.0 @@ -75,17 +78,20 @@ importers: specifier: ^4.1.5 version: 4.1.5(vitest@4.1.5) eslint: - specifier: ^9.39.2 - version: 9.39.4 + specifier: ^10.2.0 + version: 10.2.0 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.4) + version: 10.1.8(eslint@10.2.0) eslint-plugin-allowed-dependencies: - specifier: ^2.0.1 - version: 2.1.0(eslint@9.39.4)(typescript-eslint@8.55.0) + specifier: ^2.1.0 + version: 2.1.0(eslint@10.2.0)(typescript-eslint@8.59.0) eslint-plugin-prettier: - specifier: ^5.5.4 - version: 5.5.5(eslint-config-prettier@10.1.8)(eslint@9.39.4)(prettier@3.8.3) + specifier: ^5.5.5 + version: 5.5.5(eslint-config-prettier@10.1.8)(eslint@10.2.0)(prettier@3.8.3) + globals: + specifier: ^17.5.0 + version: 17.5.0 husky: specifier: ^9.1.7 version: 9.1.7 @@ -97,10 +103,7 @@ importers: version: 0.21.10(@arethetypeswrong/core@0.18.2)(synckit@0.11.12)(typescript@6.0.3) typescript-eslint: specifier: catalog:dev - version: 8.55.0(eslint@9.39.4)(typescript@6.0.3) - unrun: - specifier: ^0.2.37 - version: 0.2.37(synckit@0.11.12) + version: 8.59.0(eslint@10.2.0)(typescript@6.0.3) vitest: specifier: ^4.1.5 version: 4.1.5(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(yaml@2.8.3) @@ -115,7 +118,7 @@ importers: version: 6.0.3 zod: specifier: catalog:dev - version: 4.3.6 + version: 4.4.1 compat-test: devDependencies: @@ -123,8 +126,8 @@ importers: specifier: workspace:* version: link:../migration eslint: - specifier: npm:eslint@9.0.0 - version: 9.0.0 + specifier: npm:eslint@10.0.0 + version: 10.0.0 express: specifier: npm:express@5.1.0 version: 5.1.0 @@ -138,8 +141,8 @@ importers: specifier: npm:typescript@5.1.3 version: 5.1.3 typescript-eslint: - specifier: npm:typescript-eslint@8.0.0 - version: 8.0.0(eslint@9.0.0)(typescript@5.1.3) + specifier: npm:typescript-eslint@8.58.0 + version: 8.58.0(eslint@10.0.0)(typescript@5.1.3) zod: specifier: npm:zod@4.3.4 version: 4.3.4 @@ -151,10 +154,13 @@ importers: version: link:../express-zod-api zod: specifier: catalog:dev - version: 4.3.6 + version: 4.4.1 example: devDependencies: + '@express-zod-api/zod-plugin': + specifier: workspace:* + version: link:../zod-plugin '@types/http-errors': specifier: catalog:dev version: 2.0.5 @@ -178,13 +184,10 @@ importers: version: 6.0.3 zod: specifier: catalog:dev - version: 4.3.6 + version: 4.4.1 express-zod-api: dependencies: - '@express-zod-api/zod-plugin': - specifier: workspace:^ - version: link:../zod-plugin ansis: specifier: ^4.2.0 version: 4.2.0 @@ -198,6 +201,9 @@ importers: specifier: catalog:prod version: 0.32.0 devDependencies: + '@express-zod-api/zod-plugin': + specifier: workspace:^ + version: link:../zod-plugin '@types/compression': specifier: catalog:dev version: 1.8.1 @@ -253,11 +259,11 @@ importers: specifier: catalog:dev version: 6.0.3 undici: - specifier: ^7.16.0 - version: 7.25.0 + specifier: ^8.0.0 + version: 8.0.0 zod: specifier: catalog:dev - version: 4.3.6 + version: 4.4.1 issue952-test: dependencies: @@ -275,13 +281,13 @@ importers: version: 6.0.3 zod: specifier: catalog:dev - version: 4.3.6 + version: 4.4.1 migration: devDependencies: '@typescript-eslint/rule-tester': specifier: catalog:dev - version: 8.55.0(eslint@9.39.4)(typescript@6.0.3) + version: 8.59.0(eslint@10.2.0)(typescript@6.0.3) typescript: specifier: catalog:dev version: 6.0.3 @@ -303,7 +309,7 @@ importers: version: 6.0.3 zod: specifier: catalog:dev - version: 4.3.6 + version: 4.4.1 packages: @@ -378,37 +384,54 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.2': - resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.23.1': + resolution: {integrity: sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.5.2': + resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@3.3.5': - resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.5.5': + resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/js@9.0.0': - resolution: {integrity: sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@1.1.0': + resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/js@9.39.4': - resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@3.0.1': + resolution: {integrity: sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.6.0': + resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -418,23 +441,22 @@ packages: resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} engines: {node: '>=18.18.0'} - '@humanwhocodes/config-array@0.12.3': - resolution: {integrity: sha512-jsNnTBlMWuTpDkeE3on7+dWJi0D6fdDfeANj/w7MpS8ztROCoLvIO2nG0CcFj+E4k8j4QrSTh4Oryi3i2G669g==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead - '@humanwhocodes/retry@0.4.3': resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.1': + resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} + engines: {node: 20 || >=22} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -457,18 +479,6 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@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'} - '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} @@ -580,8 +590,8 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@tsconfig/node20@20.1.9': - resolution: {integrity: sha512-IjlTv1RsvnPtUcjTqtVsZExKVq+KQx4g5pCP5tI7rAs6Xesl2qFwSz/tPDBC4JajkL/MlezBu3gPUwqRHl+RIg==} + '@tsconfig/node22@22.0.5': + resolution: {integrity: sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw==} '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -610,6 +620,9 @@ packages: '@types/depd@1.1.37': resolution: {integrity: sha512-PkEYFHnqDFgs+bJXJX0L8mq7sn3DWh+TP0m8BBJUJfZ2WcjRm7jd7Cq68jIJt+c31R1gX0cwSK1ZXOECvN97Rg==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -655,126 +668,128 @@ packages: '@types/swagger-ui-express@4.1.8': resolution: {integrity: sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==} - '@typescript-eslint/eslint-plugin@8.0.0': - resolution: {integrity: sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==} + '@typescript-eslint/eslint-plugin@8.58.0': + resolution: {integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser': ^8.58.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/eslint-plugin@8.55.0': - resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==} + '@typescript-eslint/eslint-plugin@8.59.0': + resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.55.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser': ^8.59.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.0.0': - resolution: {integrity: sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==} + '@typescript-eslint/parser@8.58.0': + resolution: {integrity: sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.59.0': + resolution: {integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.55.0': - resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} + '@typescript-eslint/project-service@8.58.0': + resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.55.0': - resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} + '@typescript-eslint/project-service@8.59.0': + resolution: {integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/rule-tester@8.55.0': - resolution: {integrity: sha512-3/oWRUQr88O+MabXfcyHIhnxZXU/Gq8mbcqVoEM3bC4gRVejHq88T2KyNmhMn8AD3p/sLvtMi9d0Zp4f19HFrQ==} + '@typescript-eslint/rule-tester@8.59.0': + resolution: {integrity: sha512-2Ej6W28DqObFuEUQ+puEpDZFWFXAW7jIZ4TsgfLUCTNz1FID0NMfp1sXc+fQq8m5ysfPdhXAPjti6jYEu1oRcg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - '@typescript-eslint/scope-manager@8.0.0': - resolution: {integrity: sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==} + '@typescript-eslint/scope-manager@8.58.0': + resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.55.0': - resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} + '@typescript-eslint/scope-manager@8.59.0': + resolution: {integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.55.0': - resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} + '@typescript-eslint/tsconfig-utils@8.58.0': + resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.0.0': - resolution: {integrity: sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==} + '@typescript-eslint/tsconfig-utils@8.59.0': + resolution: {integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.55.0': - resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} + '@typescript-eslint/type-utils@8.58.0': + resolution: {integrity: sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.0.0': - resolution: {integrity: sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==} + '@typescript-eslint/type-utils@8.59.0': + resolution: {integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.58.0': + resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.55.0': - resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} + '@typescript-eslint/types@8.59.0': + resolution: {integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.0.0': - resolution: {integrity: sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==} + '@typescript-eslint/typescript-estree@8.58.0': + resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/typescript-estree@8.55.0': - resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} + '@typescript-eslint/typescript-estree@8.59.0': + resolution: {integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.0.0': - resolution: {integrity: sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==} + '@typescript-eslint/utils@8.58.0': + resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.55.0': - resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} + '@typescript-eslint/utils@8.59.0': + resolution: {integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.0.0': - resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} + '@typescript-eslint/visitor-keys@8.58.0': + resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.55.0': - resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} + '@typescript-eslint/visitor-keys@8.59.0': + resolution: {integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitest/coverage-v8@4.1.5': @@ -836,25 +851,10 @@ packages: ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - ansis@4.2.0: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -866,8 +866,9 @@ packages: ast-v8-to-istanbul@1.0.0: resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} @@ -876,15 +877,9 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} - brace-expansion@1.1.13: - resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} - - brace-expansion@2.0.3: - resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -906,10 +901,6 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - camelize-ts@3.0.0: resolution: {integrity: sha512-cgRwKKavoDKLTjO4FQTs3dRBePZp/2Y9Xpud0FhuCOTE86M2cniKN4CCXgRnsyXNMmQMifVHcv6SPaMtTx6ofQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -918,20 +909,9 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} - 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==} - compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -940,9 +920,6 @@ packages: resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} engines: {node: '>= 0.8.0'} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1005,10 +982,6 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dts-resolver@2.1.3: resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==} engines: {node: '>=20.19.0'} @@ -1082,26 +1055,39 @@ packages: eslint-config-prettier: optional: true - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@9.1.0: + resolution: {integrity: sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.0: + resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@9.0.0: - resolution: {integrity: sha512-IMryZ5SudxzQvuod6rUdIUz29qFItWx281VhtFVc2Psy/ZhlCeD/5DT6lBIJ4H3G+iamGJoTln1v+QSuPw0p7Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.0.0: + resolution: {integrity: sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true - eslint@9.39.4: - resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@10.2.0: + resolution: {integrity: sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: jiti: '*' @@ -1109,9 +1095,13 @@ packages: jiti: optional: true - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@11.1.0: + resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} @@ -1158,19 +1148,12 @@ packages: 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'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1187,10 +1170,6 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - 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'} @@ -1237,29 +1216,18 @@ packages: get-tsconfig@4.13.7: resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + globals@17.5.0: + resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} engines: {node: '>=18'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1299,10 +1267,6 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - import-without-cache@0.3.3: resolution: {integrity: sha512-bDxwDdF04gm550DfZHgffvlX+9kUlcz32UD0AeBTmVPFiWkrexF2XVmiuFFbDhiFuP8fQkrkvI2KdSNPYWAXkQ==} engines: {node: '>=20.19.0'} @@ -1326,14 +1290,6 @@ packages: 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-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -1355,10 +1311,6 @@ packages: js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1423,18 +1375,10 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -1456,12 +1400,13 @@ packages: engines: {node: '>=4'} hasBin: true - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@10.1.2: + resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} + engines: {node: 20 || >=22} - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -1542,10 +1487,6 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1561,20 +1502,12 @@ packages: path-to-regexp@8.4.0: resolution: {integrity: sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - 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'} @@ -1611,9 +1544,6 @@ packages: quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - ramda@0.32.0: resolution: {integrity: sha512-GQWAHhxhxWBWA8oIBr1XahFVjQ9Fic6MK9ikijfd4TZHfE2+urfk+irVlR5VOn48uwMgM+loRRBJd6Yjsbc0zQ==} @@ -1625,17 +1555,9 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - 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'} - rolldown-plugin-dts@0.23.2: resolution: {integrity: sha512-PbSqLawLgZBGcOGT3yqWBGn4cX+wh2nt5FuBGdcMHyOhoukmjbhYAl8NT9sE4U38Cm9tqLOIQeOrvzeayM0DLQ==} engines: {node: '>=20.19.0'} @@ -1664,9 +1586,6 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -1716,10 +1635,6 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - snakify-ts@2.3.0: resolution: {integrity: sha512-PM8KdclwOt3Blvu5rPmFS1jjBC9hP33MNONh4/EvyXH5BD5s1HMLi5W66J1Ozx7TOqw8ESvoPgnTg0mthbj8DA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1742,14 +1657,6 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1767,9 +1674,6 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1785,10 +1689,6 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} - 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'} @@ -1797,12 +1697,6 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -1858,21 +1752,19 @@ packages: types-ramda@0.31.0: resolution: {integrity: sha512-vaoC35CRC3xvL8Z6HkshDbi6KWM1ezK0LHN0YyxXWUn9HKzBNg/T3xSGlJZjCYspnOD3jE7bcizsp0bUXZDxnQ==} - typescript-eslint@8.0.0: - resolution: {integrity: sha512-yQWBJutWL1PmpmDddIOl9/Mi6vZjqNCjqSGBMQ4vsc2Aiodk0SnbQQWPXbSy0HNuKCuGkw1+u4aQ2mO40TdhDQ==} + typescript-eslint@8.58.0: + resolution: {integrity: sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - typescript-eslint@8.55.0: - resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==} + typescript-eslint@8.59.0: + resolution: {integrity: sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' typescript@5.1.3: resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} @@ -1895,9 +1787,9 @@ packages: undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} - undici@7.25.0: - resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} - engines: {node: '>=20.18.1'} + undici@8.0.0: + resolution: {integrity: sha512-RGabV5g1ggSX5mU4k+B8BLWgb418gDbg0wAVFeiU00iOxtw4ufGsE6GFsuSd2uqOKooWSLf71JGapOFYpE8f+A==} + engines: {node: '>=22.19.0'} unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} @@ -2036,8 +1928,8 @@ packages: zod@4.3.4: resolution: {integrity: sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==} - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.4.1: + resolution: {integrity: sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==} snapshots: @@ -2109,57 +2001,66 @@ snapshots: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.0.0)': + '@eslint-community/eslint-utils@4.9.1(eslint@10.0.0)': dependencies: - eslint: 9.0.0 + eslint: 10.0.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': + '@eslint-community/eslint-utils@4.9.1(eslint@10.2.0)': dependencies: - eslint: 9.39.4 + eslint: 10.2.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.2': + '@eslint/config-array@0.23.1': + dependencies: + '@eslint/object-schema': 3.0.1 + debug: 4.4.3 + minimatch: 10.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-array@0.23.5': dependencies: - '@eslint/object-schema': 2.1.7 + '@eslint/object-schema': 3.0.5 debug: 4.4.3 - minimatch: 3.1.5 + minimatch: 10.2.4 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.4.2': + '@eslint/config-helpers@0.5.2': + dependencies: + '@eslint/core': 1.1.0 + + '@eslint/config-helpers@0.5.5': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.1 - '@eslint/core@0.17.0': + '@eslint/core@1.1.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.5': + '@eslint/core@1.2.1': dependencies: - ajv: 6.14.0 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.2.0)': + optionalDependencies: + eslint: 10.2.0 - '@eslint/js@9.0.0': {} + '@eslint/object-schema@3.0.1': {} - '@eslint/js@9.39.4': {} + '@eslint/object-schema@3.0.5': {} - '@eslint/object-schema@2.1.7': {} + '@eslint/plugin-kit@0.6.0': + dependencies: + '@eslint/core': 1.1.0 + levn: 0.4.1 - '@eslint/plugin-kit@0.4.1': + '@eslint/plugin-kit@0.7.1': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.1 levn: 0.4.1 '@humanfs/core@0.19.1': {} @@ -2169,20 +2070,16 @@ snapshots: '@humanfs/core': 0.19.1 '@humanwhocodes/retry': 0.4.3 - '@humanwhocodes/config-array@0.12.3': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 - minimatch: 3.1.5 - transitivePeerDependencies: - - supports-color - '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/object-schema@2.0.3': {} - '@humanwhocodes/retry@0.4.3': {} + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.1': + dependencies: + '@isaacs/balanced-match': 4.0.1 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2208,18 +2105,6 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@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.19.1 - '@oxc-project/types@0.127.0': {} '@pkgr/core@0.2.9': {} @@ -2281,7 +2166,7 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@tsconfig/node20@20.1.9': {} + '@tsconfig/node22@22.0.5': {} '@tybys/wasm-util@0.10.1': dependencies: @@ -2321,6 +2206,8 @@ snapshots: dependencies: '@types/node': 25.6.0 + '@types/esrecurse@4.3.1': {} + '@types/estree@1.0.8': {} '@types/express-fileupload@1.5.1': @@ -2377,33 +2264,31 @@ snapshots: '@types/express': 5.0.6 '@types/serve-static': 2.2.0 - '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0)(eslint@9.0.0)(typescript@5.1.3)': + '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0)(eslint@10.0.0)(typescript@5.1.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.0.0(eslint@9.0.0)(typescript@5.1.3) - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/type-utils': 8.0.0(eslint@9.0.0)(typescript@5.1.3) - '@typescript-eslint/utils': 8.0.0(eslint@9.0.0)(typescript@5.1.3) - '@typescript-eslint/visitor-keys': 8.0.0 - eslint: 9.0.0 - graphemer: 1.4.0 - ignore: 5.3.2 + '@typescript-eslint/parser': 8.58.0(eslint@10.0.0)(typescript@5.1.3) + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/type-utils': 8.58.0(eslint@10.0.0)(typescript@5.1.3) + '@typescript-eslint/utils': 8.58.0(eslint@10.0.0)(typescript@5.1.3) + '@typescript-eslint/visitor-keys': 8.58.0 + eslint: 10.0.0 + ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.1.3) - optionalDependencies: + ts-api-utils: 2.5.0(typescript@5.1.3) typescript: 5.1.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0)(eslint@9.39.4)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0)(eslint@10.2.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.55.0 - eslint: 9.39.4 + '@typescript-eslint/parser': 8.59.0(eslint@10.2.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.0 + eslint: 10.2.0 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -2411,47 +2296,55 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.0.0(eslint@9.0.0)(typescript@5.1.3)': + '@typescript-eslint/parser@8.58.0(eslint@10.0.0)(typescript@5.1.3)': dependencies: - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.1.3) - '@typescript-eslint/visitor-keys': 8.0.0 + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.1.3) + '@typescript-eslint/visitor-keys': 8.58.0 debug: 4.4.3 - eslint: 9.0.0 - optionalDependencies: + eslint: 10.0.0 typescript: 5.1.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.55.0(eslint@9.39.4)(typescript@6.0.3)': + '@typescript-eslint/parser@8.59.0(eslint@10.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.0 debug: 4.4.3 - eslint: 9.39.4 + eslint: 10.2.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.55.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.58.0(typescript@5.1.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@5.1.3) + '@typescript-eslint/types': 8.58.0 + debug: 4.4.3 + typescript: 5.1.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.59.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@6.0.3) - '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/rule-tester@8.55.0(eslint@9.39.4)(typescript@6.0.3)': + '@typescript-eslint/rule-tester@8.59.0(eslint@10.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/parser': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.4)(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.0(eslint@10.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.0)(typescript@6.0.3) ajv: 6.14.0 - eslint: 9.39.4 + eslint: 10.2.0 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 semver: 7.7.4 @@ -2459,71 +2352,75 @@ snapshots: - supports-color - typescript - '@typescript-eslint/scope-manager@8.0.0': + '@typescript-eslint/scope-manager@8.58.0': + dependencies: + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/visitor-keys': 8.58.0 + + '@typescript-eslint/scope-manager@8.59.0': dependencies: - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/visitor-keys': 8.0.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 - '@typescript-eslint/scope-manager@8.55.0': + '@typescript-eslint/tsconfig-utils@8.58.0(typescript@5.1.3)': dependencies: - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/visitor-keys': 8.55.0 + typescript: 5.1.3 - '@typescript-eslint/tsconfig-utils@8.55.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.59.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.0.0(eslint@9.0.0)(typescript@5.1.3)': + '@typescript-eslint/type-utils@8.58.0(eslint@10.0.0)(typescript@5.1.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.1.3) - '@typescript-eslint/utils': 8.0.0(eslint@9.0.0)(typescript@5.1.3) + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.1.3) + '@typescript-eslint/utils': 8.58.0(eslint@10.0.0)(typescript@5.1.3) debug: 4.4.3 - ts-api-utils: 1.4.3(typescript@5.1.3) - optionalDependencies: + eslint: 10.0.0 + ts-api-utils: 2.5.0(typescript@5.1.3) typescript: 5.1.3 transitivePeerDependencies: - - eslint - supports-color - '@typescript-eslint/type-utils@8.55.0(eslint@9.39.4)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.59.0(eslint@10.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.4)(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.0)(typescript@6.0.3) debug: 4.4.3 - eslint: 9.39.4 + eslint: 10.2.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.0.0': {} + '@typescript-eslint/types@8.58.0': {} - '@typescript-eslint/types@8.55.0': {} + '@typescript-eslint/types@8.59.0': {} - '@typescript-eslint/typescript-estree@8.0.0(typescript@5.1.3)': + '@typescript-eslint/typescript-estree@8.58.0(typescript@5.1.3)': dependencies: - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/visitor-keys': 8.0.0 + '@typescript-eslint/project-service': 8.58.0(typescript@5.1.3) + '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@5.1.3) + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/visitor-keys': 8.58.0 debug: 4.4.3 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.9 + minimatch: 10.2.4 semver: 7.7.4 - ts-api-utils: 1.4.3(typescript@5.1.3) - optionalDependencies: + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.1.3) typescript: 5.1.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.55.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.55.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@6.0.3) - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/project-service': 8.59.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 debug: 4.4.3 - minimatch: 9.0.9 + minimatch: 10.2.4 semver: 7.7.4 tinyglobby: 0.2.16 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -2531,37 +2428,37 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.0.0(eslint@9.0.0)(typescript@5.1.3)': + '@typescript-eslint/utils@8.58.0(eslint@10.0.0)(typescript@5.1.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.0.0) - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.1.3) - eslint: 9.0.0 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0) + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.1.3) + eslint: 10.0.0 + typescript: 5.1.3 transitivePeerDependencies: - supports-color - - typescript - '@typescript-eslint/utils@8.55.0(eslint@9.39.4)(typescript@6.0.3)': + '@typescript-eslint/utils@8.59.0(eslint@10.2.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.3) - eslint: 9.39.4 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + eslint: 10.2.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.0.0': + '@typescript-eslint/visitor-keys@8.58.0': dependencies: - '@typescript-eslint/types': 8.0.0 - eslint-visitor-keys: 3.4.3 + '@typescript-eslint/types': 8.58.0 + eslint-visitor-keys: 5.0.0 - '@typescript-eslint/visitor-keys@8.55.0': + '@typescript-eslint/visitor-keys@8.59.0': dependencies: - '@typescript-eslint/types': 8.55.0 - eslint-visitor-keys: 4.2.1 + '@typescript-eslint/types': 8.59.0 + eslint-visitor-keys: 5.0.1 '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': dependencies: @@ -2641,18 +2538,8 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ansi-regex@5.0.1: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - ansis@4.2.0: {} - argparse@2.0.1: {} - - array-union@2.1.0: {} - assertion-error@2.0.1: {} ast-kit@3.0.0-beta.1: @@ -2667,7 +2554,7 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 - balanced-match@1.0.2: {} + balanced-match@4.0.4: {} birpc@4.0.0: {} @@ -2685,18 +2572,9 @@ snapshots: transitivePeerDependencies: - supports-color - brace-expansion@1.1.13: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.3: + brace-expansion@5.0.5: dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 + balanced-match: 4.0.4 busboy@1.6.0: dependencies: @@ -2716,25 +2594,12 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - callsites@3.1.0: {} - camelize-ts@3.0.0: {} chai@6.2.2: {} - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - cjs-module-lexer@1.4.3: {} - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - compressible@2.0.18: dependencies: mime-db: 1.54.0 @@ -2751,8 +2616,6 @@ snapshots: transitivePeerDependencies: - supports-color - concat-map@0.0.1: {} - content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -2794,10 +2657,6 @@ snapshots: depd@2.0.0: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - dts-resolver@2.1.3: {} dunder-proto@1.0.1: @@ -2826,95 +2685,99 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.39.4): + eslint-config-prettier@10.1.8(eslint@10.2.0): dependencies: - eslint: 9.39.4 + eslint: 10.2.0 - eslint-plugin-allowed-dependencies@2.1.0(eslint@9.39.4)(typescript-eslint@8.55.0): + eslint-plugin-allowed-dependencies@2.1.0(eslint@10.2.0)(typescript-eslint@8.59.0): dependencies: - eslint: 9.39.4 + eslint: 10.2.0 ramda: 0.32.0 - typescript-eslint: 8.55.0(eslint@9.39.4)(typescript@6.0.3) + typescript-eslint: 8.59.0(eslint@10.2.0)(typescript@6.0.3) - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8)(eslint@9.39.4)(prettier@3.8.3): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8)(eslint@10.2.0)(prettier@3.8.3): dependencies: - eslint: 9.39.4 + eslint: 10.2.0 prettier: 3.8.3 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.39.4) + eslint-config-prettier: 10.1.8(eslint@10.2.0) - eslint-scope@8.4.0: + eslint-scope@9.1.0: dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.0: {} - eslint@9.0.0: + eslint-visitor-keys@5.0.1: {} + + eslint@10.0.0: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.0.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0) '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.0.0 - '@humanwhocodes/config-array': 0.12.3 + '@eslint/config-array': 0.23.1 + '@eslint/config-helpers': 0.5.2 + '@eslint/core': 1.1.0 + '@eslint/plugin-kit': 0.6.0 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 ajv: 6.14.0 - chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 + eslint-scope: 9.1.0 + eslint-visitor-keys: 5.0.0 + espree: 11.1.0 esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - graphemer: 1.4.0 ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 + minimatch: 10.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 transitivePeerDependencies: - supports-color - eslint@9.39.4: + eslint@10.2.0: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.4 - '@eslint/plugin-kit': 0.4.1 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.5.5 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 ajv: 6.14.0 - chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -2925,18 +2788,23 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 + minimatch: 10.2.4 natural-compare: 1.4.0 optionator: 0.9.4 transitivePeerDependencies: - supports-color - espree@10.4.0: + espree@11.1.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.0 + + espree@11.2.0: dependencies: acorn: 8.16.0 acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 4.2.1 + eslint-visitor-keys: 5.0.1 esquery@1.7.0: dependencies: @@ -3031,22 +2899,10 @@ snapshots: 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 - fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} - fastq@1.19.1: - dependencies: - reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -3057,10 +2913,6 @@ snapshots: dependencies: flat-cache: 4.0.1 - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - finalhandler@2.1.1: dependencies: debug: 4.4.3 @@ -3117,29 +2969,14 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 - globals@14.0.0: {} - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 + globals@17.5.0: {} gopd@1.2.0: {} - graphemer@1.4.0: {} - has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -3170,11 +3007,6 @@ snapshots: ignore@7.0.5: {} - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - import-without-cache@0.3.3: {} imurmurhash@0.1.4: {} @@ -3189,10 +3021,6 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-number@7.0.0: {} - - is-path-inside@3.0.3: {} - is-promise@4.0.0: {} isexe@2.0.0: {} @@ -3212,10 +3040,6 @@ snapshots: js-tokens@10.0.0: {} - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -3267,15 +3091,8 @@ snapshots: merge-descriptors@2.0.0: {} - merge2@1.4.1: {} - methods@1.1.2: {} - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.2 - mime-db@1.52.0: {} mime-db@1.54.0: {} @@ -3290,13 +3107,13 @@ snapshots: mime@1.6.0: {} - minimatch@3.1.5: + minimatch@10.1.2: dependencies: - brace-expansion: 1.1.13 + '@isaacs/brace-expansion': 5.0.1 - minimatch@9.0.9: + minimatch@10.2.4: dependencies: - brace-expansion: 2.0.3 + brace-expansion: 5.0.5 ms@2.0.0: {} @@ -3367,10 +3184,6 @@ snapshots: dependencies: p-limit: 3.1.0 - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -3379,14 +3192,10 @@ snapshots: path-to-regexp@8.4.0: {} - path-type@4.0.0: {} - pathe@2.0.3: {} picocolors@1.1.1: {} - picomatch@2.3.2: {} - picomatch@4.0.4: {} postcss@8.5.10: @@ -3416,8 +3225,6 @@ snapshots: quansync@1.0.0: {} - queue-microtask@1.2.3: {} - ramda@0.32.0: {} range-parser@1.2.1: {} @@ -3429,12 +3236,8 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: {} - reusify@1.1.0: {} - rolldown-plugin-dts@0.23.2(rolldown@1.0.0-rc.17)(typescript@6.0.3): dependencies: '@babel/generator': 8.0.0-rc.3 @@ -3484,10 +3287,6 @@ snapshots: transitivePeerDependencies: - supports-color - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} @@ -3557,8 +3356,6 @@ snapshots: siginfo@2.0.0: {} - slash@3.0.0: {} - snakify-ts@2.3.0: dependencies: lodash.snakecase: 4.1.1 @@ -3573,12 +3370,6 @@ snapshots: streamsearch@1.1.0: {} - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-json-comments@3.1.1: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -3594,8 +3385,6 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - text-table@0.2.0: {} - tinybench@2.9.0: {} tinyexec@1.1.1: {} @@ -3607,15 +3396,11 @@ snapshots: tinyrainbow@3.1.0: {} - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - toidentifier@1.0.1: {} tree-kill@1.2.2: {} - ts-api-utils@1.4.3(typescript@5.1.3): + ts-api-utils@2.5.0(typescript@5.1.3): dependencies: typescript: 5.1.3 @@ -3675,24 +3460,24 @@ snapshots: dependencies: ts-toolbelt: 9.6.0 - typescript-eslint@8.0.0(eslint@9.0.0)(typescript@5.1.3): + typescript-eslint@8.58.0(eslint@10.0.0)(typescript@5.1.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0)(eslint@9.0.0)(typescript@5.1.3) - '@typescript-eslint/parser': 8.0.0(eslint@9.0.0)(typescript@5.1.3) - '@typescript-eslint/utils': 8.0.0(eslint@9.0.0)(typescript@5.1.3) - optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0)(eslint@10.0.0)(typescript@5.1.3) + '@typescript-eslint/parser': 8.58.0(eslint@10.0.0)(typescript@5.1.3) + '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.1.3) + '@typescript-eslint/utils': 8.58.0(eslint@10.0.0)(typescript@5.1.3) + eslint: 10.0.0 typescript: 5.1.3 transitivePeerDependencies: - - eslint - supports-color - typescript-eslint@8.55.0(eslint@9.39.4)(typescript@6.0.3): + typescript-eslint@8.59.0(eslint@10.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0)(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/parser': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.4)(typescript@6.0.3) - eslint: 9.39.4 + '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0)(eslint@10.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.0(eslint@10.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.0)(typescript@6.0.3) + eslint: 10.2.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3710,7 +3495,7 @@ snapshots: undici-types@7.19.2: {} - undici@7.25.0: {} + undici@8.0.0: {} unpipe@1.0.0: {} @@ -3797,4 +3582,4 @@ snapshots: zod@4.3.4: {} - zod@4.3.6: {} + zod@4.4.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6b9d1eb51a..c6cb303913 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -42,8 +42,6 @@ minimumReleaseAgeExclude: publicHoistPattern: - "@typescript-eslint/*" # used as an assumed transitive in migration - "@vitest/*" # used by vitest.setup.ts and vitest.config.ts - - "@eslint/*" # used by eslint.config.js - - "globals" # used by eslint.config.js - "@types/qs" # used by index.ts, fixes TS2742 for attachRouting - "@types/express-serve-static-core" # used by index.ts, fixes TS2742 for attachRouting - "rolldown" # used by readableDts tool @@ -51,22 +49,22 @@ catalogs: prod: "ramda": "^0.32.0" peer: - "zod": "~4.3.4" + "zod": "^4.3.4" dev: "@types/compression": "^1.8.1" "@types/express": "^5.0.6" "@types/express-fileupload": "^1.5.1" "@types/http-errors": "^2.0.5" "@types/ramda": "^0.31.1" - "@typescript-eslint/rule-tester": "^8.51.0" + "@typescript-eslint/rule-tester": "^8.59.0" "camelize-ts": "^3.0.0" "compression": "^1.8.1" "express": "^5.2.1" "express-fileupload": "^1.5.2" "http-errors": "^2.0.1" "typescript": "^6.0.2" - "typescript-eslint": "^8.51.0" - "zod": "~4.3.4" + "typescript-eslint": "^8.59.0" + "zod": "^4.4.1" overrides: "@scarf/scarf": "-" "lightningcss": "-" diff --git a/tools/license.ts b/tools/license.ts index 857db55fd8..e264b388c0 100644 --- a/tools/license.ts +++ b/tools/license.ts @@ -1,5 +1,5 @@ import { writeFile } from "node:fs/promises"; -import manifest from "../express-zod-api/package.json"; +import manifest from "../express-zod-api/package.json" with { type: "json" }; const text = ` MIT License diff --git a/tools/make-tests.ts b/tools/make-tests.ts index d0b42b2b5c..c49f858657 100644 --- a/tools/make-tests.ts +++ b/tools/make-tests.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { readFile, writeFile } from "node:fs/promises"; -import { givePort } from "./ports"; +import { givePort } from "./ports.ts"; const extractQuickStartFromReadme = async () => { const readme = await readFile("README.md", "utf-8"); diff --git a/tsconfig.json b/tsconfig.json index 7556c6895b..459782a6b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@tsconfig/node20/tsconfig.json", + "extends": "@tsconfig/node22/tsconfig.json", "compilerOptions": { "module": "preserve", "moduleResolution": "bundler", @@ -9,6 +9,8 @@ "noImplicitOverride": true, "noUncheckedSideEffectImports": true, "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, "types": ["node", "vitest/globals"] } } diff --git a/zod-plugin/CHANGELOG.md b/zod-plugin/CHANGELOG.md index 5fe7f190ba..8b97dc7e1d 100644 --- a/zod-plugin/CHANGELOG.md +++ b/zod-plugin/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## Version 5 + +### v5.0.0 + +- Supported Node.js versions: `^22.19.0 || ^24.0.0`; +- `getBrand()` removed: + - use `schema.meta()?.["x-brand"]` instead. +- Added `xBrand()` method to all Zod schemas: + - shorthand for `.meta({ "x-brand": ... })`; + - This method does not conflict with Zod 4.4+ internal mechanisms; + - Use `xBrand()` instead of `brand()` for setting runtime-distinguishable brands; +- Zod compatibility updated to `^4.3.4` (supports Zod 4.4+). + ## Version 4 ### v4.1.1 diff --git a/zod-plugin/README.md b/zod-plugin/README.md index d4667d85d0..8c32d08b0c 100644 --- a/zod-plugin/README.md +++ b/zod-plugin/README.md @@ -1,4 +1,4 @@ -# Zod Plugin from Express Zod API +# Zod Plugin for Express Zod API ## Overview @@ -8,31 +8,26 @@ This module extends Zod functionality when it's imported: - shorthand for `.meta({ examples: [...] })`; - Adds `.deprecated()` method to all Zod schemas: - shorthand for `.meta({ deprecated: true })`; +- Adds `.xBrand()` method to all Zod schemas: + - shorthand for `.meta({ "x-brand": ... })` making the brand available in runtime; + - This method does not conflict with Zod 4.4+ internal mechanisms; - Adds `.label()` method to `ZodDefault`: - shorthand for `.meta({ default: ... })`; - Adds `.remap()` method to `ZodObject` for renaming object properties: - Supports a mapping object or an object transforming function as an argument; - Relies on `R.renameKeys()` from the `ramda` library; -- Alters the `.brand()` method on all Zod schemas: - - shorthand for `.meta({ "x-brand": ... })` making the brand available in runtime via `getBrand()` helper; ## Requirements -- Compatible with Zod versions `~4.3.4` (<4.4.0); -- Zod 4.4+ support will be available in v5 (the next major version). +- Compatible with Zod versions `^4.3.4` (including 4.4+); ## Basic usage ```ts import { z } from "zod"; -import { getBrand } from "@express-zod-api/zod-plugin"; +import "@express-zod-api/zod-plugin"; -const schema = z.string().example("test").example("another").brand("custom"); +const schema = z.string().example("test").example("another").xBrand("custom"); -getBrand(schema); // "custom" schema.meta(); // { examples: ["test", "another"], "x-brand": "custom" } ``` - -## Helpers - -- `getBrand()` — retrieves the brand from the schema that was set by its `.brand()` method. diff --git a/zod-plugin/package.json b/zod-plugin/package.json index 01e78d0d91..d8816f074b 100644 --- a/zod-plugin/package.json +++ b/zod-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@express-zod-api/zod-plugin", - "version": "4.1.1", + "version": "5.0.0-beta.4", "license": "MIT", "description": "Zod plugin for express-zod-api", "sideEffects": true, @@ -18,7 +18,7 @@ "scripts": { "pretest": "tsc", "test": "vitest run", - "build": "tsdown --config-loader unrun", + "build": "tsdown", "prepublishOnly": "eslint && pnpm test && pnpm build" }, "type": "module", @@ -36,7 +36,7 @@ "*.md" ], "engines": { - "node": "^20.19.0 || ^22.12.0 || ^24.0.0" + "node": "^22.19.0 || ^24.0.0" }, "dependencies": { "ramda": "catalog:prod" diff --git a/zod-plugin/src/augmentation.ts b/zod-plugin/src/augmentation.ts index 9f166fbf6f..0eeac16fba 100644 --- a/zod-plugin/src/augmentation.ts +++ b/zod-plugin/src/augmentation.ts @@ -15,9 +15,10 @@ declare module "zod/v4/core" { * @see https://github.com/colinhacks/zod/blob/90efe7fa6135119224412c7081bd12ef0bccef26/plugin/effect/src/index.ts#L21-L31 * @desc This code modifies and extends zod's functionality immediately when importing the plugin. * @desc Enables .example() and .deprecated() on all schemas (ZodType) + * @desc Enables .xBrand() on all schemas as an alternative to .brand() that doesn't conflict with Zod 4.4+ * @desc Enables .label() on ZodDefault * @desc Enables .remap() on ZodObject - * @desc Stores the argument supplied to .brand() on all schemas (runtime distinguishable branded types) + * @desc Stores the argument supplied to .xBrand() on all schemas (runtime distinguishable) * */ declare module "zod" { interface ZodType< @@ -29,6 +30,8 @@ declare module "zod" { /** @desc Shorthand for .meta({ examples }) */ example(example: z.output): this; deprecated(): this; + /** @desc Shorthand for .meta({ "x-brand": ... }) */ + xBrand(brand?: PropertyKey): this; } interface ZodDefault extends z._ZodType>, z.core.$ZodDefault { diff --git a/zod-plugin/src/brand.ts b/zod-plugin/src/brand.ts index c0a7ba0788..474d7707cb 100644 --- a/zod-plugin/src/brand.ts +++ b/zod-plugin/src/brand.ts @@ -1,19 +1,2 @@ -import { globalRegistry, z } from "zod"; - /** The property we store the brand in */ export const brandProperty = "x-brand" as const; - -/** - * @public - * @desc Retrieves the brand from the schema set by its .brand() method. - * */ -export const getBrand = (subject: z.core.$ZodType) => { - const { [brandProperty]: brand } = globalRegistry.get(subject) || {}; - if ( - typeof brand === "symbol" || - typeof brand === "string" || - typeof brand === "number" - ) - return brand; - return undefined; -}; diff --git a/zod-plugin/src/index.ts b/zod-plugin/src/index.ts index fd7b1155f3..b8d89756ad 100644 --- a/zod-plugin/src/index.ts +++ b/zod-plugin/src/index.ts @@ -1,2 +1 @@ import "./runtime"; // side effects here -export { getBrand } from "./brand"; diff --git a/zod-plugin/src/runtime.ts b/zod-plugin/src/runtime.ts index 1b9552d901..ad2cc758c9 100644 --- a/zod-plugin/src/runtime.ts +++ b/zod-plugin/src/runtime.ts @@ -24,11 +24,9 @@ if (!(pluginFlag in globalThis)) { value: deprecationSetter, writable: false, }, - ["brand" satisfies keyof z.ZodType]: { - set() {}, // this is required to override the existing method - get() { - return brandSetter.bind(this) as z.ZodType["brand"]; - }, + ["xBrand" satisfies keyof z.ZodType]: { + value: brandSetter, + writable: false, }, }); } diff --git a/zod-plugin/tests/brand.spec.ts b/zod-plugin/tests/brand.spec.ts index 1def208cde..cd7df3d525 100644 --- a/zod-plugin/tests/brand.spec.ts +++ b/zod-plugin/tests/brand.spec.ts @@ -1,5 +1,4 @@ -import { z, globalRegistry } from "zod"; -import { brandProperty, getBrand } from "../src/brand"; +import { brandProperty } from "../src/brand"; describe("Brand", () => { describe("brandProperty", () => { @@ -7,15 +6,4 @@ describe("Brand", () => { expect(brandProperty).toBe("x-brand"); }); }); - - describe("getBrand", () => { - test.each([{ [brandProperty]: "test" }, {}, undefined])( - "should take it from metadata in globalRegistry %#", - (metadata) => { - const subject = z.string(); - if (metadata) globalRegistry.add(subject, metadata); - expect(getBrand(subject)).toBe(metadata?.[brandProperty]); - }, - ); - }); }); diff --git a/zod-plugin/tests/index.spec.ts b/zod-plugin/tests/index.spec.ts index 88116b5df1..aa296ec7f7 100644 --- a/zod-plugin/tests/index.spec.ts +++ b/zod-plugin/tests/index.spec.ts @@ -27,9 +27,7 @@ describe("Entrypoint", () => { .toEqualTypeOf(); }); - test("Exports", () => { - expect(entrypoint).toMatchObject({ - getBrand: expect.any(Function), - }); + test("has no exports", () => { + expect(Object.keys(entrypoint)).toHaveLength(0); }); }); diff --git a/zod-plugin/tests/runtime.spec.ts b/zod-plugin/tests/runtime.spec.ts index b14e706950..cf80f8e665 100644 --- a/zod-plugin/tests/runtime.spec.ts +++ b/zod-plugin/tests/runtime.spec.ts @@ -67,15 +67,15 @@ describe.each<{ variant: string; z: typeof zESM }>([ }); }); - describe(".brand()", () => { + describe(".xBrand()", () => { test("should set the brand", () => { - const subject = z.string().brand("test"); + const subject = z.string().xBrand("test"); expect(subject.meta()).toEqual({ "x-brand": "test" }); }); test("should withstand refinements", () => { const schema = z.string(); - const schemaWithMeta = schema.brand("test"); + const schemaWithMeta = schema.xBrand("test"); expect(schemaWithMeta.meta()).toEqual({ "x-brand": "test" }); expect(schemaWithMeta.regex(/@example.com$/).meta()).toEqual({ "x-brand": "test", @@ -83,7 +83,7 @@ describe.each<{ variant: string; z: typeof zESM }>([ }); test("should withstand describing", () => { - const schema = z.string().brand("test").describe("something"); + const schema = z.string().xBrand("test").describe("something"); expect(schema.meta()).toEqual({ "x-brand": "test", description: "something", diff --git a/zod-plugin/tsdown.config.ts b/zod-plugin/tsdown.config.ts index f1d481e7c8..c2fd153916 100644 --- a/zod-plugin/tsdown.config.ts +++ b/zod-plugin/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from "tsdown"; import manifest from "./package.json" with { type: "json" }; -import { fixDtsPlugin } from "../tools/fixDts"; +import { fixDtsPlugin } from "../tools/fixDts.ts"; const plugins = [fixDtsPlugin()];