fork of hey-api/openapi-ts because I need some additional things

Merge pull request #1430 from hey-api/fix/zod-schema-response

fix: generate response zod schemas

authored by

Lubos and committed by
GitHub
42a99204 80955054

+1433 -537
+30
.changeset/fluffy-beans-clean.md
··· 1 + --- 2 + '@hey-api/openapi-ts': minor 3 + --- 4 + 5 + fix: require sdk.transformer to use generated transformers 6 + 7 + ### Added `sdk.transformer` option 8 + 9 + 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`. 10 + 11 + ```js 12 + import { defaultPlugins } from '@hey-api/openapi-ts'; 13 + 14 + export default { 15 + client: '@hey-api/client-fetch', 16 + input: 'path/to/openapi.json', 17 + output: 'src/client', 18 + plugins: [ 19 + ...defaultPlugins, 20 + { 21 + dates: true, 22 + name: '@hey-api/transformers', 23 + }, 24 + { 25 + name: '@hey-api/sdk', 26 + transformer: true, // [!code ++] 27 + }, 28 + ], 29 + }; 30 + ```
+5
.changeset/lazy-moose-retire.md
··· 1 + --- 2 + '@hey-api/docs': patch 3 + --- 4 + 5 + docs: add validators page
+6
.changeset/polite-houses-roll.md
··· 1 + --- 2 + '@hey-api/client-axios': patch 3 + '@hey-api/client-fetch': patch 4 + --- 5 + 6 + fix: add responseValidator option
+5
.changeset/shiny-birds-remain.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + feat: Zod plugin generates response schemas
+1 -1
.github/workflows/ci.yml
··· 50 50 run: pnpm test:e2e 51 51 52 52 - name: Publish previews 53 - if: matrix.node-version == '21.x' && matrix.os == 'ubuntu-latest' 53 + if: matrix.node-version == '22.x' && matrix.os == 'ubuntu-latest' 54 54 run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
+2 -6
README.md
··· 27 27 28 28 Automatically update your code when the APIs it depends on change. [Find out more](https://heyapi.dev/openapi-ts/integrations.html). 29 29 30 - ## Migrating from OpenAPI Typescript Codegen? 31 - 32 - Please read our [migration guide](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). 33 - 34 - ## Contributing 30 + ## Migration Guides 35 31 36 - Want to get involved? Please refer to the [contributing guide](https://heyapi.dev/contributing.html). 32 + [OpenAPI Typescript Codegen](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen).
+11 -4
docs/.vitepress/config/en.ts
··· 51 51 text: 'Clients', 52 52 }, 53 53 { 54 + collapsed: true, 55 + items: [ 56 + { 57 + link: '/openapi-ts/validators/zod', 58 + text: 'Zod', 59 + }, 60 + ], 61 + link: '/openapi-ts/validators', 62 + text: 'Validators', 63 + }, 64 + { 54 65 link: '/openapi-ts/transformers', 55 66 text: 'Transformers', 56 67 }, ··· 70 81 { 71 82 link: '/openapi-ts/tanstack-query', 72 83 text: 'TanStack Query', 73 - }, 74 - { 75 - link: '/openapi-ts/zod', 76 - text: 'Zod', 77 84 }, 78 85 ], 79 86 text: 'Plugins',
+1 -1
docs/index.md
··· 67 67 68 68 <div class="home-list"> 69 69 70 - ### Migration guides 70 + ### Migration Guides 71 71 72 72 - [OpenAPI TypeScript Codegen](/openapi-ts/migrating#openapi-typescript-codegen) 73 73
+1
docs/openapi-ts/clients.md
··· 15 15 16 16 - seamless integration with `@hey-api/openapi-ts` ecosystem 17 17 - type-safe response data and errors 18 + - response data validation and transformation 18 19 - access to the original request and response 19 20 - granular request and response customization options 20 21 - minimal learning curve thanks to extending the underlying technology
+27
docs/openapi-ts/migrating.md
··· 27 27 28 28 This config option is deprecated and will be removed. 29 29 30 + ## v0.60.0 31 + 32 + ### Added `sdk.transformer` option 33 + 34 + 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`. 35 + 36 + ```js 37 + import { defaultPlugins } from '@hey-api/openapi-ts'; 38 + 39 + export default { 40 + client: '@hey-api/client-fetch', 41 + input: 'path/to/openapi.json', 42 + output: 'src/client', 43 + plugins: [ 44 + ...defaultPlugins, 45 + { 46 + dates: true, 47 + name: '@hey-api/transformers', 48 + }, 49 + { 50 + name: '@hey-api/sdk', 51 + transformer: true, // [!code ++] 52 + }, 53 + ], 54 + }; 55 + ``` 56 + 30 57 ## v0.59.0 31 58 32 59 ### Added `logs.level` option
+1 -1
docs/openapi-ts/plugins.md
··· 26 26 - [`@tanstack/svelte-query`](/openapi-ts/tanstack-query) - TanStack Query functions and query keys 27 27 - [`@tanstack/vue-query`](/openapi-ts/tanstack-query) - TanStack Query functions and query keys 28 28 - [`fastify`](/openapi-ts/fastify) - TypeScript interface for Fastify route handlers 29 - - [`zod`](/openapi-ts/zod) - Zod schemas to validate your data 29 + - [`zod`](/openapi-ts/validators/zod) - Zod schemas to validate your data 30 30 31 31 ## Community 32 32
+41 -1
docs/openapi-ts/transformers.md
··· 1 1 --- 2 2 title: Transformers 3 - description: Learn about transforming payloads with @hey-api/openapi-ts. 3 + description: Learn about transforming data with @hey-api/openapi-ts. 4 4 --- 5 5 6 6 # Transformers ··· 26 26 - error responses are not transformed 27 27 28 28 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). 29 + 30 + ## Configuration 31 + 32 + To generate transformers, add `@hey-api/transformers` to your plugins. 33 + 34 + ```js 35 + import { defaultPlugins } from '@hey-api/openapi-ts'; 36 + 37 + export default { 38 + client: '@hey-api/client-fetch', 39 + input: 'path/to/openapi.json', 40 + output: 'src/client', 41 + plugins: [ 42 + ...defaultPlugins, 43 + '@hey-api/transformers', // [!code ++] 44 + ], 45 + }; 46 + ``` 47 + 48 + ## SDKs 49 + 50 + To automatically transform response data in your SDKs, set `transformer` to `true`. 51 + 52 + ```js 53 + import { defaultPlugins } from '@hey-api/openapi-ts'; 54 + 55 + export default { 56 + client: '@hey-api/client-fetch', 57 + input: 'path/to/openapi.json', 58 + output: 'src/client', 59 + plugins: [ 60 + ...defaultPlugins, 61 + '@hey-api/transformers', 62 + { 63 + name: '@hey-api/sdk', // [!code ++] 64 + transformer: true, // [!code ++] 65 + }, 66 + ], 67 + }; 68 + ``` 29 69 30 70 ## Dates 31 71
+62
docs/openapi-ts/validators.md
··· 1 + --- 2 + title: Validators 3 + description: Learn about validating data with @hey-api/openapi-ts. 4 + --- 5 + 6 + # Validators 7 + 8 + 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. 9 + 10 + Hey API clients support validating responses so you can rest assured that you're working with the correct data. 11 + 12 + ## Available Validators 13 + 14 + - [Zod](/openapi-ts/validators/zod) 15 + - [Ajv](https://ajv.js.org/) <span class="soon">Soon</span> 16 + - [Joi](https://joi.dev/) <span class="soon">Soon</span> 17 + - [Yup](https://github.com/jquense/yup) <span class="soon">Soon</span> 18 + 19 + 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). 20 + 21 + ## Installation 22 + 23 + 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`. 24 + 25 + ::: code-group 26 + 27 + ```js [sdk] 28 + export default { 29 + client: '@hey-api/client-fetch', 30 + input: 'path/to/openapi.json', 31 + output: 'src/client', 32 + plugins: [ 33 + { 34 + name: '@hey-api/sdk', 35 + validator: 'zod', // [!code ++] 36 + }, 37 + ], 38 + }; 39 + ``` 40 + 41 + ```js [validator] 42 + export default { 43 + client: '@hey-api/client-fetch', 44 + input: 'path/to/openapi.json', 45 + output: 'src/client', 46 + plugins: [ 47 + { 48 + name: '@hey-api/sdk', 49 + validator: true, // [!code ++] 50 + }, 51 + { 52 + name: 'zod', // [!code ++] 53 + // other options 54 + }, 55 + ], 56 + }; 57 + ``` 58 + 59 + ::: 60 + 61 + <!--@include: ../examples.md--> 62 + <!--@include: ../sponsorship.md-->
+24 -2
docs/openapi-ts/zod.md docs/openapi-ts/validators/zod.md
··· 45 45 46 46 You can now generate Zod artifacts. 🎉 47 47 48 + ## SDKs 49 + 50 + To automatically validate response data in your SDKs, set `validator` to `true`. 51 + 52 + ```js 53 + import { defaultPlugins } from '@hey-api/openapi-ts'; 54 + 55 + export default { 56 + client: '@hey-api/client-fetch', 57 + input: 'path/to/openapi.json', 58 + output: 'src/client', 59 + plugins: [ 60 + ...defaultPlugins, 61 + 'zod', 62 + { 63 + name: '@hey-api/sdk', // [!code ++] 64 + validator: true, // [!code ++] 65 + }, 66 + ], 67 + }; 68 + ``` 69 + 48 70 ## Output 49 71 50 72 The Zod plugin will generate the following artifacts, depending on the input specification. ··· 53 75 54 76 More information will be provided as we finalize the plugin. 55 77 56 - <!--@include: ../examples.md--> 57 - <!--@include: ../sponsorship.md--> 78 + <!--@include: ../../examples.md--> 79 + <!--@include: ../../sponsorship.md-->
+3 -6
packages/client-axios/README.md
··· 10 10 11 11 - seamless integration with `@hey-api/openapi-ts` ecosystem 12 12 - type-safe response data and errors 13 + - response data validation and transformation 13 14 - access to the original request and response 14 15 - granular request and response customization options 15 16 - minimal learning curve thanks to extending the underlying technology ··· 27 28 28 29 Automatically update your code when the APIs it depends on change. [Find out more](https://heyapi.dev/openapi-ts/integrations.html). 29 30 30 - ## Migrating from OpenAPI Typescript Codegen? 31 - 32 - Please read our [migration guide](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). 33 - 34 - ## Contributing 31 + ## Migration Guides 35 32 36 - Want to get involved? Please refer to the [contributing guide](https://heyapi.dev/contributing.html). 33 + [OpenAPI Typescript Codegen](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen).
+8 -2
packages/client-axios/src/index.ts
··· 64 64 65 65 let { data } = response; 66 66 67 - if (opts.responseType === 'json' && opts.responseTransformer) { 68 - data = await opts.responseTransformer(data); 67 + if (opts.responseType === 'json') { 68 + if (opts.responseValidator) { 69 + await opts.responseValidator(data); 70 + } 71 + 72 + if (opts.responseTransformer) { 73 + data = await opts.responseTransformer(data); 74 + } 69 75 } 70 76 71 77 return {
+9 -4
packages/client-axios/src/types.ts
··· 83 83 */ 84 84 querySerializer?: QuerySerializer | QuerySerializerOptions; 85 85 /** 86 - * A function for transforming response data before it's returned to the 87 - * caller function. This is an ideal place to post-process server data, 88 - * e.g. convert date ISO strings into native Date objects. 86 + * A function transforming response data before it's returned. This is useful 87 + * for post-processing data, e.g. converting ISO strings into Date objects. 89 88 */ 90 89 responseTransformer?: (data: unknown) => Promise<unknown>; 91 90 /** 91 + * A function validating response data. This is useful if you want to ensure 92 + * the response conforms to the desired shape, so it can be safely passed to 93 + * the transformers and returned to the user. 94 + */ 95 + responseValidator?: (data: unknown) => Promise<unknown>; 96 + /** 92 97 * Throw an error instead of returning it in the response? 93 98 * 94 99 * @default false ··· 97 102 } 98 103 99 104 export interface RequestOptions< 100 - ThrowOnError extends boolean = false, 105 + ThrowOnError extends boolean = boolean, 101 106 Url extends string = string, 102 107 > extends Config<ThrowOnError> { 103 108 /**
+3 -6
packages/client-fetch/README.md
··· 10 10 11 11 - seamless integration with `@hey-api/openapi-ts` ecosystem 12 12 - type-safe response data and errors 13 + - response data validation and transformation 13 14 - access to the original request and response 14 15 - granular request and response customization options 15 16 - minimal learning curve thanks to extending the underlying technology ··· 27 28 28 29 Automatically update your code when the APIs it depends on change. [Find out more](https://heyapi.dev/openapi-ts/integrations.html). 29 30 30 - ## Migrating from OpenAPI Typescript Codegen? 31 - 32 - Please read our [migration guide](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). 33 - 34 - ## Contributing 31 + ## Migration Guides 35 32 36 - Want to get involved? Please refer to the [contributing guide](https://heyapi.dev/contributing.html). 33 + [OpenAPI Typescript Codegen](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen).
+8 -2
packages/client-fetch/src/index.ts
··· 106 106 : opts.parseAs) ?? 'json'; 107 107 108 108 let data = await response[parseAs](); 109 - if (parseAs === 'json' && opts.responseTransformer) { 110 - data = await opts.responseTransformer(data); 109 + if (parseAs === 'json') { 110 + if (opts.responseValidator) { 111 + await opts.responseValidator(data); 112 + } 113 + 114 + if (opts.responseTransformer) { 115 + data = await opts.responseTransformer(data); 116 + } 111 117 } 112 118 113 119 return {
+8 -3
packages/client-fetch/src/types.ts
··· 88 88 */ 89 89 querySerializer?: QuerySerializer | QuerySerializerOptions; 90 90 /** 91 - * A function for transforming response data before it's returned to the 92 - * caller function. This is an ideal place to post-process server data, 93 - * e.g. convert date ISO strings into native Date objects. 91 + * A function transforming response data before it's returned. This is useful 92 + * for post-processing data, e.g. converting ISO strings into Date objects. 94 93 */ 95 94 responseTransformer?: (data: unknown) => Promise<unknown>; 95 + /** 96 + * A function validating response data. This is useful if you want to ensure 97 + * the response conforms to the desired shape, so it can be safely passed to 98 + * the transformers and returned to the user. 99 + */ 100 + responseValidator?: (data: unknown) => Promise<unknown>; 96 101 /** 97 102 * Throw an error instead of returning it in the response? 98 103 *
+2 -6
packages/openapi-ts/README.md
··· 27 27 28 28 Automatically update your code when the APIs it depends on change. [Find out more](https://heyapi.dev/openapi-ts/integrations.html). 29 29 30 - ## Migrating from OpenAPI Typescript Codegen? 31 - 32 - Please read our [migration guide](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen). 33 - 34 - ## Contributing 30 + ## Migration Guides 35 31 36 - Want to get involved? Please refer to the [contributing guide](https://heyapi.dev/contributing.html). 32 + [OpenAPI Typescript Codegen](https://heyapi.dev/openapi-ts/migrating.html#openapi-typescript-codegen).
+67 -44
packages/openapi-ts/src/index.ts
··· 11 11 import { parseExperimental, parseLegacy } from './openApi'; 12 12 import type { ClientPlugins, UserPlugins } from './plugins'; 13 13 import { defaultPluginConfigs } from './plugins'; 14 - import type { DefaultPluginConfigs, PluginNames } from './plugins/types'; 14 + import type { 15 + DefaultPluginConfigs, 16 + PluginContext, 17 + PluginNames, 18 + } from './plugins/types'; 15 19 import type { Client } from './types/client'; 16 20 import type { 17 21 ClientConfig, ··· 180 184 return output; 181 185 }; 182 186 183 - const getPluginOrder = ({ 187 + const getPluginsConfig = ({ 184 188 pluginConfigs, 185 189 userPlugins, 190 + userPluginsConfig, 186 191 }: { 187 192 pluginConfigs: DefaultPluginConfigs<ClientPlugins>; 188 193 userPlugins: ReadonlyArray<PluginNames>; 189 - }): Config['pluginOrder'] => { 194 + userPluginsConfig: Config['plugins']; 195 + }): Pick<Config, 'plugins' | 'pluginOrder'> => { 190 196 const circularReferenceTracker = new Set<PluginNames>(); 191 - const visitedNodes = new Set<PluginNames>(); 197 + const pluginOrder = new Set<PluginNames>(); 198 + const plugins: Config['plugins'] = {}; 192 199 193 200 const dfs = (name: PluginNames) => { 194 201 if (circularReferenceTracker.has(name)) { 195 202 throw new Error(`Circular reference detected at '${name}'`); 196 203 } 197 204 198 - if (!visitedNodes.has(name)) { 205 + if (!pluginOrder.has(name)) { 199 206 circularReferenceTracker.add(name); 200 207 201 208 const pluginConfig = pluginConfigs[name]; 202 - 203 209 if (!pluginConfig) { 204 210 throw new Error( 205 211 `🚫 unknown plugin dependency "${name}" - do you need to register a custom plugin with this name?`, 206 212 ); 207 213 } 208 214 209 - for (const dependency of pluginConfig._dependencies || []) { 210 - dfs(dependency); 215 + const defaultOptions = defaultPluginConfigs[name]; 216 + const userOptions = userPluginsConfig[name]; 217 + if (userOptions && defaultOptions) { 218 + const nativePluginOption = Object.keys(userOptions).find((key) => 219 + key.startsWith('_'), 220 + ); 221 + if (nativePluginOption) { 222 + throw new Error( 223 + `🚫 cannot register plugin "${name}" - attempting to override a native plugin option "${nativePluginOption}"`, 224 + ); 225 + } 226 + } 227 + 228 + const config = { 229 + _dependencies: [], 230 + ...defaultOptions, 231 + ...userOptions, 232 + }; 233 + 234 + if (config._infer) { 235 + const context: PluginContext = { 236 + ensureDependency: (dependency) => { 237 + if ( 238 + typeof dependency === 'string' && 239 + !config._dependencies.includes(dependency) 240 + ) { 241 + config._dependencies = [...config._dependencies, dependency]; 242 + } 243 + }, 244 + pluginByTag: (tag) => { 245 + for (const userPlugin of userPlugins) { 246 + const defaultConfig = defaultPluginConfigs[userPlugin]; 247 + if ( 248 + defaultConfig && 249 + defaultConfig._tags?.includes(tag) && 250 + userPlugin !== name 251 + ) { 252 + return userPlugin; 253 + } 254 + } 255 + }, 256 + }; 257 + config._infer(config, context); 211 258 } 212 259 213 - for (const dependency of pluginConfig._optionalDependencies || []) { 214 - if (userPlugins.includes(dependency)) { 215 - dfs(dependency); 216 - } 260 + for (const dependency of config._dependencies) { 261 + dfs(dependency); 217 262 } 218 263 219 264 circularReferenceTracker.delete(name); 220 - visitedNodes.add(name); 265 + pluginOrder.add(name); 266 + 267 + // @ts-expect-error 268 + plugins[name] = config; 221 269 } 222 270 }; 223 271 ··· 225 273 dfs(name); 226 274 } 227 275 228 - return Array.from(visitedNodes); 276 + return { 277 + pluginOrder: Array.from(pluginOrder), 278 + plugins, 279 + }; 229 280 }; 230 281 231 282 const getPlugins = ( ··· 248 299 }) 249 300 .filter(Boolean); 250 301 251 - const pluginOrder = getPluginOrder({ 302 + return getPluginsConfig({ 252 303 pluginConfigs: { 253 304 ...userPluginsConfig, 254 305 ...defaultPluginConfigs, 255 306 }, 256 307 userPlugins, 308 + userPluginsConfig, 257 309 }); 258 - 259 - const plugins = pluginOrder.reduce( 260 - (result, name) => { 261 - const defaultOptions = defaultPluginConfigs[name]; 262 - const userOptions = userPluginsConfig[name]; 263 - if (userOptions && defaultOptions) { 264 - const nativePluginOption = Object.keys(userOptions).find((key) => 265 - key.startsWith('_'), 266 - ); 267 - if (nativePluginOption) { 268 - throw new Error( 269 - `🚫 cannot register plugin "${userOptions.name}" - attempting to override a native plugin option "${nativePluginOption}"`, 270 - ); 271 - } 272 - } 273 - // @ts-expect-error 274 - result[name] = { 275 - ...defaultOptions, 276 - ...userOptions, 277 - }; 278 - return result; 279 - }, 280 - {} as Config['plugins'], 281 - ); 282 - 283 - return { 284 - pluginOrder, 285 - plugins, 286 - }; 287 310 }; 288 311 289 312 const getSpec = async ({ config }: { config: Config }) => {
+12
packages/openapi-ts/src/ir/operation.ts
··· 85 85 }; 86 86 87 87 interface OperationResponsesMap { 88 + /** 89 + * A deduplicated union of all error types. Unknown types are omitted. 90 + */ 88 91 error?: IRSchemaObject; 92 + /** 93 + * An object containing a map of status codes for each error type. 94 + */ 89 95 errors?: IRSchemaObject; 96 + /** 97 + * A deduplicated union of all response types. Unknown types are omitted. 98 + */ 90 99 response?: IRSchemaObject; 100 + /** 101 + * An object containing a map of status codes for each response type. 102 + */ 91 103 responses?: IRSchemaObject; 92 104 } 93 105
+21 -1
packages/openapi-ts/src/plugins/@hey-api/sdk/config.ts
··· 7 7 _dependencies: ['@hey-api/typescript'], 8 8 _handler: handler, 9 9 _handlerLegacy: handlerLegacy, 10 - _optionalDependencies: ['@hey-api/transformers'], 10 + _infer: (config, context) => { 11 + if (config.transformer) { 12 + if (typeof config.transformer === 'boolean') { 13 + config.transformer = context.pluginByTag( 14 + 'transformer', 15 + ) as unknown as typeof config.transformer; 16 + } 17 + 18 + context.ensureDependency(config.transformer); 19 + } 20 + 21 + if (config.validator) { 22 + if (typeof config.validator === 'boolean') { 23 + config.validator = context.pluginByTag( 24 + 'validator', 25 + ) as unknown as typeof config.validator; 26 + } 27 + 28 + context.ensureDependency(config.validator); 29 + } 30 + }, 11 31 asClass: false, 12 32 auth: true, 13 33 name: '@hey-api/sdk',
+69 -53
packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts
··· 11 11 } from '../../../ir/operation'; 12 12 import { escapeComment } from '../../../utils/escape'; 13 13 import { getServiceName } from '../../../utils/postprocess'; 14 - import { irRef } from '../../../utils/ref'; 15 - import { stringCase } from '../../../utils/stringCase'; 16 14 import { transformServiceName } from '../../../utils/transform'; 15 + import { operationIrRef } from '../../shared/utils/ref'; 17 16 import type { Plugin } from '../../types'; 18 - import { operationTransformerIrRef } from '../transformers/plugin'; 17 + import { zodId } from '../../zod/plugin'; 18 + import { 19 + operationTransformerIrRef, 20 + transformersId, 21 + } from '../transformers/plugin'; 19 22 import { serviceFunctionIdentifier } from './plugin-legacy'; 20 23 import type { Config } from './types'; 21 - 22 - interface OperationIRRef { 23 - /** 24 - * Operation ID 25 - */ 26 - id: string; 27 - } 28 - 29 - export const operationIrRef = ({ 30 - id, 31 - type, 32 - }: OperationIRRef & { 33 - type: 'data' | 'error' | 'errors' | 'response' | 'responses'; 34 - }): string => { 35 - let affix = ''; 36 - switch (type) { 37 - case 'data': 38 - affix = 'Data'; 39 - break; 40 - case 'error': 41 - // error union 42 - affix = 'Error'; 43 - break; 44 - case 'errors': 45 - // errors map 46 - affix = 'Errors'; 47 - break; 48 - case 'response': 49 - // response union 50 - affix = 'Response'; 51 - break; 52 - case 'responses': 53 - // responses map 54 - affix = 'Responses'; 55 - break; 56 - } 57 - return `${irRef}${stringCase({ 58 - case: 'PascalCase', 59 - value: id, 60 - })}-${affix}`; 61 - }; 62 24 63 25 export const operationOptionsType = ({ 64 26 importedType, ··· 322 284 } 323 285 } 324 286 325 - const fileTransformers = context.file({ id: 'transformers' }); 326 - if (fileTransformers) { 327 - const identifier = fileTransformers.identifier({ 328 - $ref: operationTransformerIrRef({ id: operation.id, type: 'response' }), 287 + if (plugin.transformer === '@hey-api/transformers') { 288 + const identifierTransformer = context 289 + .file({ id: transformersId })! 290 + .identifier({ 291 + $ref: operationTransformerIrRef({ id: operation.id, type: 'response' }), 292 + namespace: 'value', 293 + }); 294 + 295 + if (identifierTransformer.name) { 296 + file.import({ 297 + module: file.relativePathToFile({ 298 + context, 299 + id: transformersId, 300 + }), 301 + name: identifierTransformer.name, 302 + }); 303 + 304 + requestOptions.push({ 305 + key: 'responseTransformer', 306 + value: identifierTransformer.name, 307 + }); 308 + } 309 + } 310 + 311 + if (plugin.validator === 'zod') { 312 + const identifierSchema = context.file({ id: zodId })!.identifier({ 313 + $ref: operationIrRef({ 314 + case: 'camelCase', 315 + id: operation.id, 316 + type: 'response', 317 + }), 329 318 namespace: 'value', 330 319 }); 331 - if (identifier.name) { 320 + 321 + if (identifierSchema.name) { 332 322 file.import({ 333 - module: file.relativePathToFile({ context, id: 'transformers' }), 334 - name: identifier.name, 323 + module: file.relativePathToFile({ 324 + context, 325 + id: zodId, 326 + }), 327 + name: identifierSchema.name, 335 328 }); 329 + 336 330 requestOptions.push({ 337 - key: 'responseTransformer', 338 - value: identifier.name, 331 + key: 'responseValidator', 332 + value: compiler.arrowFunction({ 333 + async: true, 334 + parameters: [ 335 + { 336 + name: 'data', 337 + }, 338 + ], 339 + statements: [ 340 + compiler.returnStatement({ 341 + expression: compiler.awaitExpression({ 342 + expression: compiler.callExpression({ 343 + functionName: compiler.propertyAccessExpression({ 344 + expression: compiler.identifier({ 345 + text: identifierSchema.name, 346 + }), 347 + name: compiler.identifier({ text: 'parseAsync' }), 348 + }), 349 + parameters: [compiler.identifier({ text: 'data' })], 350 + }), 351 + }), 352 + }), 353 + ], 354 + }), 339 355 }); 340 356 } 341 357 }
+28
packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts
··· 77 77 * @default '{{name}}Service' 78 78 */ 79 79 serviceNameBuilder?: string; 80 + /** 81 + * Transform response data before returning. This is useful if you want to 82 + * convert for example ISO strings into Date objects. However, transformation 83 + * adds runtime overhead, so it's not recommended to use unless necessary. 84 + * 85 + * You can customize the selected transformer output through its plugin. You 86 + * can also set `transformer` to `true` to automatically choose the 87 + * transformer from your defined plugins. 88 + * 89 + * @default false 90 + */ 91 + transformer?: '@hey-api/transformers' | boolean; 92 + /** 93 + * **This feature works only with the experimental parser** 94 + * 95 + * Validate response data against schema before returning. This is useful 96 + * if you want to ensure the response conforms to a desired shape. However, 97 + * validation adds runtime overhead, so it's not recommended to use unless 98 + * absolutely necessary. 99 + * 100 + * Ensure you have declared the selected library as a dependency to avoid 101 + * errors. You can customize the selected validator output through its 102 + * plugin. You can also set `validator` to `true` to automatically choose 103 + * the validator from your defined plugins. 104 + * 105 + * @default false 106 + */ 107 + validator?: 'zod' | boolean; 80 108 }
+1
packages/openapi-ts/src/plugins/@hey-api/transformers/config.ts
··· 7 7 _dependencies: ['@hey-api/typescript'], 8 8 _handler: handler, 9 9 _handlerLegacy: handlerLegacy, 10 + _tags: ['transformer'], 10 11 dates: true, 11 12 name: '@hey-api/transformers', 12 13 output: 'transformers',
+2 -2
packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts
··· 6 6 import { operationResponsesMap } from '../../../ir/operation'; 7 7 import { irRef } from '../../../utils/ref'; 8 8 import { stringCase } from '../../../utils/stringCase'; 9 + import { operationIrRef } from '../../shared/utils/ref'; 9 10 import type { Plugin } from '../../types'; 10 - import { operationIrRef } from '../sdk/plugin'; 11 11 import type { Config } from './types'; 12 12 13 13 interface OperationIRRef { ··· 68 68 $ref: string; 69 69 }): string => schemaIrRef({ $ref, type: 'response' }); 70 70 71 - const transformersId = 'transformers'; 71 + export const transformersId = 'transformers'; 72 72 const dataVariableName = 'data'; 73 73 74 74 const ensureStatements = (
+1 -1
packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts
··· 15 15 import { digitsRegExp } from '../../../utils/regexp'; 16 16 import { stringCase } from '../../../utils/stringCase'; 17 17 import { fieldName } from '../../shared/utils/case'; 18 + import { operationIrRef } from '../../shared/utils/ref'; 18 19 import type { Plugin } from '../../types'; 19 - import { operationIrRef } from '../sdk/plugin'; 20 20 import type { Config } from './types'; 21 21 22 22 interface SchemaWithType<T extends Required<IRSchemaObject>['type']>
+2 -4
packages/openapi-ts/src/plugins/@tanstack/query-core/plugin.ts
··· 16 16 import { getConfig } from '../../../utils/config'; 17 17 import { getServiceName } from '../../../utils/postprocess'; 18 18 import { transformServiceName } from '../../../utils/transform'; 19 - import { 20 - operationIrRef, 21 - operationOptionsType, 22 - } from '../../@hey-api/sdk/plugin'; 19 + import { operationOptionsType } from '../../@hey-api/sdk/plugin'; 23 20 import { serviceFunctionIdentifier } from '../../@hey-api/sdk/plugin-legacy'; 24 21 import { schemaToType } from '../../@hey-api/typescript/plugin'; 22 + import { operationIrRef } from '../../shared/utils/ref'; 25 23 import type { Plugin } from '../../types'; 26 24 import type { Config as AngularQueryConfig } from '../angular-query-experimental'; 27 25 import type { Config as ReactQueryConfig } from '../react-query';
+1 -1
packages/openapi-ts/src/plugins/fastify/plugin.ts
··· 5 5 import type { IROperationObject } from '../../ir/ir'; 6 6 import { operationResponsesMap } from '../../ir/operation'; 7 7 import { hasParameterGroupObjectRequired } from '../../ir/parameter'; 8 - import { operationIrRef } from '../@hey-api/sdk/plugin'; 8 + import { operationIrRef } from '../shared/utils/ref'; 9 9 import type { Plugin } from '../types'; 10 10 import type { Config } from './types'; 11 11
+46
packages/openapi-ts/src/plugins/shared/utils/ref.ts
··· 1 + import type { StringCase } from '../../../types/config'; 2 + import { irRef } from '../../../utils/ref'; 3 + import { stringCase } from '../../../utils/stringCase'; 4 + 5 + interface OperationIRRef { 6 + /** 7 + * Operation ID 8 + */ 9 + id: string; 10 + } 11 + 12 + export const operationIrRef = ({ 13 + case: _case = 'PascalCase', 14 + id, 15 + type, 16 + }: OperationIRRef & { 17 + readonly case?: StringCase; 18 + type: 'data' | 'error' | 'errors' | 'response' | 'responses'; 19 + }): string => { 20 + let affix = ''; 21 + switch (type) { 22 + case 'data': 23 + affix = 'Data'; 24 + break; 25 + case 'error': 26 + // error union 27 + affix = 'Error'; 28 + break; 29 + case 'errors': 30 + // errors map 31 + affix = 'Errors'; 32 + break; 33 + case 'response': 34 + // response union 35 + affix = 'Response'; 36 + break; 37 + case 'responses': 38 + // responses map 39 + affix = 'Responses'; 40 + break; 41 + } 42 + return `${irRef}${stringCase({ 43 + case: _case, 44 + value: id, 45 + })}-${affix}`; 46 + };
+29 -12
packages/openapi-ts/src/plugins/types.d.ts
··· 3 3 import type { Client } from '../types/client'; 4 4 import type { Files } from '../types/utils'; 5 5 6 + type OmitUnderscoreKeys<T> = { 7 + [K in keyof T as K extends `_${string}` ? never : K]: T[K]; 8 + }; 9 + 6 10 export type PluginNames = 7 11 | '@hey-api/schemas' 8 12 | '@hey-api/sdk' ··· 15 19 | '@tanstack/vue-query' 16 20 | 'fastify' 17 21 | 'zod'; 22 + 23 + type PluginTag = 'transformer' | 'validator'; 24 + 25 + export interface PluginContext { 26 + ensureDependency: (name: PluginNames | true) => void; 27 + pluginByTag: (tag: PluginTag) => PluginNames | undefined; 28 + } 18 29 19 30 interface BaseConfig { 20 31 // eslint-disable-next-line @typescript-eslint/ban-types ··· 22 33 output?: string; 23 34 } 24 35 25 - interface Dependencies { 36 + interface Meta<Config extends BaseConfig> { 26 37 /** 27 - * Required dependencies will be always processed, regardless of whether 28 - * a user defines them in their `plugins` config. 38 + * Dependency plugins will be always processed, regardless of whether user 39 + * explicitly defines them in their `plugins` config. 29 40 */ 30 41 _dependencies?: ReadonlyArray<PluginNames>; 31 42 /** 32 - * Optional dependencies are not processed unless a user explicitly defines 33 - * them in their `plugins` config. 43 + * Allows overriding config before it's sent to the parser. An example is 44 + * defining `validator` as `true` and the plugin figures out which plugin 45 + * should be used for validation. 46 + */ 47 + _infer?: ( 48 + config: Config & Omit<Meta<Config>, '_infer'>, 49 + context: PluginContext, 50 + ) => void; 51 + /** 52 + * Optional tags can be used to help with deciding plugin order and inferring 53 + * plugin configuration options. 34 54 */ 35 - _optionalDependencies?: ReadonlyArray<PluginNames>; 55 + _tags?: ReadonlyArray<PluginTag>; 36 56 } 37 57 38 58 export type DefaultPluginConfigs<T> = { 39 59 [K in PluginNames]: BaseConfig & 40 - Dependencies & { 60 + Meta<any> & { 41 61 _handler: Plugin.Handler<Required<Extract<T, { name: K }>>>; 42 62 _handlerLegacy: Plugin.LegacyHandler<Required<Extract<T, { name: K }>>>; 43 63 }; ··· 48 68 */ 49 69 export namespace Plugin { 50 70 export type Config<Config extends BaseConfig> = Config & 51 - Dependencies & { 71 + Meta<Config> & { 52 72 _handler: Plugin.Handler<Config>; 53 73 _handlerLegacy: Plugin.LegacyHandler<Config>; 54 74 }; ··· 65 85 plugin: Plugin.Instance<Config>; 66 86 }) => void; 67 87 68 - export type Instance<Config extends BaseConfig> = Omit< 69 - Config, 70 - '_dependencies' | '_handler' | '_handlerLegacy' | '_optionalDependencies' 71 - > & 88 + export type Instance<Config extends BaseConfig> = OmitUnderscoreKeys<Config> & 72 89 Pick<Required<Config>, 'output'>; 73 90 74 91 /**
+1
packages/openapi-ts/src/plugins/zod/config.ts
··· 5 5 export const defaultConfig: Plugin.Config<Config> = { 6 6 _handler: handler, 7 7 _handlerLegacy: () => {}, 8 + _tags: ['validator'], 8 9 name: 'zod', 9 10 output: 'zod', 10 11 };
+115 -88
packages/openapi-ts/src/plugins/zod/plugin.ts
··· 2 2 3 3 import { compiler } from '../../compiler'; 4 4 import type { IRContext } from '../../ir/context'; 5 - import type { IRSchemaObject } from '../../ir/ir'; 5 + import type { IROperationObject, IRSchemaObject } from '../../ir/ir'; 6 + import { operationResponsesMap } from '../../ir/operation'; 6 7 import { deduplicateSchema } from '../../ir/schema'; 7 - import { isRefOpenApiComponent } from '../../utils/ref'; 8 8 import { digitsRegExp } from '../../utils/regexp'; 9 + import { operationIrRef } from '../shared/utils/ref'; 9 10 import type { Plugin } from '../types'; 10 11 import type { Config } from './types'; 11 12 ··· 19 20 hasCircularReference: boolean; 20 21 } 21 22 22 - const zodId = 'zod'; 23 + export const zodId = 'zod'; 23 24 24 25 // frequently used identifiers 25 26 const defaultIdentifier = compiler.identifier({ text: 'default' }); 27 + const intersectionIdentifier = compiler.identifier({ text: 'intersection' }); 26 28 const lazyIdentifier = compiler.identifier({ text: 'lazy' }); 29 + const mergeIdentifier = compiler.identifier({ text: 'merge' }); 27 30 const optionalIdentifier = compiler.identifier({ text: 'optional' }); 28 31 const readonlyIdentifier = compiler.identifier({ text: 'readonly' }); 32 + const unionIdentifier = compiler.identifier({ text: 'union' }); 29 33 const zIdentifier = compiler.identifier({ text: 'z' }); 30 34 31 - const nameTransformer = (name: string) => `z${name}`; 35 + const nameTransformer = (name: string) => `z-${name}`; 32 36 33 37 const arrayTypeToZodSchema = ({ 34 38 context, 35 - namespace, 36 39 result, 37 40 schema, 38 41 }: { 39 42 context: IRContext; 40 - namespace: Array<ts.Statement>; 41 43 result: Result; 42 44 schema: SchemaWithType<'array'>; 43 45 }): ts.CallExpression => { ··· 54 56 parameters: [ 55 57 unknownTypeToZodSchema({ 56 58 context, 57 - namespace, 58 59 schema: { 59 60 type: 'unknown', 60 61 }, ··· 68 69 const itemExpressions = schema.items!.map((item) => 69 70 schemaToZodSchema({ 70 71 context, 71 - namespace, 72 72 result, 73 73 schema: item, 74 74 }), ··· 95 95 parameters: [ 96 96 unknownTypeToZodSchema({ 97 97 context, 98 - namespace, 99 98 schema: { 100 99 type: 'unknown', 101 100 }, ··· 142 141 schema, 143 142 }: { 144 143 context: IRContext; 145 - namespace: Array<ts.Statement>; 146 144 schema: SchemaWithType<'boolean'>; 147 145 }) => { 148 146 if (schema.const !== undefined) { ··· 163 161 164 162 const enumTypeToZodSchema = ({ 165 163 context, 166 - namespace, 167 164 schema, 168 165 }: { 169 166 context: IRContext; 170 - namespace: Array<ts.Statement>; 171 167 schema: SchemaWithType<'enum'>; 172 168 }): ts.CallExpression => { 173 169 const enumMembers: Array<ts.LiteralExpression> = []; ··· 186 182 if (!enumMembers.length) { 187 183 return unknownTypeToZodSchema({ 188 184 context, 189 - namespace, 190 185 schema: { 191 186 type: 'unknown', 192 187 }, ··· 213 208 schema, 214 209 }: { 215 210 context: IRContext; 216 - namespace: Array<ts.Statement>; 217 211 schema: SchemaWithType<'never'>; 218 212 }) => { 219 213 const expression = compiler.callExpression({ ··· 229 223 schema, 230 224 }: { 231 225 context: IRContext; 232 - namespace: Array<ts.Statement>; 233 226 schema: SchemaWithType<'null'>; 234 227 }) => { 235 228 const expression = compiler.callExpression({ ··· 245 238 schema, 246 239 }: { 247 240 context: IRContext; 248 - namespace: Array<ts.Statement>; 249 241 schema: SchemaWithType<'number'>; 250 242 }) => { 251 243 let numberExpression = compiler.callExpression({ ··· 307 299 308 300 const objectTypeToZodSchema = ({ 309 301 context, 310 - // namespace, 311 302 result, 312 303 schema, 313 304 }: { 314 305 context: IRContext; 315 - namespace: Array<ts.Statement>; 316 306 result: Result; 317 307 schema: SchemaWithType<'object'>; 318 308 }) => { ··· 413 403 // name: 'key', 414 404 // type: schemaToZodSchema({ 415 405 // context, 416 - // namespace, 417 406 // schema: 418 407 // indexPropertyItems.length === 1 419 408 // ? indexPropertyItems[0] ··· 444 433 schema, 445 434 }: { 446 435 context: IRContext; 447 - namespace: Array<ts.Statement>; 448 436 schema: SchemaWithType<'string'>; 449 437 }) => { 450 438 let stringExpression = compiler.callExpression({ ··· 539 527 schema, 540 528 }: { 541 529 context: IRContext; 542 - namespace: Array<ts.Statement>; 543 530 schema: SchemaWithType<'undefined'>; 544 531 }) => { 545 532 const expression = compiler.callExpression({ ··· 555 542 schema, 556 543 }: { 557 544 context: IRContext; 558 - namespace: Array<ts.Statement>; 559 545 schema: SchemaWithType<'unknown'>; 560 546 }) => { 561 547 const expression = compiler.callExpression({ ··· 571 557 schema, 572 558 }: { 573 559 context: IRContext; 574 - namespace: Array<ts.Statement>; 575 560 schema: SchemaWithType<'void'>; 576 561 }) => { 577 562 const expression = compiler.callExpression({ ··· 585 570 586 571 const schemaTypeToZodSchema = ({ 587 572 context, 588 - namespace, 589 573 result, 590 574 schema, 591 575 }: { 592 576 context: IRContext; 593 - namespace: Array<ts.Statement>; 594 577 result: Result; 595 578 schema: IRSchemaObject; 596 579 }): ts.Expression => { ··· 598 581 case 'array': 599 582 return arrayTypeToZodSchema({ 600 583 context, 601 - namespace, 602 584 result, 603 585 schema: schema as SchemaWithType<'array'>, 604 586 }); 605 587 case 'boolean': 606 588 return booleanTypeToZodSchema({ 607 589 context, 608 - namespace, 609 590 schema: schema as SchemaWithType<'boolean'>, 610 591 }); 611 592 case 'enum': 612 593 return enumTypeToZodSchema({ 613 594 context, 614 - namespace, 615 595 schema: schema as SchemaWithType<'enum'>, 616 596 }); 617 597 case 'never': 618 598 return neverTypeToZodSchema({ 619 599 context, 620 - namespace, 621 600 schema: schema as SchemaWithType<'never'>, 622 601 }); 623 602 case 'null': 624 603 return nullTypeToZodSchema({ 625 604 context, 626 - namespace, 627 605 schema: schema as SchemaWithType<'null'>, 628 606 }); 629 607 case 'number': 630 608 return numberTypeToZodSchema({ 631 609 context, 632 - namespace, 633 610 schema: schema as SchemaWithType<'number'>, 634 611 }); 635 612 case 'object': 636 613 return objectTypeToZodSchema({ 637 614 context, 638 - namespace, 639 615 result, 640 616 schema: schema as SchemaWithType<'object'>, 641 617 }); 642 618 case 'string': 643 619 return stringTypeToZodSchema({ 644 620 context, 645 - namespace, 646 621 schema: schema as SchemaWithType<'string'>, 647 622 }); 648 623 case 'tuple': 649 624 // TODO: parser - temporary unknown while not handled 650 625 return unknownTypeToZodSchema({ 651 626 context, 652 - namespace, 653 627 schema: { 654 628 type: 'unknown', 655 629 }, ··· 657 631 // TODO: parser - handle tuple 658 632 // return tupleTypeToIdentifier({ 659 633 // context, 660 - // namespace, 661 634 // schema: schema as SchemaWithType<'tuple'>, 662 635 // }); 663 636 case 'undefined': 664 637 return undefinedTypeToZodSchema({ 665 638 context, 666 - namespace, 667 639 schema: schema as SchemaWithType<'undefined'>, 668 640 }); 669 641 case 'unknown': 670 642 return unknownTypeToZodSchema({ 671 643 context, 672 - namespace, 673 644 schema: schema as SchemaWithType<'unknown'>, 674 645 }); 675 646 case 'void': 676 647 return voidTypeToZodSchema({ 677 648 context, 678 - namespace, 679 649 schema: schema as SchemaWithType<'void'>, 680 650 }); 681 651 } 682 652 }; 683 653 654 + const operationToZodSchema = ({ 655 + context, 656 + operation, 657 + result, 658 + }: { 659 + context: IRContext; 660 + operation: IROperationObject; 661 + result: Result; 662 + }) => { 663 + if (operation.responses) { 664 + const { response } = operationResponsesMap(operation); 665 + 666 + if (response) { 667 + schemaToZodSchema({ 668 + $ref: operationIrRef({ 669 + case: 'camelCase', 670 + id: operation.id, 671 + type: 'response', 672 + }), 673 + context, 674 + result, 675 + schema: response, 676 + }); 677 + } 678 + } 679 + }; 680 + 684 681 const schemaToZodSchema = ({ 685 682 $ref, 686 683 context, 687 - // TODO: parser - remove namespace, it's a type plugin construct 688 - namespace = [], 689 684 result, 690 685 schema, 691 686 }: { 687 + /** 688 + * When $ref is supplied, a node will be emitted to the file. 689 + */ 692 690 $ref?: string; 693 691 context: IRContext; 694 - namespace?: Array<ts.Statement>; 695 692 result: Result; 696 693 schema: IRSchemaObject; 697 694 }): ts.Expression => { ··· 703 700 if ($ref) { 704 701 result.circularReferenceTracker.add($ref); 705 702 706 - // emit nodes only if $ref points to a reusable component 707 - if (isRefOpenApiComponent($ref)) { 708 - identifier = file.identifier({ 709 - $ref, 710 - create: true, 711 - nameTransformer, 712 - namespace: 'value', 713 - }); 714 - } 703 + identifier = file.identifier({ 704 + $ref, 705 + create: true, 706 + nameTransformer, 707 + namespace: 'value', 708 + }); 715 709 } 716 710 717 711 if (schema.$ref) { ··· 770 764 } else if (schema.type) { 771 765 expression = schemaTypeToZodSchema({ 772 766 context, 773 - namespace, 774 767 result, 775 768 schema, 776 769 }); 777 770 } else if (schema.items) { 778 - // TODO: parser - temporary unknown while not handled 779 - expression = unknownTypeToZodSchema({ 780 - context, 781 - namespace, 782 - schema: { 783 - type: 'unknown', 784 - }, 785 - }); 771 + schema = deduplicateSchema({ schema }); 772 + 773 + if (schema.items) { 774 + const itemTypes = schema.items.map((item) => 775 + schemaToZodSchema({ 776 + context, 777 + result, 778 + schema: item, 779 + }), 780 + ); 786 781 787 - // TODO: parser - handle items 788 - // schema = deduplicateSchema({ schema }); 789 - // if (schema.items) { 790 - // const itemTypes = schema.items.map((item) => 791 - // schemaToZodSchema({ 792 - // context, 793 - // namespace, 794 - // schema: item, 795 - // }), 796 - // ); 797 - // expression = 798 - // schema.logicalOperator === 'and' 799 - // ? compiler.typeIntersectionNode({ types: itemTypes }) 800 - // : compiler.typeUnionNode({ types: itemTypes }); 801 - // } else { 802 - // expression = schemaToZodSchema({ 803 - // context, 804 - // namespace, 805 - // schema, 806 - // }); 807 - // } 782 + if (schema.logicalOperator === 'and') { 783 + const firstSchema = schema.items[0]; 784 + // we want to add an intersection, but not every schema can use the same API. 785 + // if the first item contains another array or not an object, we cannot use 786 + // `.merge()` as that does not exist on `.union()` and non-object schemas. 787 + if ( 788 + firstSchema.logicalOperator === 'or' || 789 + (firstSchema.type && firstSchema.type !== 'object') 790 + ) { 791 + expression = compiler.callExpression({ 792 + functionName: compiler.propertyAccessExpression({ 793 + expression: zIdentifier, 794 + name: intersectionIdentifier, 795 + }), 796 + parameters: itemTypes, 797 + }); 798 + } else { 799 + expression = itemTypes[0]; 800 + itemTypes.slice(1).forEach((item) => { 801 + expression = compiler.callExpression({ 802 + functionName: compiler.propertyAccessExpression({ 803 + expression: expression!, 804 + name: mergeIdentifier, 805 + }), 806 + parameters: [item], 807 + }); 808 + }); 809 + } 810 + } else { 811 + expression = compiler.callExpression({ 812 + functionName: compiler.propertyAccessExpression({ 813 + expression: zIdentifier, 814 + name: unionIdentifier, 815 + }), 816 + parameters: [ 817 + compiler.arrayLiteralExpression({ 818 + elements: itemTypes, 819 + }), 820 + ], 821 + }); 822 + } 823 + } else { 824 + expression = schemaToZodSchema({ 825 + context, 826 + result, 827 + schema, 828 + }); 829 + } 808 830 } else { 809 831 // catch-all fallback for failed schemas 810 832 expression = schemaTypeToZodSchema({ 811 833 context, 812 - namespace, 813 834 result, 814 835 schema: { 815 836 type: 'unknown', ··· 843 864 export const handler: Plugin.Handler<Config> = ({ context, plugin }) => { 844 865 const file = context.createFile({ 845 866 id: zodId, 867 + identifierCase: 'camelCase', 846 868 path: plugin.output, 847 869 }); 848 870 ··· 851 873 name: 'z', 852 874 }); 853 875 854 - // context.subscribe('operation', ({ operation }) => { 855 - // schemaToZodSchema({ 856 - // $ref, 857 - // context, 858 - // schema, 859 - // }); 860 - // }); 876 + context.subscribe('operation', ({ operation }) => { 877 + const result: Result = { 878 + circularReferenceTracker: new Set(), 879 + hasCircularReference: false, 880 + }; 881 + 882 + operationToZodSchema({ 883 + context, 884 + operation, 885 + result, 886 + }); 887 + }); 861 888 862 889 context.subscribe('schema', ({ $ref, schema }) => { 863 890 const result: Result = {
+327 -46
packages/openapi-ts/test/__snapshots__/3.0.x/plugins/zod/default/zod.gen.ts
··· 4 4 5 5 export const z400 = z.string(); 6 6 7 - export const zcamelCaseCommentWithBreaks = z.number(); 7 + export const zCamelCaseCommentWithBreaks = z.number(); 8 8 9 9 export const zCommentWithBreaks = z.number(); 10 10 ··· 26 26 27 27 export const zSimpleString = z.string(); 28 28 29 - export const zNonAsciiStringæøåÆØÅöôêÊ字符串 = z.string(); 29 + export const zNonAsciiStringæøåÆøÅöôêÊ字符串 = z.string(); 30 30 31 31 export const zSimpleFile = z.string(); 32 32 ··· 34 34 prop: z.string().optional() 35 35 }); 36 36 37 - export const zSimpleStringWithPattern = z.unknown(); 37 + export const zSimpleStringWithPattern = z.union([ 38 + z.string().max(64), 39 + z.null() 40 + ]); 38 41 39 42 export const zEnumWithStrings = z.enum([ 40 43 'Success', ··· 75 78 }))); 76 79 77 80 export const zArrayWithProperties = z.array(z.object({ 78 - '16x16': zcamelCaseCommentWithBreaks.optional(), 81 + '16x16': zCamelCaseCommentWithBreaks.optional(), 79 82 bar: z.string().optional() 80 83 })); 81 84 ··· 120 123 prop: z.string().optional() 121 124 }); 122 125 123 - export const zModel_From_Zendesk = z.string(); 126 + export const zModelFromZendesk = z.string(); 124 127 125 128 export const zModelWithNullableString = z.object({ 126 - nullableProp1: z.unknown().optional(), 127 - nullableRequiredProp1: z.unknown(), 128 - nullableProp2: z.unknown().optional(), 129 - nullableRequiredProp2: z.unknown(), 129 + nullableProp1: z.union([ 130 + z.string(), 131 + z.null() 132 + ]).optional(), 133 + nullableRequiredProp1: z.union([ 134 + z.string(), 135 + z.null() 136 + ]), 137 + nullableProp2: z.union([ 138 + z.string(), 139 + z.null() 140 + ]).optional(), 141 + nullableRequiredProp2: z.union([ 142 + z.string(), 143 + z.null() 144 + ]), 130 145 'foo_bar-enum': z.enum([ 131 146 'Success', 132 147 'Warning', ··· 184 199 prop: z.object({ 185 200 required: z.string(), 186 201 requiredAndReadOnly: z.string().readonly(), 187 - requiredAndNullable: z.unknown(), 202 + requiredAndNullable: z.union([ 203 + z.string(), 204 + z.null() 205 + ]), 188 206 string: z.string().optional(), 189 207 number: z.number().optional(), 190 208 boolean: z.boolean().optional(), ··· 221 239 prop: z.string().optional() 222 240 }); 223 241 242 + export const zModelWithCircularReference: z.ZodTypeAny = z.object({ 243 + prop: z.lazy(() => { 244 + return zModelWithCircularReference; 245 + }).optional() 246 + }); 247 + 224 248 export const zCompositionWithOneOf = z.object({ 225 - propA: z.unknown().optional() 249 + propA: z.union([ 250 + zModelWithString, 251 + zModelWithEnum, 252 + zModelWithArray, 253 + zModelWithDictionary 254 + ]).optional() 226 255 }); 227 256 228 257 export const zCompositionWithOneOfAnonymous = z.object({ 229 - propA: z.unknown().optional() 258 + propA: z.union([ 259 + z.object({ 260 + propA: z.string().optional() 261 + }), 262 + z.string(), 263 + z.number() 264 + ]).optional() 230 265 }); 231 266 232 267 export const zModelCircle = z.object({ ··· 239 274 sideLength: z.number().optional() 240 275 }); 241 276 242 - export const zCompositionWithOneOfDiscriminator = z.unknown(); 277 + export const zCompositionWithOneOfDiscriminator = z.union([ 278 + z.object({ 279 + kind: z.string().optional() 280 + }).merge(zModelCircle), 281 + z.object({ 282 + kind: z.string().optional() 283 + }).merge(zModelSquare) 284 + ]); 243 285 244 286 export const zCompositionWithAnyOf = z.object({ 245 - propA: z.unknown().optional() 287 + propA: z.union([ 288 + zModelWithString, 289 + zModelWithEnum, 290 + zModelWithArray, 291 + zModelWithDictionary 292 + ]).optional() 246 293 }); 247 294 248 295 export const zCompositionWithAnyOfAnonymous = z.object({ 249 - propA: z.unknown().optional() 296 + propA: z.union([ 297 + z.object({ 298 + propA: z.string().optional() 299 + }), 300 + z.string(), 301 + z.number() 302 + ]).optional() 250 303 }); 251 304 252 305 export const zCompositionWithNestedAnyAndTypeNull = z.object({ 253 - propA: z.unknown().optional() 306 + propA: z.union([ 307 + z.array(z.union([ 308 + zModelWithDictionary, 309 + z.null() 310 + ])), 311 + z.array(z.union([ 312 + zModelWithArray, 313 + z.null() 314 + ])) 315 + ]).optional() 254 316 }); 255 317 256 - export const z3e_num_1Период = z.enum([ 318 + export const z3eNum1Период = z.enum([ 257 319 'Bird', 258 320 'Dog' 259 321 ]); ··· 263 325 ]); 264 326 265 327 export const zCompositionWithNestedAnyOfAndNull = z.object({ 266 - propA: z.unknown().optional() 328 + propA: z.union([ 329 + z.array(z.unknown()), 330 + z.null() 331 + ]).optional() 267 332 }); 268 333 269 334 export const zCompositionWithOneOfAndNullable = z.object({ 270 - propA: z.unknown().optional() 335 + propA: z.union([ 336 + z.object({ 337 + boolean: z.boolean().optional() 338 + }), 339 + zModelWithEnum, 340 + zModelWithArray, 341 + zModelWithDictionary, 342 + z.null() 343 + ]).optional() 271 344 }); 272 345 273 346 export const zCompositionWithOneOfAndSimpleDictionary = z.object({ 274 - propA: z.unknown().optional() 347 + propA: z.union([ 348 + z.boolean(), 349 + z.object({}) 350 + ]).optional() 275 351 }); 276 352 277 353 export const zCompositionWithOneOfAndSimpleArrayDictionary = z.object({ 278 - propA: z.unknown().optional() 354 + propA: z.union([ 355 + z.boolean(), 356 + z.object({}) 357 + ]).optional() 279 358 }); 280 359 281 360 export const zCompositionWithOneOfAndComplexArrayDictionary = z.object({ 282 - propA: z.unknown().optional() 361 + propA: z.union([ 362 + z.boolean(), 363 + z.object({}) 364 + ]).optional() 283 365 }); 284 366 285 367 export const zCompositionWithAllOfAndNullable = z.object({ 286 - propA: z.unknown().optional() 368 + propA: z.union([ 369 + z.object({ 370 + boolean: z.boolean().optional() 371 + }).merge(zModelWithEnum).merge(zModelWithArray).merge(zModelWithDictionary), 372 + z.null() 373 + ]).optional() 287 374 }); 288 375 289 376 export const zCompositionWithAnyOfAndNullable = z.object({ 290 - propA: z.unknown().optional() 377 + propA: z.union([ 378 + z.object({ 379 + boolean: z.boolean().optional() 380 + }), 381 + zModelWithEnum, 382 + zModelWithArray, 383 + zModelWithDictionary, 384 + z.null() 385 + ]).optional() 291 386 }); 292 387 293 388 export const zCompositionBaseModel = z.object({ ··· 295 390 lastname: z.string().optional() 296 391 }); 297 392 298 - export const zCompositionExtendedModel = z.unknown(); 393 + export const zCompositionExtendedModel = zCompositionBaseModel.merge(z.object({ 394 + age: z.number(), 395 + firstName: z.string(), 396 + lastname: z.string() 397 + })); 299 398 300 399 export const zModelWithProperties = z.object({ 301 400 required: z.string(), 302 401 requiredAndReadOnly: z.string().readonly(), 303 - requiredAndNullable: z.unknown(), 402 + requiredAndNullable: z.union([ 403 + z.string(), 404 + z.null() 405 + ]), 304 406 string: z.string().optional(), 305 407 number: z.number().optional(), 306 408 boolean: z.boolean().optional(), ··· 313 415 }); 314 416 315 417 export const zModelWithNestedProperties = z.object({ 316 - first: z.unknown().readonly() 418 + first: z.union([ 419 + z.object({ 420 + second: z.union([ 421 + z.object({ 422 + third: z.union([ 423 + z.string(), 424 + z.null() 425 + ]).readonly() 426 + }), 427 + z.null() 428 + ]).readonly() 429 + }), 430 + z.null() 431 + ]).readonly() 317 432 }); 318 433 319 434 export const zModelWithDuplicateProperties = z.object({ ··· 332 447 propC: zModelWithString.optional() 333 448 }); 334 449 335 - export const zModelThatExtends = z.unknown(); 450 + export const zModelThatExtends = zModelWithString.merge(z.object({ 451 + propExtendsA: z.string().optional(), 452 + propExtendsB: zModelWithString.optional() 453 + })); 336 454 337 - export const zModelThatExtendsExtends = z.unknown(); 455 + export const zModelThatExtendsExtends = zModelWithString.merge(zModelThatExtends).merge(z.object({ 456 + propExtendsC: z.string().optional(), 457 + propExtendsD: zModelWithString.optional() 458 + })); 338 459 339 460 export const zModelWithPattern = z.object({ 340 461 key: z.string().max(64), ··· 356 477 file: z.string().url().readonly().optional() 357 478 }); 358 479 359 - export const zdefault = z.object({ 480 + export const zDefault = z.object({ 360 481 name: z.string().optional() 361 482 }); 362 483 ··· 388 509 }); 389 510 390 511 export const zNestedAnyOfArraysNullable = z.object({ 391 - nullableArray: z.unknown().optional() 512 + nullableArray: z.union([ 513 + z.array(z.unknown()), 514 + z.null() 515 + ]).optional() 392 516 }); 393 517 394 - export const zCompositionWithOneOfAndProperties = z.unknown(); 518 + export const zCompositionWithOneOfAndProperties = z.intersection(z.union([ 519 + z.object({ 520 + foo: z.unknown() 521 + }), 522 + z.object({ 523 + bar: zNonAsciiStringæøåÆøÅöôêÊ字符串 524 + }) 525 + ]), z.object({ 526 + baz: z.union([ 527 + z.number().gte(0), 528 + z.null() 529 + ]), 530 + qux: z.number().gte(0) 531 + })); 395 532 396 - export const zNullableObject = z.unknown(); 533 + export const zNullableObject = z.union([ 534 + z.object({ 535 + foo: z.string().optional() 536 + }), 537 + z.null() 538 + ]); 397 539 398 540 export const zCharactersInDescription = z.string(); 399 541 ··· 401 543 data: zNullableObject.optional() 402 544 }); 403 545 404 - export const zModelWithOneOfEnum = z.unknown(); 546 + export const zModelWithOneOfEnum = z.union([ 547 + z.object({ 548 + foo: z.enum([ 549 + 'Bar' 550 + ]) 551 + }), 552 + z.object({ 553 + foo: z.enum([ 554 + 'Baz' 555 + ]) 556 + }), 557 + z.object({ 558 + foo: z.enum([ 559 + 'Qux' 560 + ]) 561 + }), 562 + z.object({ 563 + content: z.string().datetime(), 564 + foo: z.enum([ 565 + 'Quux' 566 + ]) 567 + }), 568 + z.object({ 569 + content: z.unknown(), 570 + foo: z.enum([ 571 + 'Corge' 572 + ]) 573 + }) 574 + ]); 405 575 406 576 export const zModelWithNestedArrayEnumsDataFoo = z.enum([ 407 577 'foo', ··· 453 623 template: z.string().optional() 454 624 }); 455 625 456 - export const zModelWithOneOfAndProperties = z.unknown(); 626 + export const zModelWithOneOfAndProperties = z.intersection(z.union([ 627 + z.unknown(), 628 + zNonAsciiStringæøåÆøÅöôêÊ字符串 629 + ]), z.object({ 630 + baz: z.union([ 631 + z.number().gte(0), 632 + z.null() 633 + ]), 634 + qux: z.number().gte(0) 635 + })); 457 636 458 637 export const zParameterSimpleParameterUnused = z.string(); 459 638 ··· 465 644 466 645 export const zDeleteFooData2 = z.string(); 467 646 468 - export const zimport = z.string(); 647 + export const zImport = z.string(); 469 648 470 649 export const zSchemaWithFormRestrictedKeys = z.object({ 471 650 description: z.string().optional(), ··· 489 668 })).optional() 490 669 }); 491 670 492 - export const zio_k8s_apimachinery_pkg_apis_meta_v1_DeleteOptions = z.object({ 671 + export const zIoK8sApimachineryPkgApisMetaV1DeleteOptions = z.object({ 493 672 preconditions: z.object({ 494 673 resourceVersion: z.string().optional(), 495 674 uid: z.string().optional() 496 675 }).optional() 497 676 }); 498 677 499 - export const zio_k8s_apimachinery_pkg_apis_meta_v1_Preconditions = z.object({ 678 + export const zIoK8sApimachineryPkgApisMetaV1Preconditions = z.object({ 500 679 resourceVersion: z.string().optional(), 501 680 uid: z.string().optional() 502 681 }); ··· 505 684 506 685 export const zAdditionalPropertiesUnknownIssue2 = z.object({}); 507 686 508 - export const zAdditionalPropertiesUnknownIssue3 = z.unknown(); 687 + export const zAdditionalPropertiesUnknownIssue3 = z.intersection(z.string(), z.object({ 688 + entries: z.object({}) 689 + })); 509 690 510 691 export const zAdditionalPropertiesIntegerIssue = z.object({ 511 692 value: z.number() 512 693 }); 513 694 514 - export const zOneOfAllOfIssue = z.unknown(); 695 + export const zOneOfAllOfIssue = z.union([ 696 + z.intersection(z.union([ 697 + zConstValue, 698 + z.object({ 699 + item: z.boolean().optional(), 700 + error: z.union([ 701 + z.string(), 702 + z.null() 703 + ]).optional(), 704 + hasError: z.boolean().readonly().optional(), 705 + data: z.object({}).optional() 706 + }) 707 + ]), z3eNum1Период), 708 + z.object({ 709 + item: z.union([ 710 + z.string(), 711 + z.null() 712 + ]).optional(), 713 + error: z.union([ 714 + z.string(), 715 + z.null() 716 + ]).optional(), 717 + hasError: z.boolean().readonly().optional() 718 + }) 719 + ]); 515 720 516 - export const zGeneric_Schema_Duplicate_Issue_1_System_Boolean_ = z.object({ 721 + export const zGenericSchemaDuplicateIssue1SystemBoolean = z.object({ 517 722 item: z.boolean().optional(), 518 - error: z.unknown().optional(), 723 + error: z.union([ 724 + z.string(), 725 + z.null() 726 + ]).optional(), 519 727 hasError: z.boolean().readonly().optional(), 520 728 data: z.object({}).optional() 521 729 }); 522 730 523 - export const zGeneric_Schema_Duplicate_Issue_1_System_String_ = z.object({ 524 - item: z.unknown().optional(), 525 - error: z.unknown().optional(), 731 + export const zGenericSchemaDuplicateIssue1SystemString = z.object({ 732 + item: z.union([ 733 + z.string(), 734 + z.null() 735 + ]).optional(), 736 + error: z.union([ 737 + z.string(), 738 + z.null() 739 + ]).optional(), 526 740 hasError: z.boolean().readonly().optional() 527 - }); 741 + }); 742 + 743 + export const zImportResponse = z.union([ 744 + zModelFromZendesk, 745 + zModelWithReadOnlyAndWriteOnly 746 + ]); 747 + 748 + export const zApiVVersionODataControllerCountResponse = zModelFromZendesk; 749 + 750 + export const zGetApiVbyApiVersionSimpleOperationResponse = z.number(); 751 + 752 + export const zPostCallWithOptionalParamResponse = z.union([ 753 + z.number(), 754 + z.void() 755 + ]); 756 + 757 + export const zCallWithNoContentResponseResponse = z.void(); 758 + 759 + export const zCallWithResponseAndNoContentResponseResponse = z.union([ 760 + z.number(), 761 + z.void() 762 + ]); 763 + 764 + export const zDummyAResponse = z400; 765 + 766 + export const zDummyBResponse = z.void(); 767 + 768 + export const zCallWithResponseResponse = zImport; 769 + 770 + export const zCallWithDuplicateResponsesResponse = z.union([ 771 + zModelWithBoolean.merge(zModelWithInteger), 772 + zModelWithString 773 + ]); 774 + 775 + export const zCallWithResponsesResponse = z.union([ 776 + z.object({ 777 + '@namespace.string': z.string().readonly().optional(), 778 + '@namespace.integer': z.number().readonly().optional(), 779 + value: z.array(zModelWithString).readonly().optional() 780 + }), 781 + zModelThatExtends, 782 + zModelThatExtendsExtends 783 + ]); 784 + 785 + export const zTypesResponse = z.union([ 786 + z.number(), 787 + z.string(), 788 + z.boolean(), 789 + z.object({}) 790 + ]); 791 + 792 + export const zUploadFileResponse = z.boolean(); 793 + 794 + export const zFileResponseResponse = z.string(); 795 + 796 + export const zComplexTypesResponse = z.array(zModelWithString); 797 + 798 + export const zMultipartResponseResponse = z.object({ 799 + file: z.string().optional(), 800 + metadata: z.object({ 801 + foo: z.string().optional(), 802 + bar: z.string().optional() 803 + }).optional() 804 + }); 805 + 806 + export const zComplexParamsResponse = zModelWithString; 807 + 808 + export const zNonAsciiæøåÆøÅöôêÊ字符串Response = z.array(zNonAsciiStringæøåÆøÅöôêÊ字符串);
+325 -47
packages/openapi-ts/test/__snapshots__/3.1.x/plugins/zod/default/zod.gen.ts
··· 4 4 5 5 export const z400 = z.string(); 6 6 7 - export const zcamelCaseCommentWithBreaks = z.number(); 7 + export const zCamelCaseCommentWithBreaks = z.number(); 8 8 9 9 export const zCommentWithBreaks = z.number(); 10 10 ··· 26 26 27 27 export const zSimpleString = z.string(); 28 28 29 - export const zNonAsciiStringæøåÆØÅöôêÊ字符串 = z.string(); 29 + export const zNonAsciiStringæøåÆøÅöôêÊ字符串 = z.string(); 30 30 31 31 export const zSimpleFile = z.string(); 32 32 ··· 34 34 prop: z.string().optional() 35 35 }); 36 36 37 - export const zSimpleStringWithPattern = z.unknown(); 37 + export const zSimpleStringWithPattern = z.union([ 38 + z.string().max(64), 39 + z.null() 40 + ]); 38 41 39 42 export const zEnumWithStrings = z.enum([ 40 43 'Success', ··· 75 78 }))); 76 79 77 80 export const zArrayWithProperties = z.array(z.object({ 78 - '16x16': zcamelCaseCommentWithBreaks.optional(), 81 + '16x16': zCamelCaseCommentWithBreaks.optional(), 79 82 bar: z.string().optional() 80 83 })); 81 84 82 85 export const zArrayWithAnyOfProperties = z.array(z.unknown()); 83 86 84 87 export const zAnyOfAnyAndNull = z.object({ 85 - data: z.unknown().optional() 88 + data: z.union([ 89 + z.unknown(), 90 + z.null() 91 + ]).optional() 86 92 }); 87 93 88 94 export const zAnyOfArrays = z.object({ ··· 120 126 prop: z.string().optional() 121 127 }); 122 128 123 - export const zModel_From_Zendesk = z.string(); 129 + export const zModelFromZendesk = z.string(); 124 130 125 131 export const zModelWithNullableString = z.object({ 126 - nullableProp1: z.unknown().optional(), 127 - nullableRequiredProp1: z.unknown(), 128 - nullableProp2: z.unknown().optional(), 129 - nullableRequiredProp2: z.unknown(), 132 + nullableProp1: z.union([ 133 + z.string(), 134 + z.null() 135 + ]).optional(), 136 + nullableRequiredProp1: z.union([ 137 + z.string(), 138 + z.null() 139 + ]), 140 + nullableProp2: z.union([ 141 + z.string(), 142 + z.null() 143 + ]).optional(), 144 + nullableRequiredProp2: z.union([ 145 + z.string(), 146 + z.null() 147 + ]), 130 148 'foo_bar-enum': z.enum([ 131 149 'Success', 132 150 'Warning', ··· 184 202 prop: z.object({ 185 203 required: z.string(), 186 204 requiredAndReadOnly: z.string().readonly(), 187 - requiredAndNullable: z.unknown(), 205 + requiredAndNullable: z.union([ 206 + z.string(), 207 + z.null() 208 + ]), 188 209 string: z.string().optional(), 189 210 number: z.number().optional(), 190 211 boolean: z.boolean().optional(), ··· 221 242 prop: z.string().optional() 222 243 }); 223 244 245 + export const zModelWithCircularReference: z.ZodTypeAny = z.object({ 246 + prop: z.lazy(() => { 247 + return zModelWithCircularReference; 248 + }).optional() 249 + }); 250 + 224 251 export const zCompositionWithOneOf = z.object({ 225 - propA: z.unknown().optional() 252 + propA: z.union([ 253 + zModelWithString, 254 + zModelWithEnum, 255 + zModelWithArray, 256 + zModelWithDictionary 257 + ]).optional() 226 258 }); 227 259 228 260 export const zCompositionWithOneOfAnonymous = z.object({ 229 - propA: z.unknown().optional() 261 + propA: z.union([ 262 + z.object({ 263 + propA: z.string().optional() 264 + }), 265 + z.string(), 266 + z.number() 267 + ]).optional() 230 268 }); 231 269 232 270 export const zModelCircle = z.object({ ··· 239 277 sideLength: z.number().optional() 240 278 }); 241 279 242 - export const zCompositionWithOneOfDiscriminator = z.unknown(); 280 + export const zCompositionWithOneOfDiscriminator = z.union([ 281 + z.object({ 282 + kind: z.string().optional() 283 + }).merge(zModelCircle), 284 + z.object({ 285 + kind: z.string().optional() 286 + }).merge(zModelSquare) 287 + ]); 243 288 244 289 export const zCompositionWithAnyOf = z.object({ 245 - propA: z.unknown().optional() 290 + propA: z.union([ 291 + zModelWithString, 292 + zModelWithEnum, 293 + zModelWithArray, 294 + zModelWithDictionary 295 + ]).optional() 246 296 }); 247 297 248 298 export const zCompositionWithAnyOfAnonymous = z.object({ 249 - propA: z.unknown().optional() 299 + propA: z.union([ 300 + z.object({ 301 + propA: z.string().optional() 302 + }), 303 + z.string(), 304 + z.number() 305 + ]).optional() 250 306 }); 251 307 252 308 export const zCompositionWithNestedAnyAndTypeNull = z.object({ 253 - propA: z.unknown().optional() 309 + propA: z.union([ 310 + z.array(z.unknown()), 311 + z.array(z.unknown()) 312 + ]).optional() 254 313 }); 255 314 256 - export const z3e_num_1Период = z.enum([ 315 + export const z3eNum1Период = z.enum([ 257 316 'Bird', 258 317 'Dog' 259 318 ]); ··· 261 320 export const zConstValue = z.string(); 262 321 263 322 export const zCompositionWithNestedAnyOfAndNull = z.object({ 264 - propA: z.unknown().optional() 323 + propA: z.union([ 324 + z.array(z.unknown()), 325 + z.null() 326 + ]).optional() 265 327 }); 266 328 267 329 export const zCompositionWithOneOfAndNullable = z.object({ 268 - propA: z.unknown().optional() 330 + propA: z.union([ 331 + z.object({ 332 + boolean: z.boolean().optional() 333 + }), 334 + zModelWithEnum, 335 + zModelWithArray, 336 + zModelWithDictionary, 337 + z.null() 338 + ]).optional() 269 339 }); 270 340 271 341 export const zCompositionWithOneOfAndSimpleDictionary = z.object({ 272 - propA: z.unknown().optional() 342 + propA: z.union([ 343 + z.boolean(), 344 + z.object({}) 345 + ]).optional() 273 346 }); 274 347 275 348 export const zCompositionWithOneOfAndSimpleArrayDictionary = z.object({ 276 - propA: z.unknown().optional() 349 + propA: z.union([ 350 + z.boolean(), 351 + z.object({}) 352 + ]).optional() 277 353 }); 278 354 279 355 export const zCompositionWithOneOfAndComplexArrayDictionary = z.object({ 280 - propA: z.unknown().optional() 356 + propA: z.union([ 357 + z.boolean(), 358 + z.object({}) 359 + ]).optional() 281 360 }); 282 361 283 362 export const zCompositionWithAllOfAndNullable = z.object({ 284 - propA: z.unknown().optional() 363 + propA: z.union([ 364 + z.object({ 365 + boolean: z.boolean().optional() 366 + }).merge(zModelWithEnum).merge(zModelWithArray).merge(zModelWithDictionary), 367 + z.null() 368 + ]).optional() 285 369 }); 286 370 287 371 export const zCompositionWithAnyOfAndNullable = z.object({ 288 - propA: z.unknown().optional() 372 + propA: z.union([ 373 + z.object({ 374 + boolean: z.boolean().optional() 375 + }), 376 + zModelWithEnum, 377 + zModelWithArray, 378 + zModelWithDictionary, 379 + z.null() 380 + ]).optional() 289 381 }); 290 382 291 383 export const zCompositionBaseModel = z.object({ ··· 293 385 lastname: z.string().optional() 294 386 }); 295 387 296 - export const zCompositionExtendedModel = z.unknown(); 388 + export const zCompositionExtendedModel = zCompositionBaseModel.merge(z.object({ 389 + age: z.number(), 390 + firstName: z.string(), 391 + lastname: z.string() 392 + })); 297 393 298 394 export const zModelWithProperties = z.object({ 299 395 required: z.string(), 300 396 requiredAndReadOnly: z.string().readonly(), 301 - requiredAndNullable: z.unknown(), 397 + requiredAndNullable: z.union([ 398 + z.string(), 399 + z.null() 400 + ]), 302 401 string: z.string().optional(), 303 402 number: z.number().optional(), 304 403 boolean: z.boolean().optional(), ··· 311 410 }); 312 411 313 412 export const zModelWithNestedProperties = z.object({ 314 - first: z.unknown().readonly() 413 + first: z.union([ 414 + z.object({ 415 + second: z.union([ 416 + z.object({ 417 + third: z.union([ 418 + z.string(), 419 + z.null() 420 + ]).readonly() 421 + }), 422 + z.null() 423 + ]).readonly() 424 + }), 425 + z.null() 426 + ]).readonly() 315 427 }); 316 428 317 429 export const zModelWithDuplicateProperties = z.object({ ··· 330 442 propC: zModelWithString.optional() 331 443 }); 332 444 333 - export const zModelThatExtends = z.unknown(); 445 + export const zModelThatExtends = zModelWithString.merge(z.object({ 446 + propExtendsA: z.string().optional(), 447 + propExtendsB: zModelWithString.optional() 448 + })); 334 449 335 - export const zModelThatExtendsExtends = z.unknown(); 450 + export const zModelThatExtendsExtends = zModelWithString.merge(zModelThatExtends).merge(z.object({ 451 + propExtendsC: z.string().optional(), 452 + propExtendsD: zModelWithString.optional() 453 + })); 336 454 337 455 export const zModelWithPattern = z.object({ 338 456 key: z.string().max(64), ··· 354 472 file: z.string().url().readonly().optional() 355 473 }); 356 474 357 - export const zdefault = z.object({ 475 + export const zDefault = z.object({ 358 476 name: z.string().optional() 359 477 }); 360 478 ··· 382 500 }); 383 501 384 502 export const zNestedAnyOfArraysNullable = z.object({ 385 - nullableArray: z.unknown().optional() 503 + nullableArray: z.union([ 504 + z.array(z.unknown()), 505 + z.null() 506 + ]).optional() 386 507 }); 387 508 388 - export const zCompositionWithOneOfAndProperties = z.unknown(); 509 + export const zCompositionWithOneOfAndProperties = z.intersection(z.union([ 510 + z.object({ 511 + foo: z.unknown() 512 + }), 513 + z.object({ 514 + bar: zNonAsciiStringæøåÆøÅöôêÊ字符串 515 + }) 516 + ]), z.object({ 517 + baz: z.union([ 518 + z.number().gte(0), 519 + z.null() 520 + ]), 521 + qux: z.number().gte(0) 522 + })); 389 523 390 - export const zNullableObject = z.unknown(); 524 + export const zNullableObject = z.union([ 525 + z.object({ 526 + foo: z.string().optional() 527 + }), 528 + z.null() 529 + ]); 391 530 392 531 export const zCharactersInDescription = z.string(); 393 532 ··· 395 534 data: zNullableObject.optional() 396 535 }); 397 536 398 - export const zModelWithOneOfEnum = z.unknown(); 537 + export const zModelWithOneOfEnum = z.union([ 538 + z.object({ 539 + foo: z.enum([ 540 + 'Bar' 541 + ]) 542 + }), 543 + z.object({ 544 + foo: z.enum([ 545 + 'Baz' 546 + ]) 547 + }), 548 + z.object({ 549 + foo: z.enum([ 550 + 'Qux' 551 + ]) 552 + }), 553 + z.object({ 554 + content: z.string().datetime(), 555 + foo: z.enum([ 556 + 'Quux' 557 + ]) 558 + }), 559 + z.object({ 560 + content: z.unknown(), 561 + foo: z.enum([ 562 + 'Corge' 563 + ]) 564 + }) 565 + ]); 399 566 400 567 export const zModelWithNestedArrayEnumsDataFoo = z.enum([ 401 568 'foo', ··· 447 614 template: z.string().optional() 448 615 }); 449 616 450 - export const zModelWithOneOfAndProperties = z.unknown(); 617 + export const zModelWithOneOfAndProperties = z.intersection(z.union([ 618 + z.unknown(), 619 + zNonAsciiStringæøåÆøÅöôêÊ字符串 620 + ]), z.object({ 621 + baz: z.union([ 622 + z.number().gte(0), 623 + z.null() 624 + ]), 625 + qux: z.number().gte(0) 626 + })); 451 627 452 628 export const zParameterSimpleParameterUnused = z.string(); 453 629 ··· 459 635 460 636 export const zDeleteFooData2 = z.string(); 461 637 462 - export const zimport = z.string(); 638 + export const zImport = z.string(); 463 639 464 640 export const zSchemaWithFormRestrictedKeys = z.object({ 465 641 description: z.string().optional(), ··· 483 659 })).optional() 484 660 }); 485 661 486 - export const zio_k8s_apimachinery_pkg_apis_meta_v1_DeleteOptions = z.object({ 662 + export const zIoK8sApimachineryPkgApisMetaV1DeleteOptions = z.object({ 487 663 preconditions: z.object({ 488 664 resourceVersion: z.string().optional(), 489 665 uid: z.string().optional() 490 666 }).optional() 491 667 }); 492 668 493 - export const zio_k8s_apimachinery_pkg_apis_meta_v1_Preconditions = z.object({ 669 + export const zIoK8sApimachineryPkgApisMetaV1Preconditions = z.object({ 494 670 resourceVersion: z.string().optional(), 495 671 uid: z.string().optional() 496 672 }); ··· 499 675 500 676 export const zAdditionalPropertiesUnknownIssue2 = z.object({}); 501 677 502 - export const zAdditionalPropertiesUnknownIssue3 = z.unknown(); 678 + export const zAdditionalPropertiesUnknownIssue3 = z.intersection(z.string(), z.object({ 679 + entries: z.object({}) 680 + })); 503 681 504 682 export const zAdditionalPropertiesIntegerIssue = z.object({ 505 683 value: z.number() 506 684 }); 507 685 508 - export const zOneOfAllOfIssue = z.unknown(); 686 + export const zOneOfAllOfIssue = z.union([ 687 + z.intersection(z.union([ 688 + zConstValue, 689 + z.object({ 690 + item: z.boolean().optional(), 691 + error: z.union([ 692 + z.string(), 693 + z.null() 694 + ]).optional(), 695 + hasError: z.boolean().readonly().optional(), 696 + data: z.object({}).optional() 697 + }) 698 + ]), z3eNum1Период), 699 + z.object({ 700 + item: z.union([ 701 + z.string(), 702 + z.null() 703 + ]).optional(), 704 + error: z.union([ 705 + z.string(), 706 + z.null() 707 + ]).optional(), 708 + hasError: z.boolean().readonly().optional() 709 + }) 710 + ]); 509 711 510 - export const zGeneric_Schema_Duplicate_Issue_1_System_Boolean_ = z.object({ 712 + export const zGenericSchemaDuplicateIssue1SystemBoolean = z.object({ 511 713 item: z.boolean().optional(), 512 - error: z.unknown().optional(), 714 + error: z.union([ 715 + z.string(), 716 + z.null() 717 + ]).optional(), 513 718 hasError: z.boolean().readonly().optional(), 514 719 data: z.object({}).optional() 515 720 }); 516 721 517 - export const zGeneric_Schema_Duplicate_Issue_1_System_String_ = z.object({ 518 - item: z.unknown().optional(), 519 - error: z.unknown().optional(), 722 + export const zGenericSchemaDuplicateIssue1SystemString = z.object({ 723 + item: z.union([ 724 + z.string(), 725 + z.null() 726 + ]).optional(), 727 + error: z.union([ 728 + z.string(), 729 + z.null() 730 + ]).optional(), 520 731 hasError: z.boolean().readonly().optional() 521 - }); 732 + }); 733 + 734 + export const zImportResponse = z.union([ 735 + zModelFromZendesk, 736 + zModelWithReadOnlyAndWriteOnly 737 + ]); 738 + 739 + export const zApiVVersionODataControllerCountResponse = zModelFromZendesk; 740 + 741 + export const zGetApiVbyApiVersionSimpleOperationResponse = z.number(); 742 + 743 + export const zPostCallWithOptionalParamResponse = z.union([ 744 + z.number(), 745 + z.void() 746 + ]); 747 + 748 + export const zCallWithNoContentResponseResponse = z.void(); 749 + 750 + export const zCallWithResponseAndNoContentResponseResponse = z.union([ 751 + z.number(), 752 + z.void() 753 + ]); 754 + 755 + export const zDummyAResponse = z400; 756 + 757 + export const zDummyBResponse = z.void(); 758 + 759 + export const zCallWithResponseResponse = zImport; 760 + 761 + export const zCallWithDuplicateResponsesResponse = z.union([ 762 + zModelWithBoolean.merge(zModelWithInteger), 763 + zModelWithString 764 + ]); 765 + 766 + export const zCallWithResponsesResponse = z.union([ 767 + z.object({ 768 + '@namespace.string': z.string().readonly().optional(), 769 + '@namespace.integer': z.number().readonly().optional(), 770 + value: z.array(zModelWithString).readonly().optional() 771 + }), 772 + zModelThatExtends, 773 + zModelThatExtendsExtends 774 + ]); 775 + 776 + export const zTypesResponse = z.union([ 777 + z.number(), 778 + z.string(), 779 + z.boolean(), 780 + z.object({}) 781 + ]); 782 + 783 + export const zUploadFileResponse = z.boolean(); 784 + 785 + export const zFileResponseResponse = z.string(); 786 + 787 + export const zComplexTypesResponse = z.array(zModelWithString); 788 + 789 + export const zMultipartResponseResponse = z.object({ 790 + file: z.string().optional(), 791 + metadata: z.object({ 792 + foo: z.string().optional(), 793 + bar: z.string().optional() 794 + }).optional() 795 + }); 796 + 797 + export const zComplexParamsResponse = zModelWithString; 798 + 799 + export const zNonAsciiæøåÆøÅöôêÊ字符串Response = z.array(zNonAsciiStringæøåÆøÅöôêÊ字符串);
+8 -2
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle/client/index.ts.snap
··· 64 64 65 65 let { data } = response; 66 66 67 - if (opts.responseType === 'json' && opts.responseTransformer) { 68 - data = await opts.responseTransformer(data); 67 + if (opts.responseType === 'json') { 68 + if (opts.responseValidator) { 69 + await opts.responseValidator(data); 70 + } 71 + 72 + if (opts.responseTransformer) { 73 + data = await opts.responseTransformer(data); 74 + } 69 75 } 70 76 71 77 return {
+9 -4
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle/client/types.ts.snap
··· 83 83 */ 84 84 querySerializer?: QuerySerializer | QuerySerializerOptions; 85 85 /** 86 - * A function for transforming response data before it's returned to the 87 - * caller function. This is an ideal place to post-process server data, 88 - * e.g. convert date ISO strings into native Date objects. 86 + * A function transforming response data before it's returned. This is useful 87 + * for post-processing data, e.g. converting ISO strings into Date objects. 89 88 */ 90 89 responseTransformer?: (data: unknown) => Promise<unknown>; 91 90 /** 91 + * A function validating response data. This is useful if you want to ensure 92 + * the response conforms to the desired shape, so it can be safely passed to 93 + * the transformers and returned to the user. 94 + */ 95 + responseValidator?: (data: unknown) => Promise<unknown>; 96 + /** 92 97 * Throw an error instead of returning it in the response? 93 98 * 94 99 * @default false ··· 97 102 } 98 103 99 104 export interface RequestOptions< 100 - ThrowOnError extends boolean = false, 105 + ThrowOnError extends boolean = boolean, 101 106 Url extends string = string, 102 107 > extends Config<ThrowOnError> { 103 108 /**
+8 -2
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle_transform/client/index.ts.snap
··· 64 64 65 65 let { data } = response; 66 66 67 - if (opts.responseType === 'json' && opts.responseTransformer) { 68 - data = await opts.responseTransformer(data); 67 + if (opts.responseType === 'json') { 68 + if (opts.responseValidator) { 69 + await opts.responseValidator(data); 70 + } 71 + 72 + if (opts.responseTransformer) { 73 + data = await opts.responseTransformer(data); 74 + } 69 75 } 70 76 71 77 return {
+9 -4
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-axios-bundle_transform/client/types.ts.snap
··· 83 83 */ 84 84 querySerializer?: QuerySerializer | QuerySerializerOptions; 85 85 /** 86 - * A function for transforming response data before it's returned to the 87 - * caller function. This is an ideal place to post-process server data, 88 - * e.g. convert date ISO strings into native Date objects. 86 + * A function transforming response data before it's returned. This is useful 87 + * for post-processing data, e.g. converting ISO strings into Date objects. 89 88 */ 90 89 responseTransformer?: (data: unknown) => Promise<unknown>; 91 90 /** 91 + * A function validating response data. This is useful if you want to ensure 92 + * the response conforms to the desired shape, so it can be safely passed to 93 + * the transformers and returned to the user. 94 + */ 95 + responseValidator?: (data: unknown) => Promise<unknown>; 96 + /** 92 97 * Throw an error instead of returning it in the response? 93 98 * 94 99 * @default false ··· 97 102 } 98 103 99 104 export interface RequestOptions< 100 - ThrowOnError extends boolean = false, 105 + ThrowOnError extends boolean = boolean, 101 106 Url extends string = string, 102 107 > extends Config<ThrowOnError> { 103 108 /**
+8 -2
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/index.ts.snap
··· 106 106 : opts.parseAs) ?? 'json'; 107 107 108 108 let data = await response[parseAs](); 109 - if (parseAs === 'json' && opts.responseTransformer) { 110 - data = await opts.responseTransformer(data); 109 + if (parseAs === 'json') { 110 + if (opts.responseValidator) { 111 + await opts.responseValidator(data); 112 + } 113 + 114 + if (opts.responseTransformer) { 115 + data = await opts.responseTransformer(data); 116 + } 111 117 } 112 118 113 119 return {
+8 -3
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/types.ts.snap
··· 88 88 */ 89 89 querySerializer?: QuerySerializer | QuerySerializerOptions; 90 90 /** 91 - * A function for transforming response data before it's returned to the 92 - * caller function. This is an ideal place to post-process server data, 93 - * e.g. convert date ISO strings into native Date objects. 91 + * A function transforming response data before it's returned. This is useful 92 + * for post-processing data, e.g. converting ISO strings into Date objects. 94 93 */ 95 94 responseTransformer?: (data: unknown) => Promise<unknown>; 95 + /** 96 + * A function validating response data. This is useful if you want to ensure 97 + * the response conforms to the desired shape, so it can be safely passed to 98 + * the transformers and returned to the user. 99 + */ 100 + responseValidator?: (data: unknown) => Promise<unknown>; 96 101 /** 97 102 * Throw an error instead of returning it in the response? 98 103 *
+8 -2
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/index.ts.snap
··· 106 106 : opts.parseAs) ?? 'json'; 107 107 108 108 let data = await response[parseAs](); 109 - if (parseAs === 'json' && opts.responseTransformer) { 110 - data = await opts.responseTransformer(data); 109 + if (parseAs === 'json') { 110 + if (opts.responseValidator) { 111 + await opts.responseValidator(data); 112 + } 113 + 114 + if (opts.responseTransformer) { 115 + data = await opts.responseTransformer(data); 116 + } 111 117 } 112 118 113 119 return {
+8 -3
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/types.ts.snap
··· 88 88 */ 89 89 querySerializer?: QuerySerializer | QuerySerializerOptions; 90 90 /** 91 - * A function for transforming response data before it's returned to the 92 - * caller function. This is an ideal place to post-process server data, 93 - * e.g. convert date ISO strings into native Date objects. 91 + * A function transforming response data before it's returned. This is useful 92 + * for post-processing data, e.g. converting ISO strings into Date objects. 94 93 */ 95 94 responseTransformer?: (data: unknown) => Promise<unknown>; 95 + /** 96 + * A function validating response data. This is useful if you want to ensure 97 + * the response conforms to the desired shape, so it can be safely passed to 98 + * the transformers and returned to the user. 99 + */ 100 + responseValidator?: (data: unknown) => Promise<unknown>; 96 101 /** 97 102 * Throw an error instead of returning it in the response? 98 103 *
+20 -1
packages/openapi-ts/test/index.test.ts
··· 492 492 input: V3_TRANSFORMS_SPEC_PATH, 493 493 output, 494 494 plugins: [ 495 - ...(config.plugins ?? []), 495 + ...(config.plugins ?? []).map((plugin) => { 496 + if (typeof plugin === 'string') { 497 + if (plugin === '@hey-api/sdk') { 498 + return { 499 + // @ts-expect-error 500 + ...plugin, 501 + name: '@hey-api/sdk', 502 + transformer: true, 503 + }; 504 + } 505 + } else if (plugin.name === '@hey-api/sdk') { 506 + return { 507 + ...plugin, 508 + name: '@hey-api/sdk', 509 + transformer: true, 510 + }; 511 + } 512 + 513 + return plugin; 514 + }), 496 515 { 497 516 dates: true, 498 517 name: '@hey-api/transformers',
-5
packages/openapi-ts/test/plugins.test.ts
··· 212 212 }, 213 213 { 214 214 config: createConfig({ 215 - input: { 216 - // TODO: parser - remove `exclude` once recursive references are handled 217 - exclude: '^#/components/schemas/ModelWithCircularReference$', 218 - path: path.join(__dirname, 'spec', version, 'full.json'), 219 - }, 220 215 output: 'default', 221 216 plugins: ['zod'], 222 217 }),
+12 -7
packages/openapi-ts/test/sample.cjs
··· 5 5 const config = { 6 6 client: { 7 7 // bundle: true, 8 - name: '@hey-api/client-axios', 8 + // name: '@hey-api/client-axios', 9 9 // name: '@hey-api/client-fetch', 10 + name: 'legacy/xhr', 10 11 }, 11 - experimentalParser: true, 12 + // experimentalParser: true, 12 13 input: { 13 - exclude: '^#/components/schemas/ModelWithCircularReference$', 14 + // exclude: '^#/components/schemas/ModelWithCircularReference$', 14 15 // include: 15 16 // '^(#/components/schemas/import|#/paths/api/v{api-version}/simple/options)$', 16 - path: './test/spec/3.1.x/parameter-explode-false.json', 17 + // path: './test/spec/3.1.x/full.json', 18 + path: './test/spec/v3-transforms.json', 17 19 // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', 18 20 // path: 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', 19 21 }, ··· 34 36 // type: 'json', 35 37 }, 36 38 { 37 - // asClass: true, 39 + asClass: true, 38 40 // auth: false, 39 41 // include... 40 42 name: '@hey-api/sdk', 41 43 // operationId: false, 42 44 // serviceNameBuilder: '^Parameters', 45 + // transformer: '@hey-api/transformers', 46 + transformer: true, 47 + // validator: 'zod', 43 48 }, 44 49 { 45 50 dates: true, ··· 50 55 // enums: 'typescript+namespace', 51 56 enums: 'javascript', 52 57 // exportInlineEnums: true, 53 - identifierCase: 'preserve', 58 + // identifierCase: 'preserve', 54 59 name: '@hey-api/typescript', 55 60 // tree: true, 56 61 }, ··· 61 66 // name: '@tanstack/vue-query', 62 67 }, 63 68 { 64 - name: 'zod', 69 + // name: 'zod', 65 70 }, 66 71 ], 67 72 // useOptions: false,
+30 -158
pnpm-lock.yaml
··· 297 297 devDependencies: 298 298 '@angular-devkit/build-angular': 299 299 specifier: ^19.0.4 300 - 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)) 300 + 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)) 301 301 '@angular/cli': 302 302 specifier: ^19.0.4 303 303 version: 19.0.4(@types/node@22.8.5)(chokidar@4.0.1) ··· 9439 9439 dependencies: 9440 9440 '@ampproject/remapping': 2.3.0 9441 9441 '@angular-devkit/architect': 0.1900.4(chokidar@4.0.1) 9442 - '@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)) 9442 + '@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)) 9443 9443 '@angular-devkit/core': 19.0.4(chokidar@4.0.1) 9444 9444 '@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) 9445 9445 '@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 9489 tree-kill: 1.2.2 9490 9490 tslib: 2.8.1 9491 9491 typescript: 5.5.3 9492 - webpack: 5.96.1(esbuild@0.23.1) 9493 - webpack-dev-middleware: 7.4.2(webpack@5.96.1(esbuild@0.24.0)) 9494 - webpack-dev-server: 5.1.0(webpack@5.96.1(esbuild@0.24.0)) 9492 + webpack: 5.96.1(esbuild@0.24.0) 9493 + webpack-dev-middleware: 7.4.2(webpack@5.96.1(esbuild@0.23.1)) 9494 + webpack-dev-server: 5.1.0(webpack@5.96.1(esbuild@0.23.1)) 9495 9495 webpack-merge: 6.0.1 9496 9496 webpack-subresource-integrity: 5.1.0(webpack@5.96.1(esbuild@0.24.0)) 9497 9497 optionalDependencies: ··· 9518 9518 - vite 9519 9519 - webpack-cli 9520 9520 9521 - '@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))': 9522 - dependencies: 9523 - '@ampproject/remapping': 2.3.0 9524 - '@angular-devkit/architect': 0.1900.4(chokidar@4.0.1) 9525 - '@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)) 9526 - '@angular-devkit/core': 19.0.4(chokidar@4.0.1) 9527 - '@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) 9528 - '@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) 9529 - '@babel/core': 7.26.0 9530 - '@babel/generator': 7.26.2 9531 - '@babel/helper-annotate-as-pure': 7.25.9 9532 - '@babel/helper-split-export-declaration': 7.24.7 9533 - '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.0) 9534 - '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.0) 9535 - '@babel/plugin-transform-runtime': 7.25.9(@babel/core@7.26.0) 9536 - '@babel/preset-env': 7.26.0(@babel/core@7.26.0) 9537 - '@babel/runtime': 7.26.0 9538 - '@discoveryjs/json-ext': 0.6.3 9539 - '@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)) 9540 - '@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)) 9541 - ansi-colors: 4.1.3 9542 - autoprefixer: 10.4.20(postcss@8.4.49) 9543 - babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.96.1(esbuild@0.24.0)) 9544 - browserslist: 4.24.2 9545 - copy-webpack-plugin: 12.0.2(webpack@5.96.1(esbuild@0.24.0)) 9546 - css-loader: 7.1.2(webpack@5.96.1(esbuild@0.24.0)) 9547 - esbuild-wasm: 0.24.0 9548 - fast-glob: 3.3.2 9549 - http-proxy-middleware: 3.0.3 9550 - istanbul-lib-instrument: 6.0.3 9551 - jsonc-parser: 3.3.1 9552 - karma-source-map-support: 1.4.0 9553 - less: 4.2.0 9554 - less-loader: 12.2.0(less@4.2.0)(webpack@5.96.1(esbuild@0.24.0)) 9555 - license-webpack-plugin: 4.0.2(webpack@5.96.1(esbuild@0.24.0)) 9556 - loader-utils: 3.3.1 9557 - mini-css-extract-plugin: 2.9.2(webpack@5.96.1(esbuild@0.24.0)) 9558 - open: 10.1.0 9559 - ora: 5.4.1 9560 - picomatch: 4.0.2 9561 - piscina: 4.7.0 9562 - postcss: 8.4.49 9563 - postcss-loader: 8.1.1(postcss@8.4.49)(typescript@5.5.3)(webpack@5.96.1(esbuild@0.24.0)) 9564 - resolve-url-loader: 5.0.0 9565 - rxjs: 7.8.1 9566 - sass: 1.80.7 9567 - sass-loader: 16.0.3(sass@1.80.7)(webpack@5.96.1(esbuild@0.24.0)) 9568 - semver: 7.6.3 9569 - source-map-loader: 5.0.0(webpack@5.96.1(esbuild@0.24.0)) 9570 - source-map-support: 0.5.21 9571 - terser: 5.36.0 9572 - tree-kill: 1.2.2 9573 - tslib: 2.8.1 9574 - typescript: 5.5.3 9575 - webpack: 5.96.1(esbuild@0.23.1) 9576 - webpack-dev-middleware: 7.4.2(webpack@5.96.1(esbuild@0.24.0)) 9577 - webpack-dev-server: 5.1.0(webpack@5.96.1(esbuild@0.24.0)) 9578 - webpack-merge: 6.0.1 9579 - webpack-subresource-integrity: 5.1.0(webpack@5.96.1(esbuild@0.24.0)) 9580 - optionalDependencies: 9581 - esbuild: 0.24.0 9582 - karma: 6.4.4 9583 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@20.14.5)(typescript@5.5.3)) 9584 - transitivePeerDependencies: 9585 - - '@angular/compiler' 9586 - - '@rspack/core' 9587 - - '@swc/core' 9588 - - '@types/node' 9589 - - bufferutil 9590 - - chokidar 9591 - - debug 9592 - - html-webpack-plugin 9593 - - lightningcss 9594 - - node-sass 9595 - - sass-embedded 9596 - - stylus 9597 - - sugarss 9598 - - supports-color 9599 - - uglify-js 9600 - - utf-8-validate 9601 - - vite 9602 - - webpack-cli 9603 - 9604 - '@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))': 9521 + '@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))': 9605 9522 dependencies: 9606 9523 '@angular-devkit/architect': 0.1900.4(chokidar@4.0.1) 9607 9524 rxjs: 7.8.1 9608 - webpack: 5.96.1(esbuild@0.23.1) 9609 - webpack-dev-server: 5.1.0(webpack@5.96.1(esbuild@0.24.0)) 9525 + webpack: 5.96.1(esbuild@0.24.0) 9526 + webpack-dev-server: 5.1.0(webpack@5.96.1(esbuild@0.23.1)) 9610 9527 transitivePeerDependencies: 9611 9528 - chokidar 9612 9529 ··· 9681 9598 - supports-color 9682 9599 - terser 9683 9600 9684 - '@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)': 9685 - dependencies: 9686 - '@ampproject/remapping': 2.3.0 9687 - '@angular-devkit/architect': 0.1900.4(chokidar@4.0.1) 9688 - '@angular/compiler': 19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0)) 9689 - '@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) 9690 - '@babel/core': 7.26.0 9691 - '@babel/helper-annotate-as-pure': 7.25.9 9692 - '@babel/helper-split-export-declaration': 7.24.7 9693 - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) 9694 - '@inquirer/confirm': 5.0.2(@types/node@22.8.5) 9695 - '@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)) 9696 - beasties: 0.1.0 9697 - browserslist: 4.24.2 9698 - esbuild: 0.24.0 9699 - fast-glob: 3.3.2 9700 - https-proxy-agent: 7.0.5 9701 - istanbul-lib-instrument: 6.0.3 9702 - listr2: 8.2.5 9703 - magic-string: 0.30.12 9704 - mrmime: 2.0.0 9705 - parse5-html-rewriting-stream: 7.0.0 9706 - picomatch: 4.0.2 9707 - piscina: 4.7.0 9708 - rollup: 4.26.0 9709 - sass: 1.80.7 9710 - semver: 7.6.3 9711 - typescript: 5.5.3 9712 - vite: 5.4.11(@types/node@22.8.5)(less@4.2.0)(sass@1.80.7)(terser@5.36.0) 9713 - watchpack: 2.4.2 9714 - optionalDependencies: 9715 - less: 4.2.0 9716 - lmdb: 3.1.5 9717 - postcss: 8.4.49 9718 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@20.14.5)(typescript@5.5.3)) 9719 - transitivePeerDependencies: 9720 - - '@types/node' 9721 - - chokidar 9722 - - lightningcss 9723 - - sass-embedded 9724 - - stylus 9725 - - sugarss 9726 - - supports-color 9727 - - terser 9728 - 9729 9601 '@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)': 9730 9602 dependencies: 9731 9603 '@angular/common': 19.0.3(@angular/core@19.0.3(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1) ··· 11640 11512 dependencies: 11641 11513 '@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) 11642 11514 typescript: 5.5.3 11643 - webpack: 5.96.1(esbuild@0.23.1) 11515 + webpack: 5.96.1(esbuild@0.24.0) 11644 11516 11645 11517 '@nodelib/fs.scandir@2.1.5': 11646 11518 dependencies: ··· 13981 13853 '@babel/core': 7.26.0 13982 13854 find-cache-dir: 4.0.0 13983 13855 schema-utils: 4.2.0 13984 - webpack: 5.96.1(esbuild@0.23.1) 13856 + webpack: 5.96.1(esbuild@0.24.0) 13985 13857 13986 13858 babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.26.0): 13987 13859 dependencies: ··· 14472 14344 normalize-path: 3.0.0 14473 14345 schema-utils: 4.2.0 14474 14346 serialize-javascript: 6.0.2 14475 - webpack: 5.96.1(esbuild@0.23.1) 14347 + webpack: 5.96.1(esbuild@0.24.0) 14476 14348 14477 14349 core-js-compat@3.39.0: 14478 14350 dependencies: ··· 14519 14391 postcss-value-parser: 4.2.0 14520 14392 semver: 7.6.3 14521 14393 optionalDependencies: 14522 - webpack: 5.96.1(esbuild@0.23.1) 14394 + webpack: 5.96.1(esbuild@0.24.0) 14523 14395 14524 14396 css-select@5.1.0: 14525 14397 dependencies: ··· 16104 15976 dependencies: 16105 15977 less: 4.2.0 16106 15978 optionalDependencies: 16107 - webpack: 5.96.1(esbuild@0.23.1) 15979 + webpack: 5.96.1(esbuild@0.24.0) 16108 15980 16109 15981 less@4.2.0: 16110 15982 dependencies: ··· 16129 16001 dependencies: 16130 16002 webpack-sources: 3.2.3 16131 16003 optionalDependencies: 16132 - webpack: 5.96.1(esbuild@0.23.1) 16004 + webpack: 5.96.1(esbuild@0.24.0) 16133 16005 16134 16006 light-my-request@6.3.0: 16135 16007 dependencies: ··· 16427 16299 dependencies: 16428 16300 schema-utils: 4.2.0 16429 16301 tapable: 2.2.1 16430 - webpack: 5.96.1(esbuild@0.23.1) 16302 + webpack: 5.96.1(esbuild@0.24.0) 16431 16303 16432 16304 minimalistic-assert@1.0.1: {} 16433 16305 ··· 17085 16957 postcss: 8.4.49 17086 16958 semver: 7.6.3 17087 16959 optionalDependencies: 17088 - webpack: 5.96.1(esbuild@0.23.1) 16960 + webpack: 5.96.1(esbuild@0.24.0) 17089 16961 transitivePeerDependencies: 17090 16962 - typescript 17091 16963 ··· 17572 17444 neo-async: 2.6.2 17573 17445 optionalDependencies: 17574 17446 sass: 1.80.7 17575 - webpack: 5.96.1(esbuild@0.23.1) 17447 + webpack: 5.96.1(esbuild@0.24.0) 17576 17448 17577 17449 sass@1.80.7: 17578 17450 dependencies: ··· 17855 17727 dependencies: 17856 17728 iconv-lite: 0.6.3 17857 17729 source-map-js: 1.2.1 17858 - webpack: 5.96.1(esbuild@0.23.1) 17730 + webpack: 5.96.1(esbuild@0.24.0) 17859 17731 17860 17732 source-map-support@0.5.21: 17861 17733 dependencies: ··· 18237 18109 18238 18110 term-size@2.2.1: {} 18239 18111 18240 - terser-webpack-plugin@5.3.10(esbuild@0.23.1)(webpack@5.96.1(esbuild@0.24.0)): 18112 + terser-webpack-plugin@5.3.10(esbuild@0.24.0)(webpack@5.96.1(esbuild@0.23.1)): 18241 18113 dependencies: 18242 18114 '@jridgewell/trace-mapping': 0.3.25 18243 18115 jest-worker: 27.5.1 18244 18116 schema-utils: 3.3.0 18245 18117 serialize-javascript: 6.0.2 18246 18118 terser: 5.36.0 18247 - webpack: 5.96.1(esbuild@0.23.1) 18119 + webpack: 5.96.1(esbuild@0.24.0) 18248 18120 optionalDependencies: 18249 - esbuild: 0.23.1 18121 + esbuild: 0.24.0 18250 18122 18251 18123 terser@5.36.0: 18252 18124 dependencies: ··· 18391 18263 '@tsconfig/node14': 1.0.3 18392 18264 '@tsconfig/node16': 1.0.4 18393 18265 '@types/node': 22.8.5 18394 - acorn: 8.12.1 18266 + acorn: 8.14.0 18395 18267 acorn-walk: 8.3.3 18396 18268 arg: 4.1.3 18397 18269 create-require: 1.1.1 ··· 19067 18939 19068 18940 webidl-conversions@7.0.0: {} 19069 18941 19070 - webpack-dev-middleware@7.4.2(webpack@5.96.1(esbuild@0.24.0)): 18942 + webpack-dev-middleware@7.4.2(webpack@5.96.1(esbuild@0.23.1)): 19071 18943 dependencies: 19072 18944 colorette: 2.0.20 19073 18945 memfs: 4.14.0 ··· 19076 18948 range-parser: 1.2.1 19077 18949 schema-utils: 4.2.0 19078 18950 optionalDependencies: 19079 - webpack: 5.96.1(esbuild@0.23.1) 18951 + webpack: 5.96.1(esbuild@0.24.0) 19080 18952 19081 - webpack-dev-server@5.1.0(webpack@5.96.1(esbuild@0.24.0)): 18953 + webpack-dev-server@5.1.0(webpack@5.96.1(esbuild@0.23.1)): 19082 18954 dependencies: 19083 18955 '@types/bonjour': 3.5.13 19084 18956 '@types/connect-history-api-fallback': 1.5.4 ··· 19106 18978 serve-index: 1.9.1 19107 18979 sockjs: 0.3.24 19108 18980 spdy: 4.0.2 19109 - webpack-dev-middleware: 7.4.2(webpack@5.96.1(esbuild@0.24.0)) 18981 + webpack-dev-middleware: 7.4.2(webpack@5.96.1(esbuild@0.23.1)) 19110 18982 ws: 8.18.0 19111 18983 optionalDependencies: 19112 - webpack: 5.96.1(esbuild@0.23.1) 18984 + webpack: 5.96.1(esbuild@0.24.0) 19113 18985 transitivePeerDependencies: 19114 18986 - bufferutil 19115 18987 - debug ··· 19127 18999 webpack-subresource-integrity@5.1.0(webpack@5.96.1(esbuild@0.24.0)): 19128 19000 dependencies: 19129 19001 typed-assert: 1.0.9 19130 - webpack: 5.96.1(esbuild@0.23.1) 19002 + webpack: 5.96.1(esbuild@0.24.0) 19131 19003 19132 - webpack@5.96.1(esbuild@0.23.1): 19004 + webpack@5.96.1(esbuild@0.24.0): 19133 19005 dependencies: 19134 19006 '@types/eslint-scope': 3.7.7 19135 19007 '@types/estree': 1.0.6 ··· 19151 19023 neo-async: 2.6.2 19152 19024 schema-utils: 3.3.0 19153 19025 tapable: 2.2.1 19154 - terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.96.1(esbuild@0.24.0)) 19026 + terser-webpack-plugin: 5.3.10(esbuild@0.24.0)(webpack@5.96.1(esbuild@0.23.1)) 19155 19027 watchpack: 2.4.2 19156 19028 webpack-sources: 3.2.3 19157 19029 transitivePeerDependencies: