UNPKG

express-zod-api

Version:

A Typescript framework to help you get an API server up and running with I/O schema validation and custom middlewares in minutes.

1,184 lines (929 loc) 219 kB
# Changelog ## Version 28 ### v28.4.0 - Improved `testMiddleware()` to return the typed `output` property: - Changed from `Record<string, unknown>` to `Partial<RET>`, where `RET` is the context type the Middleware returns; - This should simplify testing the context properties, especially functional ones; - If Middleware throws, the `output` would still be an empty object, which is aligned with its new type. ### v28.3.0 - `EndpointsFactory::addContext()` now passes the previously accumulated context to its callback: - The argument function receives the current context as the first argument; - Improved the declaration file for most of the essential entities. ### v28.2.0 - Added `createCacheMiddleware()` function for solving most of the caching problems: - The function accepts an optional default policy to apply `Cache-Control` header to every response; - It returns a Middleware providing several properties and caching helpers to context: - `cacheControl` — the parsed request's `Cache-Control` header into a typed object; - `ifNoneMatch` — the parsed `If-None-Match` request header into an array of ETags or `"*"`; - `ifModifiedSince` — the parsed `If-Modified-Since` request header into a `Date`; - `addCachePolicy()` — extends the default policy into the `Cache-Control` response header; - `setETag()` — sets the `ETag` response header; - `setLastModified()` — sets the `Last-Modified` response header; - `setVary()` — sets the `Vary` response header; - `setExpires()` — sets the `Expires` response header; - `clearSiteData()` — sets the `Clear-Site-Data` header with `cache` directive; - `notModified()` — sends an HTTP 304 response and ends the stream; - Adjusted the Endpoint execution to skip output validation in case `response.writableEnded` (`notModified` called); - Added two shorthand methods to `EndpointsFactory` class: `useCache()` and `useCookies()`. ### v28.1.1 - Depicting Endpoints built on `new EventStreamFactory({})` (having empty argument) will throw a `ResultHandlerError`: - Applies to `Documentation`, `Integration` as well as self-diagnostics (starting the server in development mode). ### v28.1.0 - Added support for cookie handling: - Cookie parsing can be enabled and configured in config (requires to install `cookie-parser`); - `cookies` and `signedCookies` can be used as `inputSources` in config; - `createCookieMiddleware()` creates a Middleware that exposes `setCookie()` and `clearCookie()` helpers into context as well as the `getCookie()` one as an alternative to using cookies within `inputSources`; - Documentation depicts request parameters when Middleware has `security` schema with `type: cookie`. ### v28.0.1 - Adjusted the list of well-known headers, recognized by Documentation generator: - Added: cdn-loop; - Removed (not expected in Request): accept-ch, alternates, authentication-control, authentication-info, cross-origin-embedder-policy, cross-origin-embedder-policy-report-only, cross-origin-opener-policy, cross-origin-opener-policy-report-only, cross-origin-resource-policy, dpop-nonce, nel, p3p, public, public-key-pins, public-key-pins-report-only, setprofile, use-as-dictionary. ### v28.0.0 - Supported Node.js versions: `^22.19.0 || ^24.0.0 || ^26.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()` and `.deprecated()` 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): - Now requires `eslint@^10.0.0` and `typescript-eslint@^8.58.0`. ```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.3.0 - Supporting Node 26. ### v27.2.6 - Limited Zod compatibility to `~4.3.4` (<4.4.0): - Zod 4.4.0 introduced a breaking change to how `brand` method works, making the current plugin approach incompatible; - `@express-zod-api/zod-plugin` version bumped to `^4.1.1` (limited Zod compatibility as well); - Supporting Zod 4.4+ requires a breaking change and will be addressed in v28 (next major version). ### v27.2.5 - Reduced memory footprint at runtime: deferred population of well-known headers to Documentation; - Faster header lookup in Documentation generator (only when `headers` enabled in `inputSources`). ### v27.2.4 - Fixed performance regression since v24.0.0: - Removed unnecessary processing of output schema examples; - Those used to be processed when the first API request occurred on endpoint; - Examples are only required for Documentation generator, not for runtime validation. ### v27.2.3 - Optimizing the performance of traverses: - Replaced O(n) `Array::shift()` and `::unshift()` operations with O(1) index-based iteration; - The expected boost is from 1.8x to 20x depending on queue size; - Scope: generators (Integration and Documentation), startup (diagnostics, routing). ### v27.2.2 - `@express-zod-api/zod-plugin` version bumped to `^4.1.0`. ### v27.2.1 - Improved handling of native Express middleware return values: - Avoids potential errors when middleware returns non-promise truthy values. ### v27.2.0 - Supporting TypeScript 6: - Supported `typescript` versions: `^5.1.3 || ^6.0.2`. ### v27.1.0 - Introducing `ez.paginated()` helper for creating paginated endpoints: - The configurable helper returns `input` and `output` schemas for your Endpoint; - Use the `style` option to choose between `offset` and `cursor` pagination; - The `itemsName` option configures the name of the property containing the items array; - The `Integration` generator now equips the `Client` with a static `hasMore()` method; - The `Incremental` added to the list of well-known recognizable request headers. ```ts import { z } from "zod"; import { ez, defaultEndpointsFactory } from "express-zod-api"; const pagination = ez.paginated({ style: "offset", // or "cursor" itemSchema: z.object({ id: z.number(), name: z.string() }), itemsName: "users", // defines the output name of the items array maxLimit: 100, defaultLimit: 20, }); const listUsers = defaultEndpointsFactory.build({ input: pagination.input, output: pagination.output, handler: async ({ input: { limit, offset } }) => { const { users, total } = await db.getUsers(limit, offset); return { users, total, limit, offset }; // or { users, nextCursor, limit } for cursor pagination }, }); ``` ### v27.0.1 - Removed debug-level comments from the declaration files in the distribution. ### v27.0.0 - Supported `zod` versions: `^4.3.4`; - The new version of Zod Plugin uses the inheritable metadata feature of Zod 4.3; - The `typescript` dependency is now optional and only required for making `Integration`: - Either import and assign the `typescript` property to its constructor argument; - Or use the new static async method `create()` to delegate the import; - This change addresses the memory consumption issue fixed previously in v26.1.0, but with proper ESM handling; - Consider [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration). ```diff /** Option 1: import and assign */ import { Integration } from "express-zod-api"; + import typescript from "typescript"; const client = new Integration({ routing, config, + typescript, }); ``` ```diff /** Option 2: delegate asynchronously */ import { Integration } from "express-zod-api"; - const client = new Integration({ + const client = await Integration.create({ routing, config, }); ``` ## Version 26 ### v26.3.2 - Improved readability of the types declaration in the bundle; ### v26.3.1 - Fixed the type of `ctx` for Endpoints built on factories having at least one Middleware: - Removed `Record<string, never>` from the type union; - This prevents accessing non-existing properties on `ctx`; - The issue was reported by [@pycanis](https://github.com/pycanis). ### v26.3.0 - Supporting several latest features of Zod 4.2 and 4.3 by the `Integration` generator: - `z.looseRecord()` schema; - Object properties wrapped by `z.exactOptional()` or created by `ZodType::exactOptional()` method. ### v26.2.0 - Ability to specify a custom name for a schema in the generated Documentation: - Use the `.meta()` method on a schema with an `{ id }` as an argument; - The `id` must be unique across all schemas used in your API; - The feature proposed by [@arlyon](https://github.com/arlyon) 2 years ago, but the implementation became possible only with Zod 4 and thanks to suggestions from [@Upsilon-Iridani](https://github.com/Upsilon-Iridani). ### v26.1.0 - Optimization to the memory consumption for your API: - It has been discovered that static import of `typescript` within the framework consumes memory unnecessarily; - Importing `typescript` is only necessary to generate an Integration; - This version avoids static importing, but the solution is temporary in order to avoid breaking changes; - The issue was found, investigated and reported by [@NicolasMahe](https://github.com/NicolasMahe). ### v26.0.0 - Supported `http-errors` versions: `^2.0.1`; - Supported `zod` versions: `^4.1.13`: - This Zod patch contains an [important fix](https://github.com/colinhacks/zod/pull/5452) that makes the `globalRegistry` truly global across both CJS and ESM bundles of the Zod distribution; - The issue was found and reported by [@shadone](https://github.com/shadone); - The new version of the Zod plugin now also extends the CJS exports of Zod: - This fixes the "TypeError: example is not a function" in CJS and removes the requirement to use an ESM environment; - The issue was reported by [@squishykid](https://github.com/squishykid) and addressed earlier in [v25.5.3](#v2553); - `DependsOnMethod` removed: - You can now specify methods as direct keys of an assigned object in `Routing`; - That object can still contain nested paths as before; - Keys matching lowercase HTTP methods are treated according to the new config setting `methodLikeRouteBehavior`: - `method` — when assigned with an Endpoint, the key is treated as a method of its parent path (default); - `path` — the key is always treated as a nested path segment; - The `options` property has been renamed to `ctx` in the argument of: - `Middleware::handler()`, - `ResultHandler::handler()`, - The `handler` of the `EndpointsFactory::build()` argument, - `testMiddleware()`; - `EndpointsFactory::addOptions()` renamed to `addContext()`; - The `Integration::constructor()` argument object now requires a `config` property, similar to `Documentation`; - Consider [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration). ```diff const routing: Routing = { - "/v1/users": new DependsOnMethod({ + "/v1/users": { get: getUserEndpoint, - }).nest({ create: makeUserEndpoint - }), + }, }; ``` ## Version 25 ### v25.6.1 - Technical update before v26: no significant changes. ### v25.6.0 - Added `afterRouting` hook to server configuration: - Similar to `beforeRouting` one; - A code to execute after processing the Routing of your API, but before error handling. ### v25.5.3 - Updated environment requirements in the Readme: - Version 25 should be run in an ESM environment because it installs the Zod plugin using the `import` statement; - To ensure this, either set `type: module` in `package.json`, use `.mts` extension, or run using `tsx`/`vite-node`; - Although all Node.js versions support `require(ESM)`, `zod` remains a dual package with both CJS and ESM copies; - If your code uses `require("zod")` (CJS), it is not the same instance that the framework operates; - This can lead to `TypeError: example is not a function` and loss of metadata set by the `.meta()` method; - This issue was reported by [@squishykid](https://github.com/squishykid). ### v25.5.2 - Added `z.function()` to the list of JSON-incompatible schemas: - It will warn when using such schema in a JSON operating Endpoint; - `z.function()` [became a schema again](https://github.com/colinhacks/zod/issues/4143) in Zod 4.1.0. ### v25.5.1 - Aligned the requirements on Node.js versions between for `@express-zod-api/zod-plugin`; - Added the `signal` property, introduced in [v25.5.0](#v2550), to example in Readme. ### v25.5.0 - Feature: `AbortSignal` for subscription handlers: - The `handler` of `EventStreamFactory::build()` is now equipped with `signal` option; - That signal is aborted when the client disconnects; - The feature was suggested and implemented by [@jakub-msqt](https://github.com/jakub-msqt). ### v25.4.1 - This patch fixes the issue for users facing `TypeError: .example is not a function`, but not using that method: - Some environments fail to load Zod plugin properly so that the usage of `ZodType::example()` (plugin method) internally by the framework itself could cause that error; - This version fixes the problem by replacing the usage of `.example()` with `globalRegistry.add()`; - The issue was found and reported by [@misha-z1nchuk](https://github.com/misha-z1nchuk). ### v25.4.0 - Feat: configurable query parser: - In Express 5 the default query parser was changed from "extended" to "simple"; - The "extended" parser is the `qs` module, while the "simple" parser is the `node:querystring` module; - This version introduces the new config option `queryParser` having the default value "simple" for compatibility; - The "extended" parser supports nested objects and arrays with optional indexes in square brackets; - You can now choose between "simple" and "extended" parsers as well as configure a custom implementation. ```ts import { createConfig } from "express-zod-api"; import qs from "qs"; const config = createConfig({ // for comma-separated arrays: ?values=1,2,3 queryParser: (query) => qs.parse(query, { comma: true }), }); ``` ### v25.3.1 - Small optimization for running diagnostics (non-production mode); - Fixed the type of the `examples` property for `ez.dateIn()` and `ez.dateOut()` arguments. ### v25.3.0 - Changed bundler from `tsup` to `tsdown`. ### v25.2.0 - Zod Plugin extracted into a standalone package — `@express-zod-api/zod-plugin`: - The framework declares the plugin as a runtime dependency, you don't need to install it; - The plugin will continue to evolve independently and could now be used for developing other zod-based software. ### v25.1.0 - Ability to disable the depiction of the `HEAD` method: - New option `hasHeadMethod` on the argument of `Documentation` and `Integration` constructors; - Depicts the HEAD method for each Endpoint supporting the GET method; - The option is enabled by default (the behaviour introduced in [v24.7.0](#v2470)); - The feature suggested by [@GreaterTamarack](https://github.com/GreaterTamarack). ### v25.0.0 - Supported Node.js versions: `^20.19.0 || ^22.12.0 || ^24.0.0`; - The framework distribution is now ESM-only (finally); - All the Node.js versions listed above support `require(ESM)` syntax; - If facing TypeScript error `TS1479`, ensure either: - using the [recommended tsconfig base for Node 20+](https://github.com/tsconfig/bases/blob/main/bases/node20.json); - or switching your project to ESM by setting `"type": "module"` in `package.json`; - Supported `zod` version: `^4.0.0`; - Compatibility with `zod@^3` is dropped; - You SHOULD now `import { z } from "zod"` without the `/v4` suffix; - Changes to the Zod plugin and metadata processing: - Dropped support of examples that are given as `example` property of `.meta()` argument; - Dropped support of examples given within an object-based value of `examples` property of `.meta()` argument; - Use either `.example()` method or `.meta()` method with `examples` property being an array; - Changes to the `Middleware` class: - When the `input` schema is not defined, the `input` argument of the `handler` method is now `unknown`; - Changes to publicly exposed method: - The `getExamples()` helper is removed, use `.meta().examples` or `globalRegistry.get().examples` instead. - Consider [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration). ```diff - z.string().meta({ example: "test" }); - z.string().meta({ examples: { one: { value: "test" } } }); + z.string().meta({ examples: ["test"] }); + z.string().example("test").example("another"); // plugin method ``` ```diff - getExamples(schema); + schema.meta()?.examples || []; + globalRegistry.get(schema)?.examples || []; ``` ## Version 24 ### v24.7.3 - Fixed the depiction of the negative response to `HEAD` requests: - Should have no response body, exactly as the positive one; - This version corrects the implementation introduced in [v24.7.0](#v2470). ### v24.7.2 - Fixed the negative response MIME type for ~~`arrayResultHandler`~~ (deprecated entity): - Should have been `text/plain`. ### v24.7.1 - Compatibility fix for `zod@^3.25.68` and `^4.0.0`: - Previously typed metadata properties `example` and `examples` were removed from `zod` core: See [Commit ee5615d](https://github.com/colinhacks/zod/commit/ee5615d76b93aac15d7428a17b834a062235f6a1); - This version restores those properties as a part of Zod plugin in order to comply to the existing implementation; - The `example` property is marked as deprecated and will be removed in v25 — please refrain from using it; - The `examples` property still supports an object for backward compatibility, but it will only support array in v25; - To avoid confusion, consider using the Zod plugin's method `.example()` where possible (except `ez.dateIn()`). ### v24.7.0 - Supporting `HEAD` method: - The purpose of the `HEAD` method is to retrieve the headers without performing `GET` request; - It is the built-in feature of Express to handle `HEAD` requests by the handlers for `GET` requests; - Therefore, each `Endpoint` supporting `get` method also handles `head` requests (no work needed); - Added `HEAD` method to CORS response headers, along with `OPTIONS`, for `GET` method supporting endpoints; - ~~Positive~~ Response to `HEAD` request should contain same headers as `GET` would, but without the body: - Added `head` request depiction to the generated `Documentation`; - Added `head` request types to the generated `Integration` client; - ~~Positive~~ Response to `HEAD` request should contain the `Content-Length` header: - `ResultHandler`s using `response.send()` (as well as its shorthands such as `.json()`) automatically do that instead of sending the response body (no work needed); - Other approaches, such as stream piping, might require to implement `Content-Length` header for `HEAD` requests; - This feature was suggested by [@pepegc](https://github.com/pepegc); - Caveats: - The following properties, when assigned with functions, can now receive `head` as an argument: - `operationId` supplied to `EndpointsFactory::build()`; - `isHeader` supplied to `Documentation::constructor()`; - If the `operationId` is assigned with a `string` then it may be appended with `__HEAD` for `head` method; ### v24.6.2 - Correcting recommendations given in [v24.6.0](#v2460) regarding using with `zod@^4.0.0`: - Make sure the `moduleResolution` in your `tsconfig.json` is either `node16`, `nodenext` or `bundler`; - Consider the [recommended tsconfig base, Node 20+](https://github.com/tsconfig/bases/blob/main/bases/node20.json); - Then you MAY `import { z } from "zod"`; - Otherwise, you MUST keep `import { z } from "zod/v4"`; - In some cases module augmentation (Zod plugin) did not work and caused schema assignment errors for some users; - The issue was reported by [@MichaelHindley](https://github.com/MichaelHindley); - This potential inconvenience will be resolved by dropping `zod@^3` in the next version of Express Zod API: - If you're having troubles using the framework with `zod@^4.0.0`, consider upgrading to v25 (currently beta). ### v24.6.1 - Compatibility fix for recently changed type of Express native middleware: - Fixes error `TS2345` when passing over an Express middleware to: - `EndpointsFactory::use()`; - `EndpointsFactory::addExpressMiddleware()`; - The issue caused by `@types/express-serve-static-core` v5.0.7; - The issue reported by [@zoton2](https://github.com/zoton2). ### v24.6.0 - Supporting `zod` versions `^3.25.35 || ^4.0.0`: - If you use `zod@^4.0.0` then you MAY `import { z } from "zod"`: - If facing error, ensure `moduleResolution` in your `tsconfig.json` is either `node16`, `nodenext` or `bundler`; - If you use `zod@^3.25.35` then keep `import { z } from "zod/v4"`; - For more details, see the [Explanation of the versioning strategy](https://github.com/colinhacks/zod/issues/4371). ### v24.5.0 - `openapi3-ts` version is `^4.5.0`; - Compatibility adjustment for Zod 3.25.72. ### v24.4.3 - Externalized the code responsible for the Zod plugin's method `z.object().remap()`: - Using the new method `R.renameKeys()` of `ramda@0.31`. ### v24.4.2 - Improved the type of the `input` argument for `Endpoint::handler()`: - For `z.object()`-based schema removed `never` for unknown properties; - Fixed incorrect typing of excessive properties when using a `z.looseObject()`-based schema; - The issue reported by [@ThomasKientz](https://github.com/ThomasKientz). ```ts import { defaultEndpointsFactory } from "express-zod-api"; import { z } from "zod/v4"; const endpoint1 = defaultEndpointsFactory.buildVoid({ input: z.object({ foo: z.string(), }), handler: async ({ input: { bar } }) => { console.log(bar); // before: never, after: TypeScript Error }, }); const endpoint2 = defaultEndpointsFactory.buildVoid({ input: z.looseObject({ foo: z.string(), }), handler: async ({ input: { bar } }) => { console.log(bar); // before: never, after: unknown }, }); ``` ### v24.4.1 - Compatibility fix for Zod 3.25.67. ### v24.4.0 - Automated migration moved to the [dedicated package](https://www.npmjs.com/package/@express-zod-api/migration). ### v24.3.2 - Removed previously deprecated plural properties from `ApiResponse` interface: `statusCodes`, `mimeTypes`. ### v24.3.1 - Compatibility fix for Zod 3.25.60. ### v24.3.0 - Technical update: switched to `pnpm`, no changes to the code. ### v24.2.3 - Fixed a bug about missing CORS headers in case of request parser errors: - This includes the case where exceeding the configured `upload.limits` cause the configured `upload.limitError`; - This bug was found and reported by [@james10424](https://github.com/james10424); - The reproduction config is explained in [issue 2706](https://github.com/RobinTail/express-zod-api/issues/2706). ### v24.2.2 - Zod plugin compatibility fix for [Zod v3.25.50](https://github.com/colinhacks/zod/releases/tag/v3.25.50). ### v24.2.1 - Prioritizing Zod native depiction for `z.enum()` and `z.literal()` by the `Documentation` generator: - Enum `type` implemented in [Zod v3.25.45](https://github.com/colinhacks/zod/releases/tag/v3.25.45); - Literal `type` implemented in [Zod v3.25.49](https://github.com/colinhacks/zod/releases/tag/v3.25.49). ### v24.2.0 - Supporting `z.nonoptional()` schema by `Integration` generator. ### v24.1.0 - Supporting the new `z.templateLiteral()` schema by the `Integration` (client side types generator); - Compatibility improvements due to the recent changes in Zod 4: - Restoring publicly exposed `getExamples()` helper (with a new signature); ```ts // z.templateLiteral(["start", z.number(), "mid", z.boolean(), "end"]) type Type1 = `start${number}mid${boolean}end`; ``` ### v24.0.0 - Switched to Zod 4: - Minimum supported version of `zod` is 3.25.35, BUT imports MUST be from `zod/v4`; - Read the [Explanation of the versioning strategy](https://github.com/colinhacks/zod/issues/4371); - Express Zod API, however, is not aiming to support both Zod 3 and Zod 4 simultaneously due to: - incompatibility of data structures; - operating composite schemas (need to avoid mixing schemas of different versions); - the temporary nature of this transition; - the advantages of Zod 4 that provide opportunities to simplifications and corrections of known issues. - `IOSchema` type had to be simplified down to a schema resulting to an `object`, but not an `array`; - Refer to [Migration guide on Zod 4](https://v4.zod.dev/v4/changelog) for adjusting your schemas; - Changes to `ZodType::example()` (Zod plugin method): - Now acts as an alias for `ZodType::meta({ examples })`; - The argument has to be the output type of the schema (used to be the opposite): - This change is only breaking for transforming schemas; - In order to specify an example for an input schema the `.example()` method must be called before `.transform()`; - The transforming proprietary schemas `ez.dateIn()` and `ez.dateOut()` now accept metadata as its argument: - This allows to set examples before transformation (`ez.dateIn()`) and to avoid the examples "branding"; - Changes to `Documentation`: - Generating Documentation is mostly delegated to Zod 4 `z.toJSONSchema()`; - Express Zod API implements some overrides and improvements to fit it into OpenAPI 3.1 that extends JSON Schema; - The `numericRange` option removed from `Documentation` class constructor argument; - The `Depicter` type signature changed: became a postprocessing function returning an overridden JSON Schema; - Changes to `Integration`: - The `optionalPropStyle` option removed from `Integration` class constructor: - Use `.optional()` to add question mark to the object property as well as `undefined` to its type; - Use `.or(z.undefined())` to add `undefined` to the type of the object property; - See the [reasoning](https://x.com/colinhacks/status/1919292504861491252); - Properties assigned with `z.any()` or `z.unknown()` schema are now typed as required: - Read the [details here](https://v4.zod.dev/v4/changelog#changes-zunknown-optionality); - Added types generation for `z.never()`, `z.void()` and `z.unknown()` schemas; - The fallback type for unsupported schemas and unclear transformations in response changed from `any` to `unknown`; - The argument of `ResultHandler::handler` is now discriminated: either `output` or `error` is `null`, not both; - The `getExamples()` public helper removed — ~~use `.meta()?.examples` instead~~, restored in v24.1.0; - Added the new proprietary schema `ez.buffer()`; - The `ez.file()` schema removed: use `z.string()`, `z.base64()`, `ez.buffer()` or their union; - Consider [the automated migration](https://www.npmjs.com/package/@express-zod-api/migration). ```diff - import { z } from "zod"; + import { z } from "zod/v4"; ``` ```diff input: z.string() + .example("123") .transform(Number) - .example("123") ``` ```diff - ez.dateIn().example("2021-12-31"); + ez.dateIn({ examples: ["2021-12-31"] }); - ez.file("base64"); + z.base64(); - ez.file("buffer"); + ez.buffer(); ``` ## Version 23 ### v23.6.1 - `createServer()` displays a warning when no server is configured. ### v23.6.0 - Featuring `gracefulShutdown.beforeExit()` hook: - The function to execute after the server was closed, but before terminating the process (can be asynchronous); - The feature suggested by [@HeikoOsigus](https://github.com/HeikoOsigus). ### v23.5.0 - Integer number `format` in generated Documentation now also depends on the `numericRange` option: - `int64` is the default format for the range of JavaScript safe integers; - `int32` is used when the specified range fits 32 bits (`4294967295`); - omitted when `numericRange` is set to `null` (opt-out); - The feature suggested by [@crgeary](https://github.com/crgeary). ### v23.4.1 - Fixed headers for an edge case of flat routing with explicit methods: - Routes resolved into same path but using different methods had incomplete CORS headers (when enabled); - Similarly, could affect `Allow` header when `wrongMethodBehavior` set to `405` (default). ```ts // reproduction sample import { Routing } from "express-zod-api"; const routing: Routing = { v1: { "get /user/retrieve": endpointA, user: { // same /v1/user/retrieve, but another method: "post retrieve": endpointB, // POST was missing in response headers to OPTIONS request }, }, }; ``` ### v23.4.0 - Feature: flat routing syntax with explicit method support: - `Routing` now supports slashes within keys, so that nested segments could be flattened; - `Routing` also supports explicitly specified `Method` for the keys assigned with `Endpoint`; - Leading slash is optional; - The feature suggested by [@williamgcampbell](https://github.com/williamgcampbell). ```ts import { Routing } from "express-zod-api"; const routing: Routing = { // flat syntax: "v1/books/:bookId": getBookEndpoint, // with method: "post /v1/books": addBookEndpoint, // nested: v1: { "delete /books/:bookId": deleteBookEndpoint, "patch /books/:bookId": changeBookEndpoint, }, }; ``` ### v23.3.0 - Upgraded `ansis` (direct dependency) to `^4.0.0`. ### v23.2.0 - Supporting Node 24. ### v23.1.2 - Simplified implementation for generating Documentation of `z.enum()` and `z.literal()`; - Fixed duplication in the Documentation generator code determining the requirement for a request body. ### v23.1.1 - Fixed response depiction in the generated Documentation: - coerced types were marked as nullable; - coerced, preprocessed and `z.any()` schemas were marked as optional. ### v23.1.0 - Improved generated Documentation: - Arrays having fixed length: `z.boolean().array().length(2)`; - Records with non-literal keys (added `propertyNames`): `z.record(z.string().regex(/x-\w+/), z.boolean())`. ### v23.0.0 - Minimum version of `express` (required peer dependency) is `5.1.0` (first release of v5 marked as `latest`); - Minimum version of `compression` (optional peer dependency) is `1.8.0` (it supports Brotli); - The default value for `wrongMethodBehavior` config option is changed to `405`; - Publicly exposed interfaces: `CustomHeaderSecurity` renamed to `HeaderSecurity`, `NormalizedResponse` removed. - The `errorHandler` property removed from `testMiddleware()` argument in favor of config option having same name; - Only the following methods remained public, while other methods and properties were marked internal or removed: - `Endpoint`: `.execute()` and `.deprecated()`; - `Middleware`: `.execute()`; - `ResultHandler`: `.execute()`; - `DependsOnMethod`: `.deprecated()`; - `Documentation`: constructor only; - `Integration`: `.print()` and `.printFormatted()`; - `ServeStatic`: constructor only; - Consider the automated migration using the built-in ESLint rule. ```js // eslint.config.mjs — minimal ESLint 9 config to apply migrations automatically using "eslint --fix" import parser from "@typescript-eslint/parser"; import migration from "express-zod-api/migration"; export default [ { languageOptions: { parser }, plugins: { migration } }, { files: ["**/*.ts"], rules: { "migration/v23": "error" } }, ]; ``` ## Version 22 ### v22.13.2 - Fixed inconsistency between the actual catcher behavior and the error handling documentation: - Removed conversion of non-`HttpError`s to `BadRequest` before passing them to `errorHandler`; - A `ResultHandler` configured as `errorHandler` is responsible to handling all errors and responding accordingly. - The default `errorHandler` is `defaultResultHandler`: - Using `ensureHttpError()` it coverts non-`HttpError`s to `InternalServerError` and responds with status code `500`; - The issue has occurred since [v19.0.0](#v1900). ### v22.13.1 - Fixed: the output type of the `ez.raw()` schema (without an argument) was missing the `raw` property (since v19.0.0). ### v22.13.0 - Ability to configure and disable access logging: - New config option: `accessLogger` — the function for producing access logs; - The default value is the function writing messages similar to `GET: /v1/path` having `debug` severity; - The option can be assigned with `null` to disable writing of access logs; - Thanks to the contributions of [@gmorgen1](https://github.com/gmorgen1) and [@crgeary](https://github.com/crgeary); - [@danmichaelo](https://github.com/danmichaelo) fixed a broken link in the Security policy; - Added JSDoc for several types involved into creating Middlewares and producing Endpoints. ```ts import { createConfig } from "express-zod-api"; const config = createConfig({ accessLogger: (request, logger) => logger.info(request.path), // or null to disable }); ``` ### v22.12.0 - Featuring HTML forms support (URL Encoded request body): - Introducing the new proprietary schema `ez.form()` accepting an object shape or a custom `z.object()` schema; - Introducing the new config option `formParser` having `express.urlencoded()` as the default value; - Requests to Endpoints having `input` schema assigned with `ez.form()` are parsed using `formParser`; - Exception: requests to Endpoints having `ez.upload()` within `ez.form()` are still parsed by `express-fileupload`; - The lack of this feature was reported by [@james10424](https://github.com/james10424). ```ts import { defaultEndpointsFactory, ez } from "express-zod-api"; import { z } from "zod"; // The request content type should be "application/x-www-form-urlencoded" export const submitFeedbackEndpoint = defaultEndpointsFactory.build({ method: "post", input: ez.form({ name: z.string().min(1), email: z.string().email(), message: z.string().min(1), }), }); ``` ### v22.11.2 - Fixed: allow future versions of Express 5: - Incorrect condition for the peer dependency was introduced in v21.0.0. ```diff - "express": "^4.21.1 || 5.0.1", + "express": "^4.21.1 || ^5.0.1", ``` ### v22.11.1 - Simplified the type of `requestMock` returned from `testEndpoint` and `testMiddleware`. ### v22.11.0 - Featuring an ability to configure the numeric range of the generated Documentation: - The new property `numericRange` on the `Documentation::constructor()` argument provides the way to specify acceptable limits of `z.number()` and `z.number().int()` that your API can handle; - Possible values: `{ integer: [number, number], float: [number, number] } | null`; - Those numbers are used to depict min/max values for `z.number()` schema without limiting refinements; - The default value is the limits of the JavaScript engine: - for integers: `[ Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER ]`; - for floats: `[ -Number.MAX_VALUE, Number.MAX_VALUE ]`; - The `null` value disables the feature (no min/max values printed unless defined explicitly by the schema); - This can be useful when a third party tool is having issues to process the generated Documentation; - Such an issue was reported by [@APTy](https://github.com/APTy) for the Java-based tool: [openapi-generator](https://github.com/OpenAPITools/openapi-generator); - Examples of representation of numerical schemes depending on this setting: ```yaml - schema: z.number() numericRange: undefined depicted: type: number format: double minimum: -1.7976931348623157e+308 # -Number.MAX_VALUE maximum: 1.7976931348623157e+308 # Number.MAX_VALUE - schema: z.number() numericRange: null depicted: type: number format: double - schema: z.number().int() numericRange: undefined depicted: type: integer format: int64 minimum: -9007199254740991 # Number.MIN_SAFE_INTEGER maximum: 9007199254740991 # Number.MAX_SAFE_INTEGER - schema: z.number().int() numericRange: null depicted: type: integer format: int64 - schema: z.number().int().nonnegative().max(100) numericRange: undefined depicted: type: integer format: int64 exclusiveMinimum: 0 # explicitly defined by .nonnegative() maximum: 100 # explicitly defined by .max() ``` ### v22.10.1 - Fixed catching errors in a custom `ResultHandler` used as `errorHandler` for Not Found routes having async `handler`. ```ts // reproduction import { createConfig, ResultHandler } from "express-zod-api"; createConfig({ errorHandler: new ResultHandler({ // rejected promise was not awaited: handler: async () => { throw new Error( "You should not do it. But if you do, we've got LastResortHandler to catch it.", ); }, }), }); ``` ### v22.10.0 - Featuring required request bodies in the generated Documentation: - This version sets the `required` property to `requestBody` when: - It contains the required properties on it; - Or it's based on `ez.raw()` (proprietary schema); - The presence of `requestBody` depends on the Endpoint method(s) and the configuration of `inputSources`; - The lack of the property was reported by [@LufyCZ](https://github.com/LufyCZ). ### v22.9.1 - Minor refactoring and optimizations. ### v22.9.0 - Featuring Deprecations: - You can deprecate all usage of an `Endpoint` using `EndpointsFactory::build({ deprecated: true })`; - You can deprecate a route using the assigned `Endpoint::deprecated()` or `DependsOnMethod::deprecated()`; - You can deprecate a schema using `ZodType::deprecated()`; - All `.deprecated()` methods are immutable — they create a new copy of the subject; - Deprecated schemas and endpoints are reflected in the generated `Documentation` and `Integration`; - The feature suggested by [@mlms13](https://github.com/mlms13). ```ts import { Routing, DependsOnMethod } 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 }), }); const routing: Routing = { v1: oldEndpoint.deprecated(), // deprecates the /v1 path v2: new DependsOnMethod({ get: oldEndpoint }).deprecated(), // deprecates the /v2 path v3: someEndpoint, // the path is assigned with initially deprecated endpoint (also deprecated) }; ``` ### v22.8.0 - Feature: warning about the endpoint input scheme ignoring the parameters of the route to which it is assigned: - There is a technological gap between routing and endpoints, which at the same time allows an endpoint to be reused across multiple routes. Therefore, there are no constraints between the route parameters and the `input` schema; - This version introduces checking for such discrepancies: - non-use of the path parameter or, - a mistake in manually entering its name; - The warning is displayed when the application is launched and NOT in production mode. ```ts const updateUserEndpoint = factory.build({ method: "patch", input: z.object({ id: z.string(), // implies path parameter "id" }), }); const routing: Routing = { v1: { user: { ":username": updateUserEndpoint, // path parameter is "username" instead of "id" }, }, }; ``` ```shell warn: The input schema of the endpoint is most likely missing the parameter of the path it is assigned to. { method: 'patch', path: '/v1/user/:username', param: 'username' } ``` ### v22.7.0 - Technical release in connection with the implementation of workspaces into the project architecture. ### v22.6.0 - Feature: pulling examples up from the object schema properties: - When describing I/O schemas for generating `Documentation` the examples used to work properly only when assigned to the top level (`z.object().example()`), especially complex scenarios involving path parameters and middlewares; - This version supports examples assigned to the individual properties on the I/O object schemas; - It makes the syntax more readable and fixes the issue when example is only set for a path parameter. ```ts const before = factory.build({ input: z .object({ key: z.string(), }) .example({ key: "1234-5678-90", }), }); const after = factory.build({ input: z.object({ key: z.string().example("1234-5678-90"), }), }); ``` ### v22.5.0 - Feature: `defaultResultHandler` sets headers from `HttpError`: - If you `throw createHttpError(400, "message", { headers })` those `headers` go to the negative response. - Feature: Ability to respond with status code `405` (Method not allowed) to requests having wrong method: - Previously, in all cases where the method and route combination was not defined, the response had status code `404`; - For situations where a known route does not support the method being used, there is a more appropriate code `405`: - See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405 for details; - You can activate this feature by setting the new `wrongMethodBehavior` config option `405` (default: `404`). ```ts import { createConfig } from "express-zod-api"; createConfig({ wrongMethodBehavior: 405 }); ``` ### v22.4.2 - Excluded 41 response-only headers from the list of well-known ones used to depict request params in Documentation. ### v22.4.1 - Fixed a bug that could lead to duplicate properties in generated client types: - If the middleware and/or endpoint schemas had the same property, it was duplicated by Integration. - The issue was introduced in [v20.15.3](#v20153) and reported by [@bobgubko](https://github.com/bobgubko). ```ts // reproduction factory .addMiddleware({ input: z.object({ query: z.string() }), // ... }) .build({ input: z.object({ query: z.string() }), // ... }); ``` ```ts type Before = { query: string; query: string; // <— bug #2352 }; type After = { query: string; }; ``` ### v22.4.0 - Feat: ability to supply extra data to a custom implementation of the generated client: - You can instantiate the client class with an implementation accepting an optional context of your choice; - The public `.provide()` method can now accept an additional argument having the type of that context; - The problem on missing such ability was reported by [@LucWag](https://github.com/LucWag). ```ts import { Client, Implementation } from "./generated-client.ts"; interface MyCtx { extraKey: string; } const implementation: Implementation<MyCtx> = async ( method, path, params, ctx, // ctx is optional MyCtx ) => {}; const client = new Client(implementation); client.provide("get /v1/user/retrieve", { id: "10" }, { extraKey: "123456" }); ``` ### v22.3.1 - Fixed issue on emitting server-sent events (SSE), introduced in v21.5.0: - Emitting SSE failed due to internal error `flush is not a function` having `compression` disabled in config; - The `.flush()` method of `response` is a feature of `compression` (optional peer dependency); - It is required to call the method when `compression` is enabled; - This version fixes the issue by calling the method conditionally; - This bug was reported by [@bobgubko](https://github.com/bobgubko). ### v22.3.0 - Feat: `Subscription` class for consuming Server-sent events: - The `Integration` can now also generate a frontend helper class `Subscription` to ease SSE support; - The new class establishes an `EventSource` instance and exposes it as the public `source` property; - The class also provides the public `on` method for your typed listeners; - You can configure the generated class name using `subscriptionClassName` option (default: `Subscription`); - The feature is only applicable to the `variant` option set to `client` (default). ```ts import { Subscription } from "./client.ts"; // the generated file new Subscription("get /v1/events/stream", {}).on("time", (time) => {}); ``` ### v22.2.0 - Feat: detecting headers from `Middleware::security` declarations: - When `headers` are enabled within `inputSources` of config, the `Documentation` generator can now identify them among other inputs additionally by using the security declarations of middlewares attached to an `Endpoint`; - This approach enables handling of custom headers without `x-` prefix. ```ts const authMiddleware = new Middleware({ security: { type: "header", name: "token" }, }); ``` ### v22.1.1 - This version contains an important technical simplification of routines related to processing of `security` declarations of the used `Middleware` when generating API `Documentation`. - No changes to the operation are expected. This refactoring is required for a feature that will be released later. ### v22.1.0 - Feat: ability to configure the generated client class name: - New option `clientClassName` for `Integration::constructor()` argument, default: `Client`. - Feat: default implementation for the generated client: - The argument of the generated client class constructor became optional; - The `Implementation` previously suggested as an example (using `fetch`) became the one used by default; - You may no longer need to write the implementation if the default one suits your needs. ```ts import { Integration } from "express-zod-api"; new Integration({ clientClassName: "FancyClient" }); ``` ```ts import { FancyClient } from "./generated-client.ts"; const client = new FancyClient(/* optional implementation */); ``` ### v22.0.0 - Minimum supported Node versions: 20.9.0 and 22.0.0: - Node 18 is no longer supported; its end of life is April 30, 2025. - `BuiltinLogger::profile()` behavior changed for picoseconds: expressing them through nanoseconds; - Feature: handling all (not just `x-` prefixed) headers as an input source (when enabled): - Behavior changed for `headers` inside `inputSources` config option: all headers are addressed to the `input` object; - This change is motivated by the deprecation of `x-` prefixed headers; - Since the order inside `inputSources` matters, consider moving `headers` to the first place to avoid overwrites; - The generated `Documentation` recognizes both `x-` prefixed inputs and well-known headers listed on IANA.ORG; - You can customize that behavior by using the new option `isHeader` of the `Documentation::constructor()`. - The `splitResponse` property on the `Integration::constructor()` argument is removed; - Changes to the client code generated by `Integration`: - The class name changed from `ExpressZodAPIClient` to just `Client`; - The overload of the `Client::provide()` having 3 arguments and the `Provider` type are removed; - The public `jsonEndpoints` const is removed — use the `content-type` header of an actual response instead; - The public type `MethodPath` is removed — use the `Request` type instead. - The approach to tagging endpoints changed: - The `tags` property moved from the argument of `createConfig()` to `Documentation::constructor()`; - The overload of `EndpointsFactory::constructor()` accepting `config` property is removed; - The argument of `EventStreamFactory::constructor()` is now the events map (formerly assigned to `events` property); - Tags should be declared as the keys of the augmented interface `TagOverrides` instead; - The public method `Endpoint::getSecurity()` now returns an array; - Consider the automated migration using the built-in ESLint rule. ```js // eslint.config.mjs — minimal ESLint 9 config to apply migrations automatically using "eslint --fix" import parser from "@typescript-eslint/parser"; import migration from "express-zod-api/migration"; export default [ { languageOptions: { parser }, plugins: { migration } }, { files: ["**/*.ts"], rules: { "migration/v22": "error" } }, ]; ``` ```diff createConfig({ - tags: {}, inputSources: { - get: ["query", "headers"] // if you have headers on last place + get: ["headers", "