diff --git a/.changeset/fluffy-beans-clean.md b/.changeset/fluffy-beans-clean.md new file mode 100644 index 000000000..746f8f066 --- /dev/null +++ b/.changeset/fluffy-beans-clean.md @@ -0,0 +1,30 @@ +--- +'@hey-api/openapi-ts': minor +--- + +fix: require sdk.transformer to use generated transformers + +### Added `sdk.transformer` option + +When generating SDKs, you now have to specify `transformer` in order to modify response data. By default, adding `@hey-api/transformers` to your plugins will only produce additional output. To preserve the previous functionality, set `sdk.transformer` to `true`. + +```js +import { defaultPlugins } from '@hey-api/openapi-ts'; + +export default { + client: '@hey-api/client-fetch', + input: 'path/to/openapi.json', + output: 'src/client', + plugins: [ + ...defaultPlugins, + { + dates: true, + name: '@hey-api/transformers', + }, + { + name: '@hey-api/sdk', + transformer: true, // [!code ++] + }, + ], +}; +``` diff --git a/.changeset/lazy-moose-retire.md b/.changeset/lazy-moose-retire.md new file mode 100644 index 000000000..3cebbccc7 --- /dev/null +++ b/.changeset/lazy-moose-retire.md @@ -0,0 +1,5 @@ +--- +'@hey-api/docs': patch +--- + +docs: add validators page diff --git a/.changeset/polite-houses-roll.md b/.changeset/polite-houses-roll.md new file mode 100644 index 000000000..afb94b33b --- /dev/null +++ b/.changeset/polite-houses-roll.md @@ -0,0 +1,6 @@ +--- +'@hey-api/client-axios': patch +'@hey-api/client-fetch': patch +--- + +fix: add responseValidator option diff --git a/.changeset/shiny-birds-remain.md b/.changeset/shiny-birds-remain.md new file mode 100644 index 000000000..2f4f864c4 --- /dev/null +++ b/.changeset/shiny-birds-remain.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +feat: Zod plugin generates response schemas diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd270b20b..bfca91007 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,5 +50,5 @@ jobs: run: pnpm test:e2e - name: Publish previews - if: matrix.node-version == '21.x' && matrix.os == 'ubuntu-latest' + if: matrix.node-version == '22.x' && matrix.os == 'ubuntu-latest' run: pnpx pkg-pr-new publish --compact --pnpm './packages/*' diff --git a/README.md b/README.md index 19d09cd5d..b7beb9758 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,6 @@ Love Hey API? Please consider becoming a [sponsor](https://github.com/sponsors/h Automatically update your code when the APIs it depends on change. [Find out more](https://heyapi.dev/openapi-ts/integrations.html). -## Migrating from OpenAPI Typescript Codegen? +## Migration Guides -Please read our [migration guide](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). - -## Contributing - -Want to get involved? Please refer to the [contributing guide](https://heyapi.dev/contributing.html). +[OpenAPI Typescript Codegen](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). diff --git a/docs/.vitepress/config/en.ts b/docs/.vitepress/config/en.ts index ebc473862..97dd37c20 100644 --- a/docs/.vitepress/config/en.ts +++ b/docs/.vitepress/config/en.ts @@ -50,6 +50,17 @@ export default defineConfig({ link: '/openapi-ts/clients', text: 'Clients', }, + { + collapsed: true, + items: [ + { + link: '/openapi-ts/validators/zod', + text: 'Zod', + }, + ], + link: '/openapi-ts/validators', + text: 'Validators', + }, { link: '/openapi-ts/transformers', text: 'Transformers', @@ -71,10 +82,6 @@ export default defineConfig({ link: '/openapi-ts/tanstack-query', text: 'TanStack Query', }, - { - link: '/openapi-ts/zod', - text: 'Zod', - }, ], text: 'Plugins', }, diff --git a/docs/index.md b/docs/index.md index 1efa325a6..6a3a9c17a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -67,7 +67,7 @@ features:
-### Migration guides +### Migration Guides - [OpenAPI TypeScript Codegen](/openapi-ts/migrating#openapi-typescript-codegen) diff --git a/docs/openapi-ts/clients.md b/docs/openapi-ts/clients.md index ff13c0040..7f9bd8eaf 100644 --- a/docs/openapi-ts/clients.md +++ b/docs/openapi-ts/clients.md @@ -15,6 +15,7 @@ We all send HTTP requests in a slightly different way. Hey API doesn't force you - seamless integration with `@hey-api/openapi-ts` ecosystem - type-safe response data and errors +- response data validation and transformation - access to the original request and response - granular request and response customization options - minimal learning curve thanks to extending the underlying technology diff --git a/docs/openapi-ts/migrating.md b/docs/openapi-ts/migrating.md index c1b50d069..4428ce4d3 100644 --- a/docs/openapi-ts/migrating.md +++ b/docs/openapi-ts/migrating.md @@ -27,6 +27,33 @@ This config option is deprecated and will be removed in favor of [clients](./cli This config option is deprecated and will be removed. +## v0.60.0 + +### Added `sdk.transformer` option + +When generating SDKs, you now have to specify `transformer` in order to modify response data. By default, adding `@hey-api/transformers` to your plugins will only produce additional output. To preserve the previous functionality, set `sdk.transformer` to `true`. + +```js +import { defaultPlugins } from '@hey-api/openapi-ts'; + +export default { + client: '@hey-api/client-fetch', + input: 'path/to/openapi.json', + output: 'src/client', + plugins: [ + ...defaultPlugins, + { + dates: true, + name: '@hey-api/transformers', + }, + { + name: '@hey-api/sdk', + transformer: true, // [!code ++] + }, + ], +}; +``` + ## v0.59.0 ### Added `logs.level` option diff --git a/docs/openapi-ts/plugins.md b/docs/openapi-ts/plugins.md index 06bfae7b0..485173d71 100644 --- a/docs/openapi-ts/plugins.md +++ b/docs/openapi-ts/plugins.md @@ -26,7 +26,7 @@ These plugins help reduce boilerplate associated with third-party dependencies. - [`@tanstack/svelte-query`](/openapi-ts/tanstack-query) - TanStack Query functions and query keys - [`@tanstack/vue-query`](/openapi-ts/tanstack-query) - TanStack Query functions and query keys - [`fastify`](/openapi-ts/fastify) - TypeScript interface for Fastify route handlers -- [`zod`](/openapi-ts/zod) - Zod schemas to validate your data +- [`zod`](/openapi-ts/validators/zod) - Zod schemas to validate your data ## Community diff --git a/docs/openapi-ts/transformers.md b/docs/openapi-ts/transformers.md index 13e06a752..3023fc5aa 100644 --- a/docs/openapi-ts/transformers.md +++ b/docs/openapi-ts/transformers.md @@ -1,6 +1,6 @@ --- title: Transformers -description: Learn about transforming payloads with @hey-api/openapi-ts. +description: Learn about transforming data with @hey-api/openapi-ts. --- # Transformers @@ -27,6 +27,46 @@ Transformers handle only the most common scenarios. Some of the known limitation If your data isn't being transformed as expected, we encourage you to leave feedback on [GitHub](https://github.com/hey-api/openapi-ts/issues). +## Configuration + +To generate transformers, add `@hey-api/transformers` to your plugins. + +```js +import { defaultPlugins } from '@hey-api/openapi-ts'; + +export default { + client: '@hey-api/client-fetch', + input: 'path/to/openapi.json', + output: 'src/client', + plugins: [ + ...defaultPlugins, + '@hey-api/transformers', // [!code ++] + ], +}; +``` + +## SDKs + +To automatically transform response data in your SDKs, set `transformer` to `true`. + +```js +import { defaultPlugins } from '@hey-api/openapi-ts'; + +export default { + client: '@hey-api/client-fetch', + input: 'path/to/openapi.json', + output: 'src/client', + plugins: [ + ...defaultPlugins, + '@hey-api/transformers', + { + name: '@hey-api/sdk', // [!code ++] + transformer: true, // [!code ++] + }, + ], +}; +``` + ## Dates To convert date strings into `Date` objects, use the `dates` configuration option. diff --git a/docs/openapi-ts/validators.md b/docs/openapi-ts/validators.md new file mode 100644 index 000000000..c1fe1bfa2 --- /dev/null +++ b/docs/openapi-ts/validators.md @@ -0,0 +1,62 @@ +--- +title: Validators +description: Learn about validating data with @hey-api/openapi-ts. +--- + +# Validators + +There are times when you cannot blindly trust the server to return the correct data. You might be working on a critical application where any mistakes would be costly, or you're simply dealing with a legacy or undocumented system. + +Hey API clients support validating responses so you can rest assured that you're working with the correct data. + +## Available Validators + +- [Zod](/openapi-ts/validators/zod) +- [Ajv](https://ajv.js.org/) Soon +- [Joi](https://joi.dev/) Soon +- [Yup](https://github.com/jquense/yup) Soon + +If you'd like Hey API to support your validator, let us know by [opening an issue](https://github.com/hey-api/openapi-ts/issues). + +## Installation + +There are two ways to generate validators. If you only need response validation in your SDKs, set `sdk.validator` to the desired value. For a more granular approach, add the validator to your plugins and set `sdk.validator` to `true`. + +::: code-group + +```js [sdk] +export default { + client: '@hey-api/client-fetch', + input: 'path/to/openapi.json', + output: 'src/client', + plugins: [ + { + name: '@hey-api/sdk', + validator: 'zod', // [!code ++] + }, + ], +}; +``` + +```js [validator] +export default { + client: '@hey-api/client-fetch', + input: 'path/to/openapi.json', + output: 'src/client', + plugins: [ + { + name: '@hey-api/sdk', + validator: true, // [!code ++] + }, + { + name: 'zod', // [!code ++] + // other options + }, + ], +}; +``` + +::: + + + diff --git a/docs/openapi-ts/zod.md b/docs/openapi-ts/validators/zod.md similarity index 74% rename from docs/openapi-ts/zod.md rename to docs/openapi-ts/validators/zod.md index 476201ed5..d345a76be 100644 --- a/docs/openapi-ts/zod.md +++ b/docs/openapi-ts/validators/zod.md @@ -45,6 +45,28 @@ export default { You can now generate Zod artifacts. 🎉 +## SDKs + +To automatically validate response data in your SDKs, set `validator` to `true`. + +```js +import { defaultPlugins } from '@hey-api/openapi-ts'; + +export default { + client: '@hey-api/client-fetch', + input: 'path/to/openapi.json', + output: 'src/client', + plugins: [ + ...defaultPlugins, + 'zod', + { + name: '@hey-api/sdk', // [!code ++] + validator: true, // [!code ++] + }, + ], +}; +``` + ## Output The Zod plugin will generate the following artifacts, depending on the input specification. @@ -53,5 +75,5 @@ The Zod plugin will generate the following artifacts, depending on the input spe More information will be provided as we finalize the plugin. - - + + diff --git a/packages/client-axios/README.md b/packages/client-axios/README.md index f7df5f272..ebf98fb6f 100644 --- a/packages/client-axios/README.md +++ b/packages/client-axios/README.md @@ -10,6 +10,7 @@ - seamless integration with `@hey-api/openapi-ts` ecosystem - type-safe response data and errors +- response data validation and transformation - access to the original request and response - granular request and response customization options - minimal learning curve thanks to extending the underlying technology @@ -27,10 +28,6 @@ Love Hey API? Please consider becoming a [sponsor](https://github.com/sponsors/h Automatically update your code when the APIs it depends on change. [Find out more](https://heyapi.dev/openapi-ts/integrations.html). -## Migrating from OpenAPI Typescript Codegen? +## Migration Guides -Please read our [migration guide](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). - -## Contributing - -Want to get involved? Please refer to the [contributing guide](https://heyapi.dev/contributing.html). +[OpenAPI Typescript Codegen](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). diff --git a/packages/client-axios/src/index.ts b/packages/client-axios/src/index.ts index 55c4970c8..3a377c8ba 100644 --- a/packages/client-axios/src/index.ts +++ b/packages/client-axios/src/index.ts @@ -64,8 +64,14 @@ export const createClient = (config: Config): Client => { let { data } = response; - if (opts.responseType === 'json' && opts.responseTransformer) { - data = await opts.responseTransformer(data); + if (opts.responseType === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } } return { diff --git a/packages/client-axios/src/types.ts b/packages/client-axios/src/types.ts index 56cacb235..927d791f5 100644 --- a/packages/client-axios/src/types.ts +++ b/packages/client-axios/src/types.ts @@ -83,11 +83,16 @@ export interface Config */ querySerializer?: QuerySerializer | QuerySerializerOptions; /** - * A function for transforming response data before it's returned to the - * caller function. This is an ideal place to post-process server data, - * e.g. convert date ISO strings into native Date objects. + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. */ responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; /** * Throw an error instead of returning it in the response? * @@ -97,7 +102,7 @@ export interface Config } export interface RequestOptions< - ThrowOnError extends boolean = false, + ThrowOnError extends boolean = boolean, Url extends string = string, > extends Config { /** diff --git a/packages/client-fetch/README.md b/packages/client-fetch/README.md index 38926cc82..22cb3a493 100644 --- a/packages/client-fetch/README.md +++ b/packages/client-fetch/README.md @@ -10,6 +10,7 @@ - seamless integration with `@hey-api/openapi-ts` ecosystem - type-safe response data and errors +- response data validation and transformation - access to the original request and response - granular request and response customization options - minimal learning curve thanks to extending the underlying technology @@ -27,10 +28,6 @@ Love Hey API? Please consider becoming a [sponsor](https://github.com/sponsors/h Automatically update your code when the APIs it depends on change. [Find out more](https://heyapi.dev/openapi-ts/integrations.html). -## Migrating from OpenAPI Typescript Codegen? +## Migration Guides -Please read our [migration guide](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). - -## Contributing - -Want to get involved? Please refer to the [contributing guide](https://heyapi.dev/contributing.html). +[OpenAPI Typescript Codegen](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). diff --git a/packages/client-fetch/src/index.ts b/packages/client-fetch/src/index.ts index 2883b08cc..1985110ee 100644 --- a/packages/client-fetch/src/index.ts +++ b/packages/client-fetch/src/index.ts @@ -106,8 +106,14 @@ export const createClient = (config: Config = {}): Client => { : opts.parseAs) ?? 'json'; let data = await response[parseAs](); - if (parseAs === 'json' && opts.responseTransformer) { - data = await opts.responseTransformer(data); + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } } return { diff --git a/packages/client-fetch/src/types.ts b/packages/client-fetch/src/types.ts index 0c914db6c..4049d690b 100644 --- a/packages/client-fetch/src/types.ts +++ b/packages/client-fetch/src/types.ts @@ -88,11 +88,16 @@ export interface Config */ querySerializer?: QuerySerializer | QuerySerializerOptions; /** - * A function for transforming response data before it's returned to the - * caller function. This is an ideal place to post-process server data, - * e.g. convert date ISO strings into native Date objects. + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. */ responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; /** * Throw an error instead of returning it in the response? * diff --git a/packages/openapi-ts/README.md b/packages/openapi-ts/README.md index 19d09cd5d..b7beb9758 100644 --- a/packages/openapi-ts/README.md +++ b/packages/openapi-ts/README.md @@ -27,10 +27,6 @@ Love Hey API? Please consider becoming a [sponsor](https://github.com/sponsors/h Automatically update your code when the APIs it depends on change. [Find out more](https://heyapi.dev/openapi-ts/integrations.html). -## Migrating from OpenAPI Typescript Codegen? +## Migration Guides -Please read our [migration guide](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). - -## Contributing - -Want to get involved? Please refer to the [contributing guide](https://heyapi.dev/contributing.html). +[OpenAPI Typescript Codegen](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index ceb391a2a..cf8ae8215 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -11,7 +11,11 @@ import type { IRContext } from './ir/context'; import { parseExperimental, parseLegacy } from './openApi'; import type { ClientPlugins, UserPlugins } from './plugins'; import { defaultPluginConfigs } from './plugins'; -import type { DefaultPluginConfigs, PluginNames } from './plugins/types'; +import type { + DefaultPluginConfigs, + PluginContext, + PluginNames, +} from './plugins/types'; import type { Client } from './types/client'; import type { ClientConfig, @@ -180,44 +184,88 @@ const getOutput = (userConfig: ClientConfig): Config['output'] => { return output; }; -const getPluginOrder = ({ +const getPluginsConfig = ({ pluginConfigs, userPlugins, + userPluginsConfig, }: { pluginConfigs: DefaultPluginConfigs; userPlugins: ReadonlyArray; -}): Config['pluginOrder'] => { + userPluginsConfig: Config['plugins']; +}): Pick => { const circularReferenceTracker = new Set(); - const visitedNodes = new Set(); + const pluginOrder = new Set(); + const plugins: Config['plugins'] = {}; const dfs = (name: PluginNames) => { if (circularReferenceTracker.has(name)) { throw new Error(`Circular reference detected at '${name}'`); } - if (!visitedNodes.has(name)) { + if (!pluginOrder.has(name)) { circularReferenceTracker.add(name); const pluginConfig = pluginConfigs[name]; - if (!pluginConfig) { throw new Error( `🚫 unknown plugin dependency "${name}" - do you need to register a custom plugin with this name?`, ); } - for (const dependency of pluginConfig._dependencies || []) { - dfs(dependency); + const defaultOptions = defaultPluginConfigs[name]; + const userOptions = userPluginsConfig[name]; + if (userOptions && defaultOptions) { + const nativePluginOption = Object.keys(userOptions).find((key) => + key.startsWith('_'), + ); + if (nativePluginOption) { + throw new Error( + `🚫 cannot register plugin "${name}" - attempting to override a native plugin option "${nativePluginOption}"`, + ); + } } - for (const dependency of pluginConfig._optionalDependencies || []) { - if (userPlugins.includes(dependency)) { - dfs(dependency); - } + const config = { + _dependencies: [], + ...defaultOptions, + ...userOptions, + }; + + if (config._infer) { + const context: PluginContext = { + ensureDependency: (dependency) => { + if ( + typeof dependency === 'string' && + !config._dependencies.includes(dependency) + ) { + config._dependencies = [...config._dependencies, dependency]; + } + }, + pluginByTag: (tag) => { + for (const userPlugin of userPlugins) { + const defaultConfig = defaultPluginConfigs[userPlugin]; + if ( + defaultConfig && + defaultConfig._tags?.includes(tag) && + userPlugin !== name + ) { + return userPlugin; + } + } + }, + }; + config._infer(config, context); + } + + for (const dependency of config._dependencies) { + dfs(dependency); } circularReferenceTracker.delete(name); - visitedNodes.add(name); + pluginOrder.add(name); + + // @ts-expect-error + plugins[name] = config; } }; @@ -225,7 +273,10 @@ const getPluginOrder = ({ dfs(name); } - return Array.from(visitedNodes); + return { + pluginOrder: Array.from(pluginOrder), + plugins, + }; }; const getPlugins = ( @@ -248,42 +299,14 @@ const getPlugins = ( }) .filter(Boolean); - const pluginOrder = getPluginOrder({ + return getPluginsConfig({ pluginConfigs: { ...userPluginsConfig, ...defaultPluginConfigs, }, userPlugins, + userPluginsConfig, }); - - const plugins = pluginOrder.reduce( - (result, name) => { - const defaultOptions = defaultPluginConfigs[name]; - const userOptions = userPluginsConfig[name]; - if (userOptions && defaultOptions) { - const nativePluginOption = Object.keys(userOptions).find((key) => - key.startsWith('_'), - ); - if (nativePluginOption) { - throw new Error( - `🚫 cannot register plugin "${userOptions.name}" - attempting to override a native plugin option "${nativePluginOption}"`, - ); - } - } - // @ts-expect-error - result[name] = { - ...defaultOptions, - ...userOptions, - }; - return result; - }, - {} as Config['plugins'], - ); - - return { - pluginOrder, - plugins, - }; }; const getSpec = async ({ config }: { config: Config }) => { diff --git a/packages/openapi-ts/src/ir/operation.ts b/packages/openapi-ts/src/ir/operation.ts index 8be67d406..26dfde777 100644 --- a/packages/openapi-ts/src/ir/operation.ts +++ b/packages/openapi-ts/src/ir/operation.ts @@ -85,9 +85,21 @@ export const statusCodeToGroup = ({ }; interface OperationResponsesMap { + /** + * A deduplicated union of all error types. Unknown types are omitted. + */ error?: IRSchemaObject; + /** + * An object containing a map of status codes for each error type. + */ errors?: IRSchemaObject; + /** + * A deduplicated union of all response types. Unknown types are omitted. + */ response?: IRSchemaObject; + /** + * An object containing a map of status codes for each response type. + */ responses?: IRSchemaObject; } diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts index ca0f51c2a..ca0acfa77 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts @@ -7,7 +7,27 @@ export const defaultConfig: Plugin.Config = { _dependencies: ['@hey-api/typescript'], _handler: handler, _handlerLegacy: handlerLegacy, - _optionalDependencies: ['@hey-api/transformers'], + _infer: (config, context) => { + if (config.transformer) { + if (typeof config.transformer === 'boolean') { + config.transformer = context.pluginByTag( + 'transformer', + ) as unknown as typeof config.transformer; + } + + context.ensureDependency(config.transformer); + } + + if (config.validator) { + if (typeof config.validator === 'boolean') { + config.validator = context.pluginByTag( + 'validator', + ) as unknown as typeof config.validator; + } + + context.ensureDependency(config.validator); + } + }, asClass: false, auth: true, name: '@hey-api/sdk', diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts index d59474702..faec0a5d8 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts @@ -11,55 +11,17 @@ import { } from '../../../ir/operation'; import { escapeComment } from '../../../utils/escape'; import { getServiceName } from '../../../utils/postprocess'; -import { irRef } from '../../../utils/ref'; -import { stringCase } from '../../../utils/stringCase'; import { transformServiceName } from '../../../utils/transform'; +import { operationIrRef } from '../../shared/utils/ref'; import type { Plugin } from '../../types'; -import { operationTransformerIrRef } from '../transformers/plugin'; +import { zodId } from '../../zod/plugin'; +import { + operationTransformerIrRef, + transformersId, +} from '../transformers/plugin'; import { serviceFunctionIdentifier } from './plugin-legacy'; import type { Config } from './types'; -interface OperationIRRef { - /** - * Operation ID - */ - id: string; -} - -export const operationIrRef = ({ - id, - type, -}: OperationIRRef & { - type: 'data' | 'error' | 'errors' | 'response' | 'responses'; -}): string => { - let affix = ''; - switch (type) { - case 'data': - affix = 'Data'; - break; - case 'error': - // error union - affix = 'Error'; - break; - case 'errors': - // errors map - affix = 'Errors'; - break; - case 'response': - // response union - affix = 'Response'; - break; - case 'responses': - // responses map - affix = 'Responses'; - break; - } - return `${irRef}${stringCase({ - case: 'PascalCase', - value: id, - })}-${affix}`; -}; - export const operationOptionsType = ({ importedType, throwOnError, @@ -322,20 +284,74 @@ const operationStatements = ({ } } - const fileTransformers = context.file({ id: 'transformers' }); - if (fileTransformers) { - const identifier = fileTransformers.identifier({ - $ref: operationTransformerIrRef({ id: operation.id, type: 'response' }), + if (plugin.transformer === '@hey-api/transformers') { + const identifierTransformer = context + .file({ id: transformersId })! + .identifier({ + $ref: operationTransformerIrRef({ id: operation.id, type: 'response' }), + namespace: 'value', + }); + + if (identifierTransformer.name) { + file.import({ + module: file.relativePathToFile({ + context, + id: transformersId, + }), + name: identifierTransformer.name, + }); + + requestOptions.push({ + key: 'responseTransformer', + value: identifierTransformer.name, + }); + } + } + + if (plugin.validator === 'zod') { + const identifierSchema = context.file({ id: zodId })!.identifier({ + $ref: operationIrRef({ + case: 'camelCase', + id: operation.id, + type: 'response', + }), namespace: 'value', }); - if (identifier.name) { + + if (identifierSchema.name) { file.import({ - module: file.relativePathToFile({ context, id: 'transformers' }), - name: identifier.name, + module: file.relativePathToFile({ + context, + id: zodId, + }), + name: identifierSchema.name, }); + requestOptions.push({ - key: 'responseTransformer', - value: identifier.name, + key: 'responseValidator', + value: compiler.arrowFunction({ + async: true, + parameters: [ + { + name: 'data', + }, + ], + statements: [ + compiler.returnStatement({ + expression: compiler.awaitExpression({ + expression: compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: compiler.identifier({ + text: identifierSchema.name, + }), + name: compiler.identifier({ text: 'parseAsync' }), + }), + parameters: [compiler.identifier({ text: 'data' })], + }), + }), + }), + ], + }), }); } } diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts index b2de7af43..406dde3c6 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts @@ -77,4 +77,32 @@ export interface Config extends Plugin.Name<'@hey-api/sdk'> { * @default '{{name}}Service' */ serviceNameBuilder?: string; + /** + * Transform response data before returning. This is useful if you want to + * convert for example ISO strings into Date objects. However, transformation + * adds runtime overhead, so it's not recommended to use unless necessary. + * + * You can customize the selected transformer output through its plugin. You + * can also set `transformer` to `true` to automatically choose the + * transformer from your defined plugins. + * + * @default false + */ + transformer?: '@hey-api/transformers' | boolean; + /** + * **This feature works only with the experimental parser** + * + * Validate response data against schema before returning. This is useful + * if you want to ensure the response conforms to a desired shape. However, + * validation adds runtime overhead, so it's not recommended to use unless + * absolutely necessary. + * + * Ensure you have declared the selected library as a dependency to avoid + * errors. You can customize the selected validator output through its + * plugin. You can also set `validator` to `true` to automatically choose + * the validator from your defined plugins. + * + * @default false + */ + validator?: 'zod' | boolean; } diff --git a/packages/openapi-ts/src/plugins/@hey-api/transformers/config.ts b/packages/openapi-ts/src/plugins/@hey-api/transformers/config.ts index e5d980d6e..3ec63394d 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/transformers/config.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/transformers/config.ts @@ -7,6 +7,7 @@ export const defaultConfig: Plugin.Config = { _dependencies: ['@hey-api/typescript'], _handler: handler, _handlerLegacy: handlerLegacy, + _tags: ['transformer'], dates: true, name: '@hey-api/transformers', output: 'transformers', diff --git a/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts index 647e90b12..49f84f031 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts @@ -6,8 +6,8 @@ import type { IRSchemaObject } from '../../../ir/ir'; import { operationResponsesMap } from '../../../ir/operation'; import { irRef } from '../../../utils/ref'; import { stringCase } from '../../../utils/stringCase'; +import { operationIrRef } from '../../shared/utils/ref'; import type { Plugin } from '../../types'; -import { operationIrRef } from '../sdk/plugin'; import type { Config } from './types'; interface OperationIRRef { @@ -68,7 +68,7 @@ export const schemaResponseTransformerRef = ({ $ref: string; }): string => schemaIrRef({ $ref, type: 'response' }); -const transformersId = 'transformers'; +export const transformersId = 'transformers'; const dataVariableName = 'data'; const ensureStatements = ( diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts index 65d3918ca..0a5655028 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts @@ -15,8 +15,8 @@ import { irRef, isRefOpenApiComponent } from '../../../utils/ref'; import { digitsRegExp } from '../../../utils/regexp'; import { stringCase } from '../../../utils/stringCase'; import { fieldName } from '../../shared/utils/case'; +import { operationIrRef } from '../../shared/utils/ref'; import type { Plugin } from '../../types'; -import { operationIrRef } from '../sdk/plugin'; import type { Config } from './types'; interface SchemaWithType['type']> diff --git a/packages/openapi-ts/src/plugins/@tanstack/query-core/plugin.ts b/packages/openapi-ts/src/plugins/@tanstack/query-core/plugin.ts index 0f3b3cd8c..b51e190f7 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/query-core/plugin.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/query-core/plugin.ts @@ -16,12 +16,10 @@ import { import { getConfig } from '../../../utils/config'; import { getServiceName } from '../../../utils/postprocess'; import { transformServiceName } from '../../../utils/transform'; -import { - operationIrRef, - operationOptionsType, -} from '../../@hey-api/sdk/plugin'; +import { operationOptionsType } from '../../@hey-api/sdk/plugin'; import { serviceFunctionIdentifier } from '../../@hey-api/sdk/plugin-legacy'; import { schemaToType } from '../../@hey-api/typescript/plugin'; +import { operationIrRef } from '../../shared/utils/ref'; import type { Plugin } from '../../types'; import type { Config as AngularQueryConfig } from '../angular-query-experimental'; import type { Config as ReactQueryConfig } from '../react-query'; diff --git a/packages/openapi-ts/src/plugins/fastify/plugin.ts b/packages/openapi-ts/src/plugins/fastify/plugin.ts index 56d66b960..64d0d5ae1 100644 --- a/packages/openapi-ts/src/plugins/fastify/plugin.ts +++ b/packages/openapi-ts/src/plugins/fastify/plugin.ts @@ -5,7 +5,7 @@ import type { IRContext } from '../../ir/context'; import type { IROperationObject } from '../../ir/ir'; import { operationResponsesMap } from '../../ir/operation'; import { hasParameterGroupObjectRequired } from '../../ir/parameter'; -import { operationIrRef } from '../@hey-api/sdk/plugin'; +import { operationIrRef } from '../shared/utils/ref'; import type { Plugin } from '../types'; import type { Config } from './types'; diff --git a/packages/openapi-ts/src/plugins/shared/utils/ref.ts b/packages/openapi-ts/src/plugins/shared/utils/ref.ts new file mode 100644 index 000000000..59a9788b6 --- /dev/null +++ b/packages/openapi-ts/src/plugins/shared/utils/ref.ts @@ -0,0 +1,46 @@ +import type { StringCase } from '../../../types/config'; +import { irRef } from '../../../utils/ref'; +import { stringCase } from '../../../utils/stringCase'; + +interface OperationIRRef { + /** + * Operation ID + */ + id: string; +} + +export const operationIrRef = ({ + case: _case = 'PascalCase', + id, + type, +}: OperationIRRef & { + readonly case?: StringCase; + type: 'data' | 'error' | 'errors' | 'response' | 'responses'; +}): string => { + let affix = ''; + switch (type) { + case 'data': + affix = 'Data'; + break; + case 'error': + // error union + affix = 'Error'; + break; + case 'errors': + // errors map + affix = 'Errors'; + break; + case 'response': + // response union + affix = 'Response'; + break; + case 'responses': + // responses map + affix = 'Responses'; + break; + } + return `${irRef}${stringCase({ + case: _case, + value: id, + })}-${affix}`; +}; diff --git a/packages/openapi-ts/src/plugins/types.d.ts b/packages/openapi-ts/src/plugins/types.d.ts index c4ca5a357..e818f83cb 100644 --- a/packages/openapi-ts/src/plugins/types.d.ts +++ b/packages/openapi-ts/src/plugins/types.d.ts @@ -3,6 +3,10 @@ import type { OpenApi } from '../openApi'; import type { Client } from '../types/client'; import type { Files } from '../types/utils'; +type OmitUnderscoreKeys = { + [K in keyof T as K extends `_${string}` ? never : K]: T[K]; +}; + export type PluginNames = | '@hey-api/schemas' | '@hey-api/sdk' @@ -16,28 +20,44 @@ export type PluginNames = | 'fastify' | 'zod'; +type PluginTag = 'transformer' | 'validator'; + +export interface PluginContext { + ensureDependency: (name: PluginNames | true) => void; + pluginByTag: (tag: PluginTag) => PluginNames | undefined; +} + interface BaseConfig { // eslint-disable-next-line @typescript-eslint/ban-types name: PluginNames | (string & {}); output?: string; } -interface Dependencies { +interface Meta { /** - * Required dependencies will be always processed, regardless of whether - * a user defines them in their `plugins` config. + * Dependency plugins will be always processed, regardless of whether user + * explicitly defines them in their `plugins` config. */ _dependencies?: ReadonlyArray; /** - * Optional dependencies are not processed unless a user explicitly defines - * them in their `plugins` config. + * Allows overriding config before it's sent to the parser. An example is + * defining `validator` as `true` and the plugin figures out which plugin + * should be used for validation. + */ + _infer?: ( + config: Config & Omit, '_infer'>, + context: PluginContext, + ) => void; + /** + * Optional tags can be used to help with deciding plugin order and inferring + * plugin configuration options. */ - _optionalDependencies?: ReadonlyArray; + _tags?: ReadonlyArray; } export type DefaultPluginConfigs = { [K in PluginNames]: BaseConfig & - Dependencies & { + Meta & { _handler: Plugin.Handler>>; _handlerLegacy: Plugin.LegacyHandler>>; }; @@ -48,7 +68,7 @@ export type DefaultPluginConfigs = { */ export namespace Plugin { export type Config = Config & - Dependencies & { + Meta & { _handler: Plugin.Handler; _handlerLegacy: Plugin.LegacyHandler; }; @@ -65,10 +85,7 @@ export namespace Plugin { plugin: Plugin.Instance; }) => void; - export type Instance = Omit< - Config, - '_dependencies' | '_handler' | '_handlerLegacy' | '_optionalDependencies' - > & + export type Instance = OmitUnderscoreKeys & Pick, 'output'>; /** diff --git a/packages/openapi-ts/src/plugins/zod/config.ts b/packages/openapi-ts/src/plugins/zod/config.ts index 3dc397675..d5e95589b 100644 --- a/packages/openapi-ts/src/plugins/zod/config.ts +++ b/packages/openapi-ts/src/plugins/zod/config.ts @@ -5,6 +5,7 @@ import type { Config } from './types'; export const defaultConfig: Plugin.Config = { _handler: handler, _handlerLegacy: () => {}, + _tags: ['validator'], name: 'zod', output: 'zod', }; diff --git a/packages/openapi-ts/src/plugins/zod/plugin.ts b/packages/openapi-ts/src/plugins/zod/plugin.ts index 75d934e26..5c1cdcf1d 100644 --- a/packages/openapi-ts/src/plugins/zod/plugin.ts +++ b/packages/openapi-ts/src/plugins/zod/plugin.ts @@ -2,10 +2,11 @@ import ts from 'typescript'; import { compiler } from '../../compiler'; import type { IRContext } from '../../ir/context'; -import type { IRSchemaObject } from '../../ir/ir'; +import type { IROperationObject, IRSchemaObject } from '../../ir/ir'; +import { operationResponsesMap } from '../../ir/operation'; import { deduplicateSchema } from '../../ir/schema'; -import { isRefOpenApiComponent } from '../../utils/ref'; import { digitsRegExp } from '../../utils/regexp'; +import { operationIrRef } from '../shared/utils/ref'; import type { Plugin } from '../types'; import type { Config } from './types'; @@ -19,25 +20,26 @@ interface Result { hasCircularReference: boolean; } -const zodId = 'zod'; +export const zodId = 'zod'; // frequently used identifiers const defaultIdentifier = compiler.identifier({ text: 'default' }); +const intersectionIdentifier = compiler.identifier({ text: 'intersection' }); const lazyIdentifier = compiler.identifier({ text: 'lazy' }); +const mergeIdentifier = compiler.identifier({ text: 'merge' }); const optionalIdentifier = compiler.identifier({ text: 'optional' }); const readonlyIdentifier = compiler.identifier({ text: 'readonly' }); +const unionIdentifier = compiler.identifier({ text: 'union' }); const zIdentifier = compiler.identifier({ text: 'z' }); -const nameTransformer = (name: string) => `z${name}`; +const nameTransformer = (name: string) => `z-${name}`; const arrayTypeToZodSchema = ({ context, - namespace, result, schema, }: { context: IRContext; - namespace: Array; result: Result; schema: SchemaWithType<'array'>; }): ts.CallExpression => { @@ -54,7 +56,6 @@ const arrayTypeToZodSchema = ({ parameters: [ unknownTypeToZodSchema({ context, - namespace, schema: { type: 'unknown', }, @@ -68,7 +69,6 @@ const arrayTypeToZodSchema = ({ const itemExpressions = schema.items!.map((item) => schemaToZodSchema({ context, - namespace, result, schema: item, }), @@ -95,7 +95,6 @@ const arrayTypeToZodSchema = ({ parameters: [ unknownTypeToZodSchema({ context, - namespace, schema: { type: 'unknown', }, @@ -142,7 +141,6 @@ const booleanTypeToZodSchema = ({ schema, }: { context: IRContext; - namespace: Array; schema: SchemaWithType<'boolean'>; }) => { if (schema.const !== undefined) { @@ -163,11 +161,9 @@ const booleanTypeToZodSchema = ({ const enumTypeToZodSchema = ({ context, - namespace, schema, }: { context: IRContext; - namespace: Array; schema: SchemaWithType<'enum'>; }): ts.CallExpression => { const enumMembers: Array = []; @@ -186,7 +182,6 @@ const enumTypeToZodSchema = ({ if (!enumMembers.length) { return unknownTypeToZodSchema({ context, - namespace, schema: { type: 'unknown', }, @@ -213,7 +208,6 @@ const neverTypeToZodSchema = ({ schema, }: { context: IRContext; - namespace: Array; schema: SchemaWithType<'never'>; }) => { const expression = compiler.callExpression({ @@ -229,7 +223,6 @@ const nullTypeToZodSchema = ({ schema, }: { context: IRContext; - namespace: Array; schema: SchemaWithType<'null'>; }) => { const expression = compiler.callExpression({ @@ -245,7 +238,6 @@ const numberTypeToZodSchema = ({ schema, }: { context: IRContext; - namespace: Array; schema: SchemaWithType<'number'>; }) => { let numberExpression = compiler.callExpression({ @@ -307,12 +299,10 @@ const numberTypeToZodSchema = ({ const objectTypeToZodSchema = ({ context, - // namespace, result, schema, }: { context: IRContext; - namespace: Array; result: Result; schema: SchemaWithType<'object'>; }) => { @@ -413,7 +403,6 @@ const objectTypeToZodSchema = ({ // name: 'key', // type: schemaToZodSchema({ // context, - // namespace, // schema: // indexPropertyItems.length === 1 // ? indexPropertyItems[0] @@ -444,7 +433,6 @@ const stringTypeToZodSchema = ({ schema, }: { context: IRContext; - namespace: Array; schema: SchemaWithType<'string'>; }) => { let stringExpression = compiler.callExpression({ @@ -539,7 +527,6 @@ const undefinedTypeToZodSchema = ({ schema, }: { context: IRContext; - namespace: Array; schema: SchemaWithType<'undefined'>; }) => { const expression = compiler.callExpression({ @@ -555,7 +542,6 @@ const unknownTypeToZodSchema = ({ schema, }: { context: IRContext; - namespace: Array; schema: SchemaWithType<'unknown'>; }) => { const expression = compiler.callExpression({ @@ -571,7 +557,6 @@ const voidTypeToZodSchema = ({ schema, }: { context: IRContext; - namespace: Array; schema: SchemaWithType<'void'>; }) => { const expression = compiler.callExpression({ @@ -585,12 +570,10 @@ const voidTypeToZodSchema = ({ const schemaTypeToZodSchema = ({ context, - namespace, result, schema, }: { context: IRContext; - namespace: Array; result: Result; schema: IRSchemaObject; }): ts.Expression => { @@ -598,58 +581,49 @@ const schemaTypeToZodSchema = ({ case 'array': return arrayTypeToZodSchema({ context, - namespace, result, schema: schema as SchemaWithType<'array'>, }); case 'boolean': return booleanTypeToZodSchema({ context, - namespace, schema: schema as SchemaWithType<'boolean'>, }); case 'enum': return enumTypeToZodSchema({ context, - namespace, schema: schema as SchemaWithType<'enum'>, }); case 'never': return neverTypeToZodSchema({ context, - namespace, schema: schema as SchemaWithType<'never'>, }); case 'null': return nullTypeToZodSchema({ context, - namespace, schema: schema as SchemaWithType<'null'>, }); case 'number': return numberTypeToZodSchema({ context, - namespace, schema: schema as SchemaWithType<'number'>, }); case 'object': return objectTypeToZodSchema({ context, - namespace, result, schema: schema as SchemaWithType<'object'>, }); case 'string': return stringTypeToZodSchema({ context, - namespace, schema: schema as SchemaWithType<'string'>, }); case 'tuple': // TODO: parser - temporary unknown while not handled return unknownTypeToZodSchema({ context, - namespace, schema: { type: 'unknown', }, @@ -657,41 +631,64 @@ const schemaTypeToZodSchema = ({ // TODO: parser - handle tuple // return tupleTypeToIdentifier({ // context, - // namespace, // schema: schema as SchemaWithType<'tuple'>, // }); case 'undefined': return undefinedTypeToZodSchema({ context, - namespace, schema: schema as SchemaWithType<'undefined'>, }); case 'unknown': return unknownTypeToZodSchema({ context, - namespace, schema: schema as SchemaWithType<'unknown'>, }); case 'void': return voidTypeToZodSchema({ context, - namespace, schema: schema as SchemaWithType<'void'>, }); } }; +const operationToZodSchema = ({ + context, + operation, + result, +}: { + context: IRContext; + operation: IROperationObject; + result: Result; +}) => { + if (operation.responses) { + const { response } = operationResponsesMap(operation); + + if (response) { + schemaToZodSchema({ + $ref: operationIrRef({ + case: 'camelCase', + id: operation.id, + type: 'response', + }), + context, + result, + schema: response, + }); + } + } +}; + const schemaToZodSchema = ({ $ref, context, - // TODO: parser - remove namespace, it's a type plugin construct - namespace = [], result, schema, }: { + /** + * When $ref is supplied, a node will be emitted to the file. + */ $ref?: string; context: IRContext; - namespace?: Array; result: Result; schema: IRSchemaObject; }): ts.Expression => { @@ -703,15 +700,12 @@ const schemaToZodSchema = ({ if ($ref) { result.circularReferenceTracker.add($ref); - // emit nodes only if $ref points to a reusable component - if (isRefOpenApiComponent($ref)) { - identifier = file.identifier({ - $ref, - create: true, - nameTransformer, - namespace: 'value', - }); - } + identifier = file.identifier({ + $ref, + create: true, + nameTransformer, + namespace: 'value', + }); } if (schema.$ref) { @@ -770,46 +764,73 @@ const schemaToZodSchema = ({ } else if (schema.type) { expression = schemaTypeToZodSchema({ context, - namespace, result, schema, }); } else if (schema.items) { - // TODO: parser - temporary unknown while not handled - expression = unknownTypeToZodSchema({ - context, - namespace, - schema: { - type: 'unknown', - }, - }); + schema = deduplicateSchema({ schema }); - // TODO: parser - handle items - // schema = deduplicateSchema({ schema }); - // if (schema.items) { - // const itemTypes = schema.items.map((item) => - // schemaToZodSchema({ - // context, - // namespace, - // schema: item, - // }), - // ); - // expression = - // schema.logicalOperator === 'and' - // ? compiler.typeIntersectionNode({ types: itemTypes }) - // : compiler.typeUnionNode({ types: itemTypes }); - // } else { - // expression = schemaToZodSchema({ - // context, - // namespace, - // schema, - // }); - // } + if (schema.items) { + const itemTypes = schema.items.map((item) => + schemaToZodSchema({ + context, + result, + schema: item, + }), + ); + + if (schema.logicalOperator === 'and') { + const firstSchema = schema.items[0]; + // we want to add an intersection, but not every schema can use the same API. + // if the first item contains another array or not an object, we cannot use + // `.merge()` as that does not exist on `.union()` and non-object schemas. + if ( + firstSchema.logicalOperator === 'or' || + (firstSchema.type && firstSchema.type !== 'object') + ) { + expression = compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: zIdentifier, + name: intersectionIdentifier, + }), + parameters: itemTypes, + }); + } else { + expression = itemTypes[0]; + itemTypes.slice(1).forEach((item) => { + expression = compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: expression!, + name: mergeIdentifier, + }), + parameters: [item], + }); + }); + } + } else { + expression = compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: zIdentifier, + name: unionIdentifier, + }), + parameters: [ + compiler.arrayLiteralExpression({ + elements: itemTypes, + }), + ], + }); + } + } else { + expression = schemaToZodSchema({ + context, + result, + schema, + }); + } } else { // catch-all fallback for failed schemas expression = schemaTypeToZodSchema({ context, - namespace, result, schema: { type: 'unknown', @@ -843,6 +864,7 @@ const schemaToZodSchema = ({ export const handler: Plugin.Handler = ({ context, plugin }) => { const file = context.createFile({ id: zodId, + identifierCase: 'camelCase', path: plugin.output, }); @@ -851,13 +873,18 @@ export const handler: Plugin.Handler = ({ context, plugin }) => { name: 'z', }); - // context.subscribe('operation', ({ operation }) => { - // schemaToZodSchema({ - // $ref, - // context, - // schema, - // }); - // }); + context.subscribe('operation', ({ operation }) => { + const result: Result = { + circularReferenceTracker: new Set(), + hasCircularReference: false, + }; + + operationToZodSchema({ + context, + operation, + result, + }); + }); context.subscribe('schema', ({ $ref, schema }) => { const result: Result = { diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/zod/default/zod.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/zod/default/zod.gen.ts index b0eac6bf3..90a7326d2 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/zod/default/zod.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/zod/default/zod.gen.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; export const z400 = z.string(); -export const zcamelCaseCommentWithBreaks = z.number(); +export const zCamelCaseCommentWithBreaks = z.number(); export const zCommentWithBreaks = z.number(); @@ -26,7 +26,7 @@ export const zSimpleBoolean = z.boolean(); export const zSimpleString = z.string(); -export const zNonAsciiStringæøåÆØÅöôêÊ字符串 = z.string(); +export const zNonAsciiStringæøåÆøÅöôêÊ字符串 = z.string(); export const zSimpleFile = z.string(); @@ -34,7 +34,10 @@ export const zSimpleReference = z.object({ prop: z.string().optional() }); -export const zSimpleStringWithPattern = z.unknown(); +export const zSimpleStringWithPattern = z.union([ + z.string().max(64), + z.null() +]); export const zEnumWithStrings = z.enum([ 'Success', @@ -75,7 +78,7 @@ export const zArrayWithArray = z.array(z.array(z.object({ }))); export const zArrayWithProperties = z.array(z.object({ - '16x16': zcamelCaseCommentWithBreaks.optional(), + '16x16': zCamelCaseCommentWithBreaks.optional(), bar: z.string().optional() })); @@ -120,13 +123,25 @@ export const zModelWithStringError = z.object({ prop: z.string().optional() }); -export const zModel_From_Zendesk = z.string(); +export const zModelFromZendesk = z.string(); export const zModelWithNullableString = z.object({ - nullableProp1: z.unknown().optional(), - nullableRequiredProp1: z.unknown(), - nullableProp2: z.unknown().optional(), - nullableRequiredProp2: z.unknown(), + nullableProp1: z.union([ + z.string(), + z.null() + ]).optional(), + nullableRequiredProp1: z.union([ + z.string(), + z.null() + ]), + nullableProp2: z.union([ + z.string(), + z.null() + ]).optional(), + nullableRequiredProp2: z.union([ + z.string(), + z.null() + ]), 'foo_bar-enum': z.enum([ 'Success', 'Warning', @@ -184,7 +199,10 @@ export const zModelWithReference = z.object({ prop: z.object({ required: z.string(), requiredAndReadOnly: z.string().readonly(), - requiredAndNullable: z.unknown(), + requiredAndNullable: z.union([ + z.string(), + z.null() + ]), string: z.string().optional(), number: z.number().optional(), boolean: z.boolean().optional(), @@ -221,12 +239,29 @@ export const zDeprecatedModel = z.object({ prop: z.string().optional() }); +export const zModelWithCircularReference: z.ZodTypeAny = z.object({ + prop: z.lazy(() => { + return zModelWithCircularReference; + }).optional() +}); + export const zCompositionWithOneOf = z.object({ - propA: z.unknown().optional() + propA: z.union([ + zModelWithString, + zModelWithEnum, + zModelWithArray, + zModelWithDictionary + ]).optional() }); export const zCompositionWithOneOfAnonymous = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.object({ + propA: z.string().optional() + }), + z.string(), + z.number() + ]).optional() }); export const zModelCircle = z.object({ @@ -239,21 +274,48 @@ export const zModelSquare = z.object({ sideLength: z.number().optional() }); -export const zCompositionWithOneOfDiscriminator = z.unknown(); +export const zCompositionWithOneOfDiscriminator = z.union([ + z.object({ + kind: z.string().optional() + }).merge(zModelCircle), + z.object({ + kind: z.string().optional() + }).merge(zModelSquare) +]); export const zCompositionWithAnyOf = z.object({ - propA: z.unknown().optional() + propA: z.union([ + zModelWithString, + zModelWithEnum, + zModelWithArray, + zModelWithDictionary + ]).optional() }); export const zCompositionWithAnyOfAnonymous = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.object({ + propA: z.string().optional() + }), + z.string(), + z.number() + ]).optional() }); export const zCompositionWithNestedAnyAndTypeNull = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.array(z.union([ + zModelWithDictionary, + z.null() + ])), + z.array(z.union([ + zModelWithArray, + z.null() + ])) + ]).optional() }); -export const z3e_num_1Период = z.enum([ +export const z3eNum1Период = z.enum([ 'Bird', 'Dog' ]); @@ -263,31 +325,64 @@ export const zConstValue = z.enum([ ]); export const zCompositionWithNestedAnyOfAndNull = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.array(z.unknown()), + z.null() + ]).optional() }); export const zCompositionWithOneOfAndNullable = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.object({ + boolean: z.boolean().optional() + }), + zModelWithEnum, + zModelWithArray, + zModelWithDictionary, + z.null() + ]).optional() }); export const zCompositionWithOneOfAndSimpleDictionary = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.boolean(), + z.object({}) + ]).optional() }); export const zCompositionWithOneOfAndSimpleArrayDictionary = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.boolean(), + z.object({}) + ]).optional() }); export const zCompositionWithOneOfAndComplexArrayDictionary = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.boolean(), + z.object({}) + ]).optional() }); export const zCompositionWithAllOfAndNullable = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.object({ + boolean: z.boolean().optional() + }).merge(zModelWithEnum).merge(zModelWithArray).merge(zModelWithDictionary), + z.null() + ]).optional() }); export const zCompositionWithAnyOfAndNullable = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.object({ + boolean: z.boolean().optional() + }), + zModelWithEnum, + zModelWithArray, + zModelWithDictionary, + z.null() + ]).optional() }); export const zCompositionBaseModel = z.object({ @@ -295,12 +390,19 @@ export const zCompositionBaseModel = z.object({ lastname: z.string().optional() }); -export const zCompositionExtendedModel = z.unknown(); +export const zCompositionExtendedModel = zCompositionBaseModel.merge(z.object({ + age: z.number(), + firstName: z.string(), + lastname: z.string() +})); export const zModelWithProperties = z.object({ required: z.string(), requiredAndReadOnly: z.string().readonly(), - requiredAndNullable: z.unknown(), + requiredAndNullable: z.union([ + z.string(), + z.null() + ]), string: z.string().optional(), number: z.number().optional(), boolean: z.boolean().optional(), @@ -313,7 +415,20 @@ export const zModelWithProperties = z.object({ }); export const zModelWithNestedProperties = z.object({ - first: z.unknown().readonly() + first: z.union([ + z.object({ + second: z.union([ + z.object({ + third: z.union([ + z.string(), + z.null() + ]).readonly() + }), + z.null() + ]).readonly() + }), + z.null() + ]).readonly() }); export const zModelWithDuplicateProperties = z.object({ @@ -332,9 +447,15 @@ export const zModelWithDuplicateImports = z.object({ propC: zModelWithString.optional() }); -export const zModelThatExtends = z.unknown(); +export const zModelThatExtends = zModelWithString.merge(z.object({ + propExtendsA: z.string().optional(), + propExtendsB: zModelWithString.optional() +})); -export const zModelThatExtendsExtends = z.unknown(); +export const zModelThatExtendsExtends = zModelWithString.merge(zModelThatExtends).merge(z.object({ + propExtendsC: z.string().optional(), + propExtendsD: zModelWithString.optional() +})); export const zModelWithPattern = z.object({ key: z.string().max(64), @@ -356,7 +477,7 @@ export const zFile = z.object({ file: z.string().url().readonly().optional() }); -export const zdefault = z.object({ +export const zDefault = z.object({ name: z.string().optional() }); @@ -388,12 +509,33 @@ export const zModelWithAdditionalPropertiesEqTrue = z.object({ }); export const zNestedAnyOfArraysNullable = z.object({ - nullableArray: z.unknown().optional() + nullableArray: z.union([ + z.array(z.unknown()), + z.null() + ]).optional() }); -export const zCompositionWithOneOfAndProperties = z.unknown(); +export const zCompositionWithOneOfAndProperties = z.intersection(z.union([ + z.object({ + foo: z.unknown() + }), + z.object({ + bar: zNonAsciiStringæøåÆøÅöôêÊ字符串 + }) +]), z.object({ + baz: z.union([ + z.number().gte(0), + z.null() + ]), + qux: z.number().gte(0) +})); -export const zNullableObject = z.unknown(); +export const zNullableObject = z.union([ + z.object({ + foo: z.string().optional() + }), + z.null() +]); export const zCharactersInDescription = z.string(); @@ -401,7 +543,35 @@ export const zModelWithNullableObject = z.object({ data: zNullableObject.optional() }); -export const zModelWithOneOfEnum = z.unknown(); +export const zModelWithOneOfEnum = z.union([ + z.object({ + foo: z.enum([ + 'Bar' + ]) + }), + z.object({ + foo: z.enum([ + 'Baz' + ]) + }), + z.object({ + foo: z.enum([ + 'Qux' + ]) + }), + z.object({ + content: z.string().datetime(), + foo: z.enum([ + 'Quux' + ]) + }), + z.object({ + content: z.unknown(), + foo: z.enum([ + 'Corge' + ]) + }) +]); export const zModelWithNestedArrayEnumsDataFoo = z.enum([ 'foo', @@ -453,7 +623,16 @@ export const zModelWithBackticksInDescription = z.object({ template: z.string().optional() }); -export const zModelWithOneOfAndProperties = z.unknown(); +export const zModelWithOneOfAndProperties = z.intersection(z.union([ + z.unknown(), + zNonAsciiStringæøåÆøÅöôêÊ字符串 +]), z.object({ + baz: z.union([ + z.number().gte(0), + z.null() + ]), + qux: z.number().gte(0) +})); export const zParameterSimpleParameterUnused = z.string(); @@ -465,7 +644,7 @@ export const zDeleteFooData = z.string(); export const zDeleteFooData2 = z.string(); -export const zimport = z.string(); +export const zImport = z.string(); export const zSchemaWithFormRestrictedKeys = z.object({ description: z.string().optional(), @@ -489,14 +668,14 @@ export const zSchemaWithFormRestrictedKeys = z.object({ })).optional() }); -export const zio_k8s_apimachinery_pkg_apis_meta_v1_DeleteOptions = z.object({ +export const zIoK8sApimachineryPkgApisMetaV1DeleteOptions = z.object({ preconditions: z.object({ resourceVersion: z.string().optional(), uid: z.string().optional() }).optional() }); -export const zio_k8s_apimachinery_pkg_apis_meta_v1_Preconditions = z.object({ +export const zIoK8sApimachineryPkgApisMetaV1Preconditions = z.object({ resourceVersion: z.string().optional(), uid: z.string().optional() }); @@ -505,23 +684,125 @@ export const zAdditionalPropertiesUnknownIssue = z.object({}); export const zAdditionalPropertiesUnknownIssue2 = z.object({}); -export const zAdditionalPropertiesUnknownIssue3 = z.unknown(); +export const zAdditionalPropertiesUnknownIssue3 = z.intersection(z.string(), z.object({ + entries: z.object({}) +})); export const zAdditionalPropertiesIntegerIssue = z.object({ value: z.number() }); -export const zOneOfAllOfIssue = z.unknown(); +export const zOneOfAllOfIssue = z.union([ + z.intersection(z.union([ + zConstValue, + z.object({ + item: z.boolean().optional(), + error: z.union([ + z.string(), + z.null() + ]).optional(), + hasError: z.boolean().readonly().optional(), + data: z.object({}).optional() + }) + ]), z3eNum1Период), + z.object({ + item: z.union([ + z.string(), + z.null() + ]).optional(), + error: z.union([ + z.string(), + z.null() + ]).optional(), + hasError: z.boolean().readonly().optional() + }) +]); -export const zGeneric_Schema_Duplicate_Issue_1_System_Boolean_ = z.object({ +export const zGenericSchemaDuplicateIssue1SystemBoolean = z.object({ item: z.boolean().optional(), - error: z.unknown().optional(), + error: z.union([ + z.string(), + z.null() + ]).optional(), hasError: z.boolean().readonly().optional(), data: z.object({}).optional() }); -export const zGeneric_Schema_Duplicate_Issue_1_System_String_ = z.object({ - item: z.unknown().optional(), - error: z.unknown().optional(), +export const zGenericSchemaDuplicateIssue1SystemString = z.object({ + item: z.union([ + z.string(), + z.null() + ]).optional(), + error: z.union([ + z.string(), + z.null() + ]).optional(), hasError: z.boolean().readonly().optional() -}); \ No newline at end of file +}); + +export const zImportResponse = z.union([ + zModelFromZendesk, + zModelWithReadOnlyAndWriteOnly +]); + +export const zApiVVersionODataControllerCountResponse = zModelFromZendesk; + +export const zGetApiVbyApiVersionSimpleOperationResponse = z.number(); + +export const zPostCallWithOptionalParamResponse = z.union([ + z.number(), + z.void() +]); + +export const zCallWithNoContentResponseResponse = z.void(); + +export const zCallWithResponseAndNoContentResponseResponse = z.union([ + z.number(), + z.void() +]); + +export const zDummyAResponse = z400; + +export const zDummyBResponse = z.void(); + +export const zCallWithResponseResponse = zImport; + +export const zCallWithDuplicateResponsesResponse = z.union([ + zModelWithBoolean.merge(zModelWithInteger), + zModelWithString +]); + +export const zCallWithResponsesResponse = z.union([ + z.object({ + '@namespace.string': z.string().readonly().optional(), + '@namespace.integer': z.number().readonly().optional(), + value: z.array(zModelWithString).readonly().optional() + }), + zModelThatExtends, + zModelThatExtendsExtends +]); + +export const zTypesResponse = z.union([ + z.number(), + z.string(), + z.boolean(), + z.object({}) +]); + +export const zUploadFileResponse = z.boolean(); + +export const zFileResponseResponse = z.string(); + +export const zComplexTypesResponse = z.array(zModelWithString); + +export const zMultipartResponseResponse = z.object({ + file: z.string().optional(), + metadata: z.object({ + foo: z.string().optional(), + bar: z.string().optional() + }).optional() +}); + +export const zComplexParamsResponse = zModelWithString; + +export const zNonAsciiæøåÆøÅöôêÊ字符串Response = z.array(zNonAsciiStringæøåÆøÅöôêÊ字符串); \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/zod/default/zod.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/zod/default/zod.gen.ts index 06ce5d746..ac499cb56 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/zod/default/zod.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/zod/default/zod.gen.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; export const z400 = z.string(); -export const zcamelCaseCommentWithBreaks = z.number(); +export const zCamelCaseCommentWithBreaks = z.number(); export const zCommentWithBreaks = z.number(); @@ -26,7 +26,7 @@ export const zSimpleBoolean = z.boolean(); export const zSimpleString = z.string(); -export const zNonAsciiStringæøåÆØÅöôêÊ字符串 = z.string(); +export const zNonAsciiStringæøåÆøÅöôêÊ字符串 = z.string(); export const zSimpleFile = z.string(); @@ -34,7 +34,10 @@ export const zSimpleReference = z.object({ prop: z.string().optional() }); -export const zSimpleStringWithPattern = z.unknown(); +export const zSimpleStringWithPattern = z.union([ + z.string().max(64), + z.null() +]); export const zEnumWithStrings = z.enum([ 'Success', @@ -75,14 +78,17 @@ export const zArrayWithArray = z.array(z.array(z.object({ }))); export const zArrayWithProperties = z.array(z.object({ - '16x16': zcamelCaseCommentWithBreaks.optional(), + '16x16': zCamelCaseCommentWithBreaks.optional(), bar: z.string().optional() })); export const zArrayWithAnyOfProperties = z.array(z.unknown()); export const zAnyOfAnyAndNull = z.object({ - data: z.unknown().optional() + data: z.union([ + z.unknown(), + z.null() + ]).optional() }); export const zAnyOfArrays = z.object({ @@ -120,13 +126,25 @@ export const zModelWithStringError = z.object({ prop: z.string().optional() }); -export const zModel_From_Zendesk = z.string(); +export const zModelFromZendesk = z.string(); export const zModelWithNullableString = z.object({ - nullableProp1: z.unknown().optional(), - nullableRequiredProp1: z.unknown(), - nullableProp2: z.unknown().optional(), - nullableRequiredProp2: z.unknown(), + nullableProp1: z.union([ + z.string(), + z.null() + ]).optional(), + nullableRequiredProp1: z.union([ + z.string(), + z.null() + ]), + nullableProp2: z.union([ + z.string(), + z.null() + ]).optional(), + nullableRequiredProp2: z.union([ + z.string(), + z.null() + ]), 'foo_bar-enum': z.enum([ 'Success', 'Warning', @@ -184,7 +202,10 @@ export const zModelWithReference = z.object({ prop: z.object({ required: z.string(), requiredAndReadOnly: z.string().readonly(), - requiredAndNullable: z.unknown(), + requiredAndNullable: z.union([ + z.string(), + z.null() + ]), string: z.string().optional(), number: z.number().optional(), boolean: z.boolean().optional(), @@ -221,12 +242,29 @@ export const zDeprecatedModel = z.object({ prop: z.string().optional() }); +export const zModelWithCircularReference: z.ZodTypeAny = z.object({ + prop: z.lazy(() => { + return zModelWithCircularReference; + }).optional() +}); + export const zCompositionWithOneOf = z.object({ - propA: z.unknown().optional() + propA: z.union([ + zModelWithString, + zModelWithEnum, + zModelWithArray, + zModelWithDictionary + ]).optional() }); export const zCompositionWithOneOfAnonymous = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.object({ + propA: z.string().optional() + }), + z.string(), + z.number() + ]).optional() }); export const zModelCircle = z.object({ @@ -239,21 +277,42 @@ export const zModelSquare = z.object({ sideLength: z.number().optional() }); -export const zCompositionWithOneOfDiscriminator = z.unknown(); +export const zCompositionWithOneOfDiscriminator = z.union([ + z.object({ + kind: z.string().optional() + }).merge(zModelCircle), + z.object({ + kind: z.string().optional() + }).merge(zModelSquare) +]); export const zCompositionWithAnyOf = z.object({ - propA: z.unknown().optional() + propA: z.union([ + zModelWithString, + zModelWithEnum, + zModelWithArray, + zModelWithDictionary + ]).optional() }); export const zCompositionWithAnyOfAnonymous = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.object({ + propA: z.string().optional() + }), + z.string(), + z.number() + ]).optional() }); export const zCompositionWithNestedAnyAndTypeNull = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.array(z.unknown()), + z.array(z.unknown()) + ]).optional() }); -export const z3e_num_1Период = z.enum([ +export const z3eNum1Период = z.enum([ 'Bird', 'Dog' ]); @@ -261,31 +320,64 @@ export const z3e_num_1Период = z.enum([ export const zConstValue = z.string(); export const zCompositionWithNestedAnyOfAndNull = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.array(z.unknown()), + z.null() + ]).optional() }); export const zCompositionWithOneOfAndNullable = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.object({ + boolean: z.boolean().optional() + }), + zModelWithEnum, + zModelWithArray, + zModelWithDictionary, + z.null() + ]).optional() }); export const zCompositionWithOneOfAndSimpleDictionary = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.boolean(), + z.object({}) + ]).optional() }); export const zCompositionWithOneOfAndSimpleArrayDictionary = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.boolean(), + z.object({}) + ]).optional() }); export const zCompositionWithOneOfAndComplexArrayDictionary = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.boolean(), + z.object({}) + ]).optional() }); export const zCompositionWithAllOfAndNullable = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.object({ + boolean: z.boolean().optional() + }).merge(zModelWithEnum).merge(zModelWithArray).merge(zModelWithDictionary), + z.null() + ]).optional() }); export const zCompositionWithAnyOfAndNullable = z.object({ - propA: z.unknown().optional() + propA: z.union([ + z.object({ + boolean: z.boolean().optional() + }), + zModelWithEnum, + zModelWithArray, + zModelWithDictionary, + z.null() + ]).optional() }); export const zCompositionBaseModel = z.object({ @@ -293,12 +385,19 @@ export const zCompositionBaseModel = z.object({ lastname: z.string().optional() }); -export const zCompositionExtendedModel = z.unknown(); +export const zCompositionExtendedModel = zCompositionBaseModel.merge(z.object({ + age: z.number(), + firstName: z.string(), + lastname: z.string() +})); export const zModelWithProperties = z.object({ required: z.string(), requiredAndReadOnly: z.string().readonly(), - requiredAndNullable: z.unknown(), + requiredAndNullable: z.union([ + z.string(), + z.null() + ]), string: z.string().optional(), number: z.number().optional(), boolean: z.boolean().optional(), @@ -311,7 +410,20 @@ export const zModelWithProperties = z.object({ }); export const zModelWithNestedProperties = z.object({ - first: z.unknown().readonly() + first: z.union([ + z.object({ + second: z.union([ + z.object({ + third: z.union([ + z.string(), + z.null() + ]).readonly() + }), + z.null() + ]).readonly() + }), + z.null() + ]).readonly() }); export const zModelWithDuplicateProperties = z.object({ @@ -330,9 +442,15 @@ export const zModelWithDuplicateImports = z.object({ propC: zModelWithString.optional() }); -export const zModelThatExtends = z.unknown(); +export const zModelThatExtends = zModelWithString.merge(z.object({ + propExtendsA: z.string().optional(), + propExtendsB: zModelWithString.optional() +})); -export const zModelThatExtendsExtends = z.unknown(); +export const zModelThatExtendsExtends = zModelWithString.merge(zModelThatExtends).merge(z.object({ + propExtendsC: z.string().optional(), + propExtendsD: zModelWithString.optional() +})); export const zModelWithPattern = z.object({ key: z.string().max(64), @@ -354,7 +472,7 @@ export const zFile = z.object({ file: z.string().url().readonly().optional() }); -export const zdefault = z.object({ +export const zDefault = z.object({ name: z.string().optional() }); @@ -382,12 +500,33 @@ export const zModelWithAdditionalPropertiesEqTrue = z.object({ }); export const zNestedAnyOfArraysNullable = z.object({ - nullableArray: z.unknown().optional() + nullableArray: z.union([ + z.array(z.unknown()), + z.null() + ]).optional() }); -export const zCompositionWithOneOfAndProperties = z.unknown(); +export const zCompositionWithOneOfAndProperties = z.intersection(z.union([ + z.object({ + foo: z.unknown() + }), + z.object({ + bar: zNonAsciiStringæøåÆøÅöôêÊ字符串 + }) +]), z.object({ + baz: z.union([ + z.number().gte(0), + z.null() + ]), + qux: z.number().gte(0) +})); -export const zNullableObject = z.unknown(); +export const zNullableObject = z.union([ + z.object({ + foo: z.string().optional() + }), + z.null() +]); export const zCharactersInDescription = z.string(); @@ -395,7 +534,35 @@ export const zModelWithNullableObject = z.object({ data: zNullableObject.optional() }); -export const zModelWithOneOfEnum = z.unknown(); +export const zModelWithOneOfEnum = z.union([ + z.object({ + foo: z.enum([ + 'Bar' + ]) + }), + z.object({ + foo: z.enum([ + 'Baz' + ]) + }), + z.object({ + foo: z.enum([ + 'Qux' + ]) + }), + z.object({ + content: z.string().datetime(), + foo: z.enum([ + 'Quux' + ]) + }), + z.object({ + content: z.unknown(), + foo: z.enum([ + 'Corge' + ]) + }) +]); export const zModelWithNestedArrayEnumsDataFoo = z.enum([ 'foo', @@ -447,7 +614,16 @@ export const zModelWithBackticksInDescription = z.object({ template: z.string().optional() }); -export const zModelWithOneOfAndProperties = z.unknown(); +export const zModelWithOneOfAndProperties = z.intersection(z.union([ + z.unknown(), + zNonAsciiStringæøåÆøÅöôêÊ字符串 +]), z.object({ + baz: z.union([ + z.number().gte(0), + z.null() + ]), + qux: z.number().gte(0) +})); export const zParameterSimpleParameterUnused = z.string(); @@ -459,7 +635,7 @@ export const zDeleteFooData = z.string(); export const zDeleteFooData2 = z.string(); -export const zimport = z.string(); +export const zImport = z.string(); export const zSchemaWithFormRestrictedKeys = z.object({ description: z.string().optional(), @@ -483,14 +659,14 @@ export const zSchemaWithFormRestrictedKeys = z.object({ })).optional() }); -export const zio_k8s_apimachinery_pkg_apis_meta_v1_DeleteOptions = z.object({ +export const zIoK8sApimachineryPkgApisMetaV1DeleteOptions = z.object({ preconditions: z.object({ resourceVersion: z.string().optional(), uid: z.string().optional() }).optional() }); -export const zio_k8s_apimachinery_pkg_apis_meta_v1_Preconditions = z.object({ +export const zIoK8sApimachineryPkgApisMetaV1Preconditions = z.object({ resourceVersion: z.string().optional(), uid: z.string().optional() }); @@ -499,23 +675,125 @@ export const zAdditionalPropertiesUnknownIssue = z.object({}); export const zAdditionalPropertiesUnknownIssue2 = z.object({}); -export const zAdditionalPropertiesUnknownIssue3 = z.unknown(); +export const zAdditionalPropertiesUnknownIssue3 = z.intersection(z.string(), z.object({ + entries: z.object({}) +})); export const zAdditionalPropertiesIntegerIssue = z.object({ value: z.number() }); -export const zOneOfAllOfIssue = z.unknown(); +export const zOneOfAllOfIssue = z.union([ + z.intersection(z.union([ + zConstValue, + z.object({ + item: z.boolean().optional(), + error: z.union([ + z.string(), + z.null() + ]).optional(), + hasError: z.boolean().readonly().optional(), + data: z.object({}).optional() + }) + ]), z3eNum1Период), + z.object({ + item: z.union([ + z.string(), + z.null() + ]).optional(), + error: z.union([ + z.string(), + z.null() + ]).optional(), + hasError: z.boolean().readonly().optional() + }) +]); -export const zGeneric_Schema_Duplicate_Issue_1_System_Boolean_ = z.object({ +export const zGenericSchemaDuplicateIssue1SystemBoolean = z.object({ item: z.boolean().optional(), - error: z.unknown().optional(), + error: z.union([ + z.string(), + z.null() + ]).optional(), hasError: z.boolean().readonly().optional(), data: z.object({}).optional() }); -export const zGeneric_Schema_Duplicate_Issue_1_System_String_ = z.object({ - item: z.unknown().optional(), - error: z.unknown().optional(), +export const zGenericSchemaDuplicateIssue1SystemString = z.object({ + item: z.union([ + z.string(), + z.null() + ]).optional(), + error: z.union([ + z.string(), + z.null() + ]).optional(), hasError: z.boolean().readonly().optional() -}); \ No newline at end of file +}); + +export const zImportResponse = z.union([ + zModelFromZendesk, + zModelWithReadOnlyAndWriteOnly +]); + +export const zApiVVersionODataControllerCountResponse = zModelFromZendesk; + +export const zGetApiVbyApiVersionSimpleOperationResponse = z.number(); + +export const zPostCallWithOptionalParamResponse = z.union([ + z.number(), + z.void() +]); + +export const zCallWithNoContentResponseResponse = z.void(); + +export const zCallWithResponseAndNoContentResponseResponse = z.union([ + z.number(), + z.void() +]); + +export const zDummyAResponse = z400; + +export const zDummyBResponse = z.void(); + +export const zCallWithResponseResponse = zImport; + +export const zCallWithDuplicateResponsesResponse = z.union([ + zModelWithBoolean.merge(zModelWithInteger), + zModelWithString +]); + +export const zCallWithResponsesResponse = z.union([ + z.object({ + '@namespace.string': z.string().readonly().optional(), + '@namespace.integer': z.number().readonly().optional(), + value: z.array(zModelWithString).readonly().optional() + }), + zModelThatExtends, + zModelThatExtendsExtends +]); + +export const zTypesResponse = z.union([ + z.number(), + z.string(), + z.boolean(), + z.object({}) +]); + +export const zUploadFileResponse = z.boolean(); + +export const zFileResponseResponse = z.string(); + +export const zComplexTypesResponse = z.array(zModelWithString); + +export const zMultipartResponseResponse = z.object({ + file: z.string().optional(), + metadata: z.object({ + foo: z.string().optional(), + bar: z.string().optional() + }).optional() +}); + +export const zComplexParamsResponse = zModelWithString; + +export const zNonAsciiæøåÆøÅöôêÊ字符串Response = z.array(zNonAsciiStringæøåÆøÅöôêÊ字符串); \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle/client/index.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle/client/index.ts.snap index 55c4970c8..3a377c8ba 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle/client/index.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle/client/index.ts.snap @@ -64,8 +64,14 @@ export const createClient = (config: Config): Client => { let { data } = response; - if (opts.responseType === 'json' && opts.responseTransformer) { - data = await opts.responseTransformer(data); + if (opts.responseType === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } } return { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle/client/types.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle/client/types.ts.snap index 56cacb235..927d791f5 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle/client/types.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle/client/types.ts.snap @@ -83,11 +83,16 @@ export interface Config */ querySerializer?: QuerySerializer | QuerySerializerOptions; /** - * A function for transforming response data before it's returned to the - * caller function. This is an ideal place to post-process server data, - * e.g. convert date ISO strings into native Date objects. + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. */ responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; /** * Throw an error instead of returning it in the response? * @@ -97,7 +102,7 @@ export interface Config } export interface RequestOptions< - ThrowOnError extends boolean = false, + ThrowOnError extends boolean = boolean, Url extends string = string, > extends Config { /** diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle_transform/client/index.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle_transform/client/index.ts.snap index 55c4970c8..3a377c8ba 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle_transform/client/index.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle_transform/client/index.ts.snap @@ -64,8 +64,14 @@ export const createClient = (config: Config): Client => { let { data } = response; - if (opts.responseType === 'json' && opts.responseTransformer) { - data = await opts.responseTransformer(data); + if (opts.responseType === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } } return { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle_transform/client/types.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle_transform/client/types.ts.snap index 56cacb235..927d791f5 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle_transform/client/types.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle_transform/client/types.ts.snap @@ -83,11 +83,16 @@ export interface Config */ querySerializer?: QuerySerializer | QuerySerializerOptions; /** - * A function for transforming response data before it's returned to the - * caller function. This is an ideal place to post-process server data, - * e.g. convert date ISO strings into native Date objects. + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. */ responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; /** * Throw an error instead of returning it in the response? * @@ -97,7 +102,7 @@ export interface Config } export interface RequestOptions< - ThrowOnError extends boolean = false, + ThrowOnError extends boolean = boolean, Url extends string = string, > extends Config { /** diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/index.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/index.ts.snap index 2883b08cc..1985110ee 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/index.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/index.ts.snap @@ -106,8 +106,14 @@ export const createClient = (config: Config = {}): Client => { : opts.parseAs) ?? 'json'; let data = await response[parseAs](); - if (parseAs === 'json' && opts.responseTransformer) { - data = await opts.responseTransformer(data); + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } } return { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/types.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/types.ts.snap index 0c914db6c..4049d690b 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/types.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/types.ts.snap @@ -88,11 +88,16 @@ export interface Config */ querySerializer?: QuerySerializer | QuerySerializerOptions; /** - * A function for transforming response data before it's returned to the - * caller function. This is an ideal place to post-process server data, - * e.g. convert date ISO strings into native Date objects. + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. */ responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; /** * Throw an error instead of returning it in the response? * diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/index.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/index.ts.snap index 2883b08cc..1985110ee 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/index.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/index.ts.snap @@ -106,8 +106,14 @@ export const createClient = (config: Config = {}): Client => { : opts.parseAs) ?? 'json'; let data = await response[parseAs](); - if (parseAs === 'json' && opts.responseTransformer) { - data = await opts.responseTransformer(data); + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } } return { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/types.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/types.ts.snap index 0c914db6c..4049d690b 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/types.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/types.ts.snap @@ -88,11 +88,16 @@ export interface Config */ querySerializer?: QuerySerializer | QuerySerializerOptions; /** - * A function for transforming response data before it's returned to the - * caller function. This is an ideal place to post-process server data, - * e.g. convert date ISO strings into native Date objects. + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. */ responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; /** * Throw an error instead of returning it in the response? * diff --git a/packages/openapi-ts/test/index.test.ts b/packages/openapi-ts/test/index.test.ts index ee9378a44..f2ac8e145 100644 --- a/packages/openapi-ts/test/index.test.ts +++ b/packages/openapi-ts/test/index.test.ts @@ -492,7 +492,26 @@ describe('OpenAPI v3', () => { input: V3_TRANSFORMS_SPEC_PATH, output, plugins: [ - ...(config.plugins ?? []), + ...(config.plugins ?? []).map((plugin) => { + if (typeof plugin === 'string') { + if (plugin === '@hey-api/sdk') { + return { + // @ts-expect-error + ...plugin, + name: '@hey-api/sdk', + transformer: true, + }; + } + } else if (plugin.name === '@hey-api/sdk') { + return { + ...plugin, + name: '@hey-api/sdk', + transformer: true, + }; + } + + return plugin; + }), { dates: true, name: '@hey-api/transformers', diff --git a/packages/openapi-ts/test/plugins.test.ts b/packages/openapi-ts/test/plugins.test.ts index 10bae60fc..b04ecc073 100644 --- a/packages/openapi-ts/test/plugins.test.ts +++ b/packages/openapi-ts/test/plugins.test.ts @@ -212,11 +212,6 @@ for (const version of versions) { }, { config: createConfig({ - input: { - // TODO: parser - remove `exclude` once recursive references are handled - exclude: '^#/components/schemas/ModelWithCircularReference$', - path: path.join(__dirname, 'spec', version, 'full.json'), - }, output: 'default', plugins: ['zod'], }), diff --git a/packages/openapi-ts/test/sample.cjs b/packages/openapi-ts/test/sample.cjs index e2b9743ef..aef07ee26 100644 --- a/packages/openapi-ts/test/sample.cjs +++ b/packages/openapi-ts/test/sample.cjs @@ -5,15 +5,17 @@ const main = async () => { const config = { client: { // bundle: true, - name: '@hey-api/client-axios', + // name: '@hey-api/client-axios', // name: '@hey-api/client-fetch', + name: 'legacy/xhr', }, - experimentalParser: true, + // experimentalParser: true, input: { - exclude: '^#/components/schemas/ModelWithCircularReference$', + // exclude: '^#/components/schemas/ModelWithCircularReference$', // include: // '^(#/components/schemas/import|#/paths/api/v{api-version}/simple/options)$', - path: './test/spec/3.1.x/parameter-explode-false.json', + // path: './test/spec/3.1.x/full.json', + path: './test/spec/v3-transforms.json', // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', // path: 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', }, @@ -34,12 +36,15 @@ const main = async () => { // type: 'json', }, { - // asClass: true, + asClass: true, // auth: false, // include... name: '@hey-api/sdk', // operationId: false, // serviceNameBuilder: '^Parameters', + // transformer: '@hey-api/transformers', + transformer: true, + // validator: 'zod', }, { dates: true, @@ -50,7 +55,7 @@ const main = async () => { // enums: 'typescript+namespace', enums: 'javascript', // exportInlineEnums: true, - identifierCase: 'preserve', + // identifierCase: 'preserve', name: '@hey-api/typescript', // tree: true, }, @@ -61,7 +66,7 @@ const main = async () => { // name: '@tanstack/vue-query', }, { - name: 'zod', + // name: 'zod', }, ], // useOptions: false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ebb4bcad..8772600a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -297,7 +297,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: ^19.0.4 - version: 19.0.4(@angular/compiler-cli@19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3))(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(@types/node@22.8.5)(chokidar@4.0.1)(karma@6.4.4)(tailwindcss@3.4.9)(typescript@5.5.3)(vite@5.4.11(@types/node@22.8.5)(less@4.2.0)(sass@1.80.7)(terser@5.36.0)) + version: 19.0.4(@angular/compiler-cli@19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3))(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(@types/node@22.8.5)(chokidar@4.0.1)(karma@6.4.4)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.8.5)(typescript@5.5.3)))(typescript@5.5.3)(vite@5.4.11(@types/node@22.8.5)(less@4.2.0)(sass@1.80.7)(terser@5.36.0)) '@angular/cli': specifier: ^19.0.4 version: 19.0.4(@types/node@22.8.5)(chokidar@4.0.1) @@ -9439,7 +9439,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1900.4(chokidar@4.0.1) - '@angular-devkit/build-webpack': 0.1900.4(chokidar@4.0.1)(webpack-dev-server@5.1.0(webpack@5.96.1(esbuild@0.24.0)))(webpack@5.96.1(esbuild@0.24.0)) + '@angular-devkit/build-webpack': 0.1900.4(chokidar@4.0.1)(webpack-dev-server@5.1.0(webpack@5.96.1(esbuild@0.23.1)))(webpack@5.96.1(esbuild@0.24.0)) '@angular-devkit/core': 19.0.4(chokidar@4.0.1) '@angular/build': 19.0.4(@angular/compiler-cli@19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3))(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(@types/node@22.8.5)(chokidar@4.0.1)(less@4.2.0)(postcss@8.4.49)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.8.5)(typescript@5.5.3)))(terser@5.36.0)(typescript@5.5.3) '@angular/compiler-cli': 19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3) @@ -9489,9 +9489,9 @@ snapshots: tree-kill: 1.2.2 tslib: 2.8.1 typescript: 5.5.3 - webpack: 5.96.1(esbuild@0.23.1) - webpack-dev-middleware: 7.4.2(webpack@5.96.1(esbuild@0.24.0)) - webpack-dev-server: 5.1.0(webpack@5.96.1(esbuild@0.24.0)) + webpack: 5.96.1(esbuild@0.24.0) + webpack-dev-middleware: 7.4.2(webpack@5.96.1(esbuild@0.23.1)) + webpack-dev-server: 5.1.0(webpack@5.96.1(esbuild@0.23.1)) webpack-merge: 6.0.1 webpack-subresource-integrity: 5.1.0(webpack@5.96.1(esbuild@0.24.0)) optionalDependencies: @@ -9518,95 +9518,12 @@ snapshots: - vite - webpack-cli - '@angular-devkit/build-angular@19.0.4(@angular/compiler-cli@19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3))(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(@types/node@22.8.5)(chokidar@4.0.1)(karma@6.4.4)(tailwindcss@3.4.9)(typescript@5.5.3)(vite@5.4.11(@types/node@22.8.5)(less@4.2.0)(sass@1.80.7)(terser@5.36.0))': - dependencies: - '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1900.4(chokidar@4.0.1) - '@angular-devkit/build-webpack': 0.1900.4(chokidar@4.0.1)(webpack-dev-server@5.1.0(webpack@5.96.1(esbuild@0.24.0)))(webpack@5.96.1(esbuild@0.24.0)) - '@angular-devkit/core': 19.0.4(chokidar@4.0.1) - '@angular/build': 19.0.4(@angular/compiler-cli@19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3))(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(@types/node@22.8.5)(chokidar@4.0.1)(less@4.2.0)(postcss@8.4.49)(tailwindcss@3.4.9)(terser@5.36.0)(typescript@5.5.3) - '@angular/compiler-cli': 19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3) - '@babel/core': 7.26.0 - '@babel/generator': 7.26.2 - '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-runtime': 7.25.9(@babel/core@7.26.0) - '@babel/preset-env': 7.26.0(@babel/core@7.26.0) - '@babel/runtime': 7.26.0 - '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 19.0.4(@angular/compiler-cli@19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3))(typescript@5.5.3)(webpack@5.96.1(esbuild@0.24.0)) - '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.11(@types/node@22.8.5)(less@4.2.0)(sass@1.80.7)(terser@5.36.0)) - ansi-colors: 4.1.3 - autoprefixer: 10.4.20(postcss@8.4.49) - babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.96.1(esbuild@0.24.0)) - browserslist: 4.24.2 - copy-webpack-plugin: 12.0.2(webpack@5.96.1(esbuild@0.24.0)) - css-loader: 7.1.2(webpack@5.96.1(esbuild@0.24.0)) - esbuild-wasm: 0.24.0 - fast-glob: 3.3.2 - http-proxy-middleware: 3.0.3 - istanbul-lib-instrument: 6.0.3 - jsonc-parser: 3.3.1 - karma-source-map-support: 1.4.0 - less: 4.2.0 - less-loader: 12.2.0(less@4.2.0)(webpack@5.96.1(esbuild@0.24.0)) - license-webpack-plugin: 4.0.2(webpack@5.96.1(esbuild@0.24.0)) - loader-utils: 3.3.1 - mini-css-extract-plugin: 2.9.2(webpack@5.96.1(esbuild@0.24.0)) - open: 10.1.0 - ora: 5.4.1 - picomatch: 4.0.2 - piscina: 4.7.0 - postcss: 8.4.49 - postcss-loader: 8.1.1(postcss@8.4.49)(typescript@5.5.3)(webpack@5.96.1(esbuild@0.24.0)) - resolve-url-loader: 5.0.0 - rxjs: 7.8.1 - sass: 1.80.7 - sass-loader: 16.0.3(sass@1.80.7)(webpack@5.96.1(esbuild@0.24.0)) - semver: 7.6.3 - source-map-loader: 5.0.0(webpack@5.96.1(esbuild@0.24.0)) - source-map-support: 0.5.21 - terser: 5.36.0 - tree-kill: 1.2.2 - tslib: 2.8.1 - typescript: 5.5.3 - webpack: 5.96.1(esbuild@0.23.1) - webpack-dev-middleware: 7.4.2(webpack@5.96.1(esbuild@0.24.0)) - webpack-dev-server: 5.1.0(webpack@5.96.1(esbuild@0.24.0)) - webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(webpack@5.96.1(esbuild@0.24.0)) - optionalDependencies: - esbuild: 0.24.0 - karma: 6.4.4 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@20.14.5)(typescript@5.5.3)) - transitivePeerDependencies: - - '@angular/compiler' - - '@rspack/core' - - '@swc/core' - - '@types/node' - - bufferutil - - chokidar - - debug - - html-webpack-plugin - - lightningcss - - node-sass - - sass-embedded - - stylus - - sugarss - - supports-color - - uglify-js - - utf-8-validate - - vite - - webpack-cli - - '@angular-devkit/build-webpack@0.1900.4(chokidar@4.0.1)(webpack-dev-server@5.1.0(webpack@5.96.1(esbuild@0.24.0)))(webpack@5.96.1(esbuild@0.24.0))': + '@angular-devkit/build-webpack@0.1900.4(chokidar@4.0.1)(webpack-dev-server@5.1.0(webpack@5.96.1(esbuild@0.23.1)))(webpack@5.96.1(esbuild@0.24.0))': dependencies: '@angular-devkit/architect': 0.1900.4(chokidar@4.0.1) rxjs: 7.8.1 - webpack: 5.96.1(esbuild@0.23.1) - webpack-dev-server: 5.1.0(webpack@5.96.1(esbuild@0.24.0)) + webpack: 5.96.1(esbuild@0.24.0) + webpack-dev-server: 5.1.0(webpack@5.96.1(esbuild@0.23.1)) transitivePeerDependencies: - chokidar @@ -9681,51 +9598,6 @@ snapshots: - supports-color - terser - '@angular/build@19.0.4(@angular/compiler-cli@19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3))(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(@types/node@22.8.5)(chokidar@4.0.1)(less@4.2.0)(postcss@8.4.49)(tailwindcss@3.4.9)(terser@5.36.0)(typescript@5.5.3)': - dependencies: - '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.1900.4(chokidar@4.0.1) - '@angular/compiler': 19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)) - '@angular/compiler-cli': 19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3) - '@babel/core': 7.26.0 - '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) - '@inquirer/confirm': 5.0.2(@types/node@22.8.5) - '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.4.11(@types/node@22.8.5)(less@4.2.0)(sass@1.80.7)(terser@5.36.0)) - beasties: 0.1.0 - browserslist: 4.24.2 - esbuild: 0.24.0 - fast-glob: 3.3.2 - https-proxy-agent: 7.0.5 - istanbul-lib-instrument: 6.0.3 - listr2: 8.2.5 - magic-string: 0.30.12 - mrmime: 2.0.0 - parse5-html-rewriting-stream: 7.0.0 - picomatch: 4.0.2 - piscina: 4.7.0 - rollup: 4.26.0 - sass: 1.80.7 - semver: 7.6.3 - typescript: 5.5.3 - vite: 5.4.11(@types/node@22.8.5)(less@4.2.0)(sass@1.80.7)(terser@5.36.0) - watchpack: 2.4.2 - optionalDependencies: - less: 4.2.0 - lmdb: 3.1.5 - postcss: 8.4.49 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@20.14.5)(typescript@5.5.3)) - transitivePeerDependencies: - - '@types/node' - - chokidar - - lightningcss - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - '@angular/cdk@19.0.2(@angular/common@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1)': dependencies: '@angular/common': 19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1) @@ -11640,7 +11512,7 @@ snapshots: dependencies: '@angular/compiler-cli': 19.0.3(@angular/compiler@19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)))(typescript@5.5.3) typescript: 5.5.3 - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) '@nodelib/fs.scandir@2.1.5': dependencies: @@ -13981,7 +13853,7 @@ snapshots: '@babel/core': 7.26.0 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.26.0): dependencies: @@ -14472,7 +14344,7 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.2.0 serialize-javascript: 6.0.2 - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) core-js-compat@3.39.0: dependencies: @@ -14519,7 +14391,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) css-select@5.1.0: dependencies: @@ -16104,7 +15976,7 @@ snapshots: dependencies: less: 4.2.0 optionalDependencies: - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) less@4.2.0: dependencies: @@ -16129,7 +16001,7 @@ snapshots: dependencies: webpack-sources: 3.2.3 optionalDependencies: - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) light-my-request@6.3.0: dependencies: @@ -16427,7 +16299,7 @@ snapshots: dependencies: schema-utils: 4.2.0 tapable: 2.2.1 - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) minimalistic-assert@1.0.1: {} @@ -17085,7 +16957,7 @@ snapshots: postcss: 8.4.49 semver: 7.6.3 optionalDependencies: - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) transitivePeerDependencies: - typescript @@ -17572,7 +17444,7 @@ snapshots: neo-async: 2.6.2 optionalDependencies: sass: 1.80.7 - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) sass@1.80.7: dependencies: @@ -17855,7 +17727,7 @@ snapshots: dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) source-map-support@0.5.21: dependencies: @@ -18237,16 +18109,16 @@ snapshots: term-size@2.2.1: {} - terser-webpack-plugin@5.3.10(esbuild@0.23.1)(webpack@5.96.1(esbuild@0.24.0)): + terser-webpack-plugin@5.3.10(esbuild@0.24.0)(webpack@5.96.1(esbuild@0.23.1)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.36.0 - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) optionalDependencies: - esbuild: 0.23.1 + esbuild: 0.24.0 terser@5.36.0: dependencies: @@ -18391,7 +18263,7 @@ snapshots: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 22.8.5 - acorn: 8.12.1 + acorn: 8.14.0 acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 @@ -19067,7 +18939,7 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-dev-middleware@7.4.2(webpack@5.96.1(esbuild@0.24.0)): + webpack-dev-middleware@7.4.2(webpack@5.96.1(esbuild@0.23.1)): dependencies: colorette: 2.0.20 memfs: 4.14.0 @@ -19076,9 +18948,9 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.2.0 optionalDependencies: - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) - webpack-dev-server@5.1.0(webpack@5.96.1(esbuild@0.24.0)): + webpack-dev-server@5.1.0(webpack@5.96.1(esbuild@0.23.1)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -19106,10 +18978,10 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.96.1(esbuild@0.24.0)) + webpack-dev-middleware: 7.4.2(webpack@5.96.1(esbuild@0.23.1)) ws: 8.18.0 optionalDependencies: - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) transitivePeerDependencies: - bufferutil - debug @@ -19127,9 +18999,9 @@ snapshots: webpack-subresource-integrity@5.1.0(webpack@5.96.1(esbuild@0.24.0)): dependencies: typed-assert: 1.0.9 - webpack: 5.96.1(esbuild@0.23.1) + webpack: 5.96.1(esbuild@0.24.0) - webpack@5.96.1(esbuild@0.23.1): + webpack@5.96.1(esbuild@0.24.0): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 @@ -19151,7 +19023,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.96.1(esbuild@0.24.0)) + terser-webpack-plugin: 5.3.10(esbuild@0.24.0)(webpack@5.96.1(esbuild@0.23.1)) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: