Skip to content

Commit

Permalink
Merge pull request #1316 from hey-api/chore/zod-plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlubos authored Nov 20, 2024
2 parents 3e514d6 + a79fac8 commit 55e36cd
Show file tree
Hide file tree
Showing 22 changed files with 1,527 additions and 336 deletions.
5 changes: 5 additions & 0 deletions .changeset/curly-ravens-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/docs': patch
---

docs: add Plugins page
5 changes: 5 additions & 0 deletions .changeset/strange-rice-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': patch
---

fix: add input.exclude option
5 changes: 5 additions & 0 deletions .changeset/wet-laws-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': patch
---

fix: make Zod plugin available in plugins options
6 changes: 5 additions & 1 deletion docs/.vitepress/config/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export default defineConfig({
},
{
items: [
{
link: '/openapi-ts/plugins',
text: 'Introduction',
},
{
link: '/openapi-ts/fastify',
text: 'Fastify',
Expand All @@ -57,7 +61,7 @@ export default defineConfig({
},
{
link: '/openapi-ts/zod',
text: 'Zod <span class="soon">soon</span>',
text: 'Zod',
},
],
text: 'Plugins',
Expand Down
6 changes: 6 additions & 0 deletions docs/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ export const embedProject = (projectId: string) => async (event: Event) => {
'openapi-ts.config.ts,src/client/@tanstack/react-query.gen.ts,src/client/types.gen.ts,src/App.tsx',
view: 'editor',
});
case 'hey-api-client-fetch-plugin-zod-example':
return await sdk.embedProjectId(container, projectId, {
height: 700,
openFile: 'openapi-ts.config.ts,src/client/zod.gen.ts,src/App.tsx',
view: 'editor',
});
case 'hey-api-example':
return await sdk.embedProjectId(container, projectId, {
height: 700,
Expand Down
22 changes: 19 additions & 3 deletions docs/openapi-ts/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,37 @@ We use [`@apidevtools/json-schema-ref-parser`](https://github.com/APIDevTools/js
Filters work only with the [experimental parser](#parser) which is currently an opt-in feature.
:::

If you work with large specifications and want to generate output from their subset, set `input.include` to a regular expression string matching against resource references.
If you work with large specifications and want to generate output from their subset, you can use regular expressions to select the relevant definitions. Set `input.include` to match resource references to be included or `input.exclude` to match resource references to be excluded. When both regular expressions match the same definition, `input.exclude` takes precedence over `input.include`.

```js
::: code-group

```js [include]
export default {
client: '@hey-api/client-fetch',
experimentalParser: true, // [!code ++]
input: {
// match only the schema named `foo` and `GET` operation for the `/api/v1/foo` path // [!code ++]
include: '^(#/components/schemas/foo|#/paths/api/v1/foo/get)$', // [!code ++]
path: 'path/to/openapi.json',
},
output: 'src/client',
};
```

The configuration above will process only the schema named `foo` and `GET` operation for the `/api/v1/foo` path.
```js [exclude]
export default {
client: '@hey-api/client-fetch',
experimentalParser: true, // [!code ++]
input: {
// match everything except for the schema named `foo` and `GET` operation for the `/api/v1/foo` path // [!code ++]
exclude: '^(#/components/schemas/foo|#/paths/api/v1/foo/get)$', // [!code ++]
path: 'path/to/openapi.json',
},
output: 'src/client',
};
```

:::

## Output

Expand Down
4 changes: 4 additions & 0 deletions docs/openapi-ts/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,5 +388,9 @@ import type { Pet } from './client';

Client package files are located in the `client` folder. This folder will include different files depending on which client you're using. This folder isn't generated by default. If you want to bundle client packages into your output, read the [Bundling](/openapi-ts/clients/fetch#bundling) section.

## Plugins

The default output generated by Hey API plugins already allows you to build robust clients. However, you might be working with third-party packages and wishing to automate more of your boilerplate. The [Plugins](/openapi-ts/plugins) page covers this topic and more.

<!--@include: ../examples.md-->
<!--@include: ../sponsorship.md-->
164 changes: 164 additions & 0 deletions docs/openapi-ts/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
---
title: Plugins
description: Learn about and discover available plugins.
---

# Plugins

Every generated file in your output is created by a plugin. You already learned about the default plugins in [Output](/openapi-ts/output). This page contains all native plugins and shows you how to create your own.

## Hey API

Apart from being responsible for the default output, Hey API plugins are the foundation for other plugins. Instead of creating their own primitives, other plugins can reuse the artifacts from Hey API plugins. This results in smaller output and a better user experience.

- `@hey-api/schemas` - export OpenAPI definitions as JavaScript objects
- `@hey-api/services` - robust and polished SDKs
- `@hey-api/transformers` - response data transformer functions
- `@hey-api/types` - TypeScript interfaces and enums

## Third Party

These plugins help reduce boilerplate associated with third-party dependencies. Hey API natively supports the most popular packages. Please open an issue on [GitHub](https://github.com/hey-api/openapi-ts/issues) if you'd like us to support your favorite package.

- [`fastify`](/openapi-ts/fastify) - TypeScript interface for Fastify route handlers
- [`@tanstack/angular-query-experimental`](/openapi-ts/tanstack-query) - TanStack Query functions and query keys
- [`@tanstack/react-query`](/openapi-ts/tanstack-query) - TanStack Query functions and query keys
- [`@tanstack/solid-query`](/openapi-ts/tanstack-query) - TanStack Query functions and query keys
- [`@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
- [`zod`](/openapi-ts/zod) - Zod schemas to validate your data

## Community

Featured community plugins.

- [add plugin](https://github.com/hey-api/openapi-ts/pulls)

## Custom

::: warning
Plugins API is in development. The interface might change before it becomes stable. We encourage you to leave feedback on [GitHub](https://github.com/hey-api/openapi-ts/issues).
:::

If the existing plugins do not handle your use case or you're working with proprietary packages, you might want to create your own plugin.

### Configuration

We recommend following the design pattern of the native plugins. First, create a `my-plugin` folder for your plugin files. Inside, create a barrel file `index.ts` exporting the plugin's API.

::: code-group

```ts [index.ts]
export { defaultConfig, defineConfig } from './config';
export type { Config } from './types';
```

:::

`index.ts` references 2 files, so we need to create them. `types.d.ts` contains the TypeScript interface for your plugin's options. It must have the `name` and `output` fields, everything else will become your plugin's configuration options.

::: code-group

```ts [types.d.ts]
export interface Config {
/**
* Plugin name. Must be unique.
*/
name: 'my-plugin';
/**
* Name of the generated file.
* @default 'my-plugin'
*/
output?: string;
/**
* A custom option for your plugin.
*/
myOption?: boolean;
}
```

:::

`config.ts` contains the runtime configuration for your plugin. It must implement the `Config` interface from `types.d.ts` and additional plugin metadata defined in the `PluginConfig` interface.

::: code-group

```ts [config.ts]
import type { DefineConfig, PluginConfig } from '@hey-api/openapi-ts/plugins';

import { handler } from './plugin';
import type { Config } from './types';

export const defaultConfig: PluginConfig<Config> = {
_dependencies: ['@hey-api/types'],
_handler: handler,
_handlerLegacy: () => {},
name: 'my-plugin',
output: 'my-plugin',
};

/**
* Type helper for `my-plugin` plugin, returns {@link PluginConfig} object
*/
export const defineConfig: DefineConfig<Config> = (config) => ({
...defaultConfig,
...config,
});
```

:::

In the `config.ts` above, we define a `my-plugin` plugin which will generate a `my-plugin.gen.ts` output file. We also demonstrate declaring `@hey-api/types` as a dependency for our plugin, so we can safely import artifacts from `types.gen.ts`.

Lastly, we define the `_handler` method which will be responsible for generating our custom output. We just need to create the remaining `plugin.ts` file.

::: code-group

```ts [plugin.ts]
import type { PluginHandler } from '@hey-api/openapi-ts/plugins';

import type { Config } from './types';

export const handler: PluginHandler<Config> = ({ context, plugin }) => {
// create a file for our output
const file = context.createFile({
id: plugin.name,
path: plugin.output,
});

context.subscribe('before', () => {
// do something before parsing the input
});

context.subscribe('operation', ({ operation }) => {
// do something with the operation model
});

context.subscribe('schema', ({ operation }) => {
// do something with the schema model
});

context.subscribe('after', () => {
// do something after parsing the input
});
};
```

:::

And that's it! We can now register our plugin in the Hey API configuration.

```js
import { defineConfig } from './src/my-plugin';

export default {
client: '@hey-api/client-fetch',
input: 'path/to/openapi.json',
output: 'src/client',
plugins: [
defineConfig({
myOption: true,
}),
],
};
```
2 changes: 1 addition & 1 deletion docs/openapi-ts/transformers.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default {
};
```

This will generate types that use `Date` instead of `string` and appropriate transformers. Note that third party date packages are not supported at the moment.
This will generate types that use `Date` instead of `string` and appropriate transformers. Note that third-party date packages are not supported at the moment.

## Example

Expand Down
43 changes: 43 additions & 0 deletions docs/openapi-ts/zod.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,46 @@ Zod plugin is in development. You can follow the updates and provide feedback on
:::

[Zod](https://zod.dev/) is a TypeScript-first schema validation library with static type inference.

<button class="buttonLink" @click="(event) => embedProject('hey-api-client-fetch-plugin-zod-example')(event)">
Live demo
</button>

## Features

- seamless integration with `@hey-api/openapi-ts` ecosystem
- Zod schemas for requests, responses, and reusable components

## Installation

::: warning
Zod plugin works only with the [experimental parser](/openapi-ts/configuration#parser) which is currently an opt-in feature.
:::

Ensure you have already [configured](/openapi-ts/get-started) `@hey-api/openapi-ts`. Update your configuration to use the Zod plugin.

```js
export default {
client: '@hey-api/client-fetch',
experimentalParser: true, // [!code ++]
input: 'path/to/openapi.json',
output: 'src/client',
plugins: [
// ...other plugins
'zod', // [!code ++]
],
};
```

You can now generate Zod artifacts. 🎉

## Output

The Zod plugin will generate the following artifacts, depending on the input specification.

## Schemas

More information will be provided as we finalize the plugin.

<!--@include: ../examples.md-->
<!--@include: ../sponsorship.md-->
1 change: 1 addition & 0 deletions packages/openapi-ts/src/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const compiler = {
parameterDeclaration: types.createParameterDeclaration,
propertyAccessExpression: types.createPropertyAccessExpression,
propertyAccessExpressions: transform.createPropertyAccessExpressions,
propertyAssignment: types.createPropertyAssignment,
returnFunctionCall: _return.createReturnFunctionCall,
returnStatement: _return.createReturnStatement,
returnVariable: _return.createReturnVariable,
Expand Down
16 changes: 12 additions & 4 deletions packages/openapi-ts/src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,10 +549,10 @@ export const createObjectType = <
) {
initializer = createIdentifier({ text: value.value as string });
}
assignment = ts.factory.createPropertyAssignment(
value.key,
assignment = createPropertyAssignment({
initializer,
);
name: value.key,
});
}

addLeadingComments({
Expand Down Expand Up @@ -598,7 +598,7 @@ export const createObjectType = <
const assignment =
shorthand && canShorthand
? ts.factory.createShorthandPropertyAssignment(value)
: ts.factory.createPropertyAssignment(key, initializer);
: createPropertyAssignment({ initializer, name: key });

return assignment;
})
Expand Down Expand Up @@ -885,3 +885,11 @@ export const createBlock = ({
multiLine?: boolean;
statements: Array<ts.Statement>;
}) => ts.factory.createBlock(statements, multiLine);

export const createPropertyAssignment = ({
initializer,
name,
}: {
initializer: ts.Expression;
name: string | ts.PropertyName;
}) => ts.factory.createPropertyAssignment(name, initializer);
Loading

0 comments on commit 55e36cd

Please sign in to comment.