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