diff --git a/.changeset/curly-geckos-play.md b/.changeset/curly-geckos-play.md new file mode 100644 index 000000000..0b5e14600 --- /dev/null +++ b/.changeset/curly-geckos-play.md @@ -0,0 +1,5 @@ +--- +'@hey-api/codegen-core': patch +--- + +**types**: document default values for `importKind` and `kind` diff --git a/.changeset/fruity-camels-type.md b/.changeset/fruity-camels-type.md new file mode 100644 index 000000000..f4ff09b93 --- /dev/null +++ b/.changeset/fruity-camels-type.md @@ -0,0 +1,7 @@ +--- +'@hey-api/openapi-ts': minor +--- + +**plugin(valibot)**: **BREAKING:** standardize `~resolvers` API + +The [Resolvers API](https://heyapi.dev/openapi-ts/plugins/concepts/resolvers) has been simplified and expanded to provide a more consistent behavior across plugins. You can view a few common examples on the [Resolvers](https://heyapi.dev/openapi-ts/plugins/concepts/resolvers) page. diff --git a/.changeset/upset-weeks-dream.md b/.changeset/upset-weeks-dream.md new file mode 100644 index 000000000..20f29c101 --- /dev/null +++ b/.changeset/upset-weeks-dream.md @@ -0,0 +1,7 @@ +--- +'@hey-api/openapi-ts': minor +--- + +**plugin(zod)**: **BREAKING:** standardize `~resolvers` API + +The [Resolvers API](https://heyapi.dev/openapi-ts/plugins/concepts/resolvers) has been simplified and expanded to provide a more consistent behavior across plugins. You can view a few common examples on the [Resolvers](https://heyapi.dev/openapi-ts/plugins/concepts/resolvers) page. diff --git a/dev/openapi-ts.config.ts b/dev/openapi-ts.config.ts index 7f2a883da..b5b117b67 100644 --- a/dev/openapi-ts.config.ts +++ b/dev/openapi-ts.config.ts @@ -443,15 +443,15 @@ export default defineConfig(() => { // }); // return $(v).attr('instance').call(big); // }, - // object(ctx) { - // const { $, symbols } = ctx; - // const { v } = symbols; - // const additional = ctx.nodes.additionalProperties(ctx); - // if (additional === undefined) { - // const shape = ctx.nodes.shape(ctx); - // ctx.nodes.base = () => $(v).attr('looseObject').call(shape); - // } - // }, + object(ctx) { + const { $, symbols } = ctx; + const { v } = symbols; + const additional = ctx.nodes.additionalProperties(ctx); + if (additional === undefined) { + const shape = ctx.nodes.shape(ctx); + ctx.nodes.base = () => $(v).attr('looseObject').call(shape); + } + }, // string(ctx) { // const { $, schema, symbols } = ctx; // const { v } = symbols; diff --git a/docs/.vitepress/config/en.ts b/docs/.vitepress/config/en.ts index 977475f25..366d52ddc 100644 --- a/docs/.vitepress/config/en.ts +++ b/docs/.vitepress/config/en.ts @@ -255,6 +255,16 @@ export default defineConfig({ link: '/openapi-ts/web-frameworks', text: 'Web Frameworks', }, + { + collapsed: true, + items: [ + { + link: '/openapi-ts/plugins/concepts/resolvers', + text: 'Resolvers', + }, + ], + text: 'Concepts', + }, { collapsed: true, items: [ diff --git a/docs/openapi-ts/clients.md b/docs/openapi-ts/clients.md index c4cc9acc3..5c4b8f95a 100644 --- a/docs/openapi-ts/clients.md +++ b/docs/openapi-ts/clients.md @@ -1,13 +1,13 @@ --- title: Clients -description: REST clients for Hey API. Compatible with all our features. +description: HTTP clients for Hey API. Compatible with all our features. --- -# REST Clients +# HTTP Clients We all send HTTP requests in a slightly different way. Hey API doesn't force you to use any specific technology. What we do, however, is support your choice with great clients. All seamlessly integrated with our other features. diff --git a/docs/openapi-ts/migrating.md b/docs/openapi-ts/migrating.md index 063bfaf89..cfc0799e4 100644 --- a/docs/openapi-ts/migrating.md +++ b/docs/openapi-ts/migrating.md @@ -7,6 +7,12 @@ description: Migrating to @hey-api/openapi-ts. While we try to avoid breaking changes, sometimes it's unavoidable in order to offer you the latest features. This page lists changes that require updates to your code. If you run into a problem with migration, please [open an issue](https://github.com/hey-api/openapi-ts/issues). +## v0.90.0 + +### Resolvers API + +The [Resolvers API](/openapi-ts/plugins/concepts/resolvers) has been simplified and expanded to provide a more consistent behavior across plugins. You can view a few common examples on the [Resolvers](/openapi-ts/plugins/concepts/resolvers) page. + ## v0.89.0 ### Prefer named exports diff --git a/docs/openapi-ts/plugins/concepts/resolvers.md b/docs/openapi-ts/plugins/concepts/resolvers.md new file mode 100644 index 000000000..fcb7b18d9 --- /dev/null +++ b/docs/openapi-ts/plugins/concepts/resolvers.md @@ -0,0 +1,181 @@ +--- +title: Resolvers +description: Understand the concepts behind plugins. +--- + +# Resolvers + +Sometimes the default plugin behavior isn't what you need or expect. Resolvers let you patch plugins in a safe and performant way, without forking or reimplementing core logic. + +Currently available for [Valibot](/openapi-ts/plugins/valibot) and [Zod](/openapi-ts/plugins/zod). + +## Examples + +This page demonstrates resolvers through a few common scenarios. + +1. [Handle arbitrary schema formats](#example-1) +2. [Validate high precision numbers](#example-2) +3. [Replace default base](#example-3) + +## Terminology + +Before we look at examples, let's go through the resolvers API to help you understand how they work. Plugins that support resolvers expose them through the `~resolvers` option. Each resolver is a function that receives context and returns an implemented node (or patches existing ones). + +The resolver context will usually contain: + +- `$` - The node builder interface. Use it to build your custom logic. +- `nodes` - Parts of the plugin logic. You can use these to avoid reimplementing the functionality, or replace them with custom implementation. +- `plugin` - The plugin instance. You'll most likely use it to register new symbols. +- `symbols` - Frequently used symbols. These are effectively shorthands for commonly used `plugin.referenceSymbol()` calls. + +Other fields may include the current schema or relevant utilities. + +## Example 1 + +### Handle arbitrary schema formats + +By default, the Valibot plugin may produce the following schemas for `date` and `date-time` strings. + +```js +export const vDates = v.object({ + created: v.pipe(v.string(), v.isoDate()), + modified: v.pipe(v.string(), v.isoTimestamp()), +}); +``` + +We can override this behavior by patching the `nodes.format` function only for strings with `date` or `date-time` formats. + +```js +{ + name: 'valibot', + '~resolvers': { + string(ctx) { + const { $, schema, symbols } = ctx; + const { v } = symbols; + if (schema.format === 'date' || schema.format === 'date-time') { + ctx.nodes.format = () => $(v).attr('isoDateTime').call(); + } + } + } +} +``` + +This applies custom logic with surgical precision, without affecting the rest of the default behavior. + +::: code-group + +```js [after] +export const vDates = v.object({ + created: v.pipe(v.string(), v.isoDateTime()), + modified: v.pipe(v.string(), v.isoDateTime()), +}); +``` + +```js [before] +export const vDates = v.object({ + created: v.pipe(v.string(), v.isoDate()), + modified: v.pipe(v.string(), v.isoTimestamp()), +}); +``` + +::: + +## Example 2 + +### Validate high precision numbers + +Let's say you're dealing with very large or unsafe numbers. + +```js +export const vAmount = v.number(); +``` + +In this case, you'll want to use a third-party library to validate your values. We can use big.js to validate all numbers by replacing the whole resolver. + +```js +{ + name: 'valibot', + '~resolvers': { + number(ctx) { + const { $, plugin, symbols } = ctx; + const { v } = symbols; + const big = plugin.symbolOnce('Big', { + external: 'big.js', + importKind: 'default', + }); + return $(v).attr('instance').call(big); + } + } +} +``` + +We're calling `plugin.symbolOnce()` to ensure we always use the same symbol reference. + +::: code-group + +```js [after] +import Big from 'big.js'; + +export const vAmount = v.instance(Big); +``` + +```js [before] +export const vAmount = v.number(); +``` + +::: + +## Example 3 + +### Replace default base + +You might want to replace the default base schema, e.g. `v.object()`. + +```js +export const vUser = v.object({ + age: v.number(), +}); +``` + +Let's say we want to interpret any schema without explicitly defined additional properties as a loose object. + +```js +{ + name: 'valibot', + '~resolvers': { + object(ctx) { + const { $, symbols } = ctx; + const { v } = symbols; + const additional = ctx.nodes.additionalProperties(ctx); + if (additional === undefined) { + const shape = ctx.nodes.shape(ctx); + ctx.nodes.base = () => $(v).attr('looseObject').call(shape); + } + } + } +} +``` + +Above we demonstrate patching a node based on the result of another node. + +::: code-group + +```js [after] +export const vUser = v.looseObject({ + age: v.number(), +}); +``` + +```js [before] +export const vUser = v.object({ + age: v.number(), +}); +``` + +::: + +## Feedback + +We welcome feedback on the Resolvers API. [Open a GitHub issue](https://github.com/hey-api/openapi-ts/issues) to request support for additional plugins. + + diff --git a/docs/openapi-ts/plugins/valibot.md b/docs/openapi-ts/plugins/valibot.md index 77f6e6f8e..da77f95fe 100644 --- a/docs/openapi-ts/plugins/valibot.md +++ b/docs/openapi-ts/plugins/valibot.md @@ -207,6 +207,10 @@ export default { ::: +## Resolvers + +You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers). + ## API You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/valibot/types.d.ts) interface. diff --git a/docs/openapi-ts/plugins/zod.md b/docs/openapi-ts/plugins/zod.md index ff0b9dc45..df445e65d 100644 --- a/docs/openapi-ts/plugins/zod.md +++ b/docs/openapi-ts/plugins/zod.md @@ -299,6 +299,10 @@ export default { You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options. +## Resolvers + +You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers). + ## API You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/zod/types.d.ts) interface. diff --git a/docs/openapi-ts/plugins/zod/mini.md b/docs/openapi-ts/plugins/zod/mini.md index e51b143b3..a035da3e4 100644 --- a/docs/openapi-ts/plugins/zod/mini.md +++ b/docs/openapi-ts/plugins/zod/mini.md @@ -312,6 +312,10 @@ export default { You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options. +## Resolvers + +You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers). + ## API You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/zod/types.d.ts) interface. diff --git a/docs/openapi-ts/plugins/zod/v3.md b/docs/openapi-ts/plugins/zod/v3.md index 65d10b564..c13c7ea3f 100644 --- a/docs/openapi-ts/plugins/zod/v3.md +++ b/docs/openapi-ts/plugins/zod/v3.md @@ -310,6 +310,10 @@ export default { You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options. +## Resolvers + +You can further customize this plugin's behavior using [resolvers](/openapi-ts/plugins/concepts/resolvers). + ## API You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/zod/types.d.ts) interface.