typed-openapi
Install
sh
pnpm add typed-openapi
Directory Structure
.
├── openapi.yaml
└── package.json
openapi.yaml
yaml
openapi: 3.0.0
info:
title: 顧客管理API
version: '1.0.0'
description: 顧客情報の作成、取得、更新、削除を行うためのAPI
servers:
- url: 'https://api.example.com'
security:
- OAuth2:
- read
- write
paths:
/customers:
get:
summary: すべての顧客のリストを取得
security:
- OAuth2:
- read
responses:
'200':
description: 正常に取得しました
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Customer'
post:
summary: 新しい顧客を作成
security:
- OAuth2:
- write
requestBody:
description: 顧客情報を含むリクエストボディ
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CustomerInput'
responses:
'201':
description: 顧客が正常に作成されました
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
/customers/{id}:
get:
summary: 特定の顧客の詳細を取得
security:
- OAuth2:
- read
parameters:
- name: id
in: path
description: 顧客の一意な識別子
required: true
schema:
type: integer
responses:
'200':
description: 顧客の詳細情報
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
'404':
description: 顧客が見つかりません
put:
summary: 特定の顧客情報を更新
security:
- OAuth2:
- write
parameters:
- name: id
in: path
description: 顧客の一意な識別子
required: true
schema:
type: integer
requestBody:
description: 更新する顧客情報
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CustomerInput'
responses:
'200':
description: 顧客が正常に更新されました
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
'404':
description: 顧客が見つかりません
delete:
summary: 特定の顧客を削除
security:
- OAuth2:
- write
parameters:
- name: id
in: path
description: 顧客の一意な識別子
required: true
schema:
type: integer
responses:
'204':
description: 顧客が正常に削除されました
'404':
description: 顧客が見つかりません
components:
securitySchemes:
OAuth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: 'https://example.com/oauth/authorize'
tokenUrl: 'https://example.com/oauth/token'
scopes:
read: データの読み取り権限
write: データの書き込み権限
schemas:
Customer:
type: object
properties:
id:
type: integer
readOnly: true
description: 自動生成された顧客ID
name:
type: string
description: 顧客の名前
email:
type: string
format: email
description: 一意なメールアドレス
phone:
type: string
description: 電話番号
required:
- id
- name
- email
CustomerInput:
type: object
properties:
name:
type: string
description: 顧客の名前
email:
type: string
format: email
description: 一意なメールアドレス
phone:
type: string
description: 電話番号
required:
- name
- email
Zod
sh
pnpx typed-openapi openapi.yaml -o apiClient.ts -r zod
apiClient.ts
ts
import { z } from 'zod'
export type Customer = z.infer<typeof Customer>
export const Customer = z.object({
id: z.number(),
name: z.string(),
email: z.string(),
phone: z.union([z.string(), z.undefined()]).optional(),
})
export type CustomerInput = z.infer<typeof CustomerInput>
export const CustomerInput = z.object({
name: z.string(),
email: z.string(),
phone: z.union([z.string(), z.undefined()]).optional(),
})
export type get_Customers = typeof get_Customers
export const get_Customers = {
method: z.literal('GET'),
path: z.literal('/customers'),
requestFormat: z.literal('json'),
parameters: z.never(),
response: z.array(Customer),
}
export type post_Customers = typeof post_Customers
export const post_Customers = {
method: z.literal('POST'),
path: z.literal('/customers'),
requestFormat: z.literal('json'),
parameters: z.object({
body: CustomerInput,
}),
response: Customer,
}
export type get_CustomersId = typeof get_CustomersId
export const get_CustomersId = {
method: z.literal('GET'),
path: z.literal('/customers/{id}'),
requestFormat: z.literal('json'),
parameters: z.object({
path: z.object({
id: z.number(),
}),
}),
response: Customer,
}
export type put_CustomersId = typeof put_CustomersId
export const put_CustomersId = {
method: z.literal('PUT'),
path: z.literal('/customers/{id}'),
requestFormat: z.literal('json'),
parameters: z.object({
path: z.object({
id: z.number(),
}),
body: CustomerInput,
}),
response: Customer,
}
export type delete_CustomersId = typeof delete_CustomersId
export const delete_CustomersId = {
method: z.literal('DELETE'),
path: z.literal('/customers/{id}'),
requestFormat: z.literal('json'),
parameters: z.object({
path: z.object({
id: z.number(),
}),
}),
response: z.unknown(),
}
// <EndpointByMethod>
export const EndpointByMethod = {
get: {
'/customers': get_Customers,
'/customers/{id}': get_CustomersId,
},
post: {
'/customers': post_Customers,
},
put: {
'/customers/{id}': put_CustomersId,
},
delete: {
'/customers/{id}': delete_CustomersId,
},
}
export type EndpointByMethod = typeof EndpointByMethod
// </EndpointByMethod>
// <EndpointByMethod.Shorthands>
export type GetEndpoints = EndpointByMethod['get']
export type PostEndpoints = EndpointByMethod['post']
export type PutEndpoints = EndpointByMethod['put']
export type DeleteEndpoints = EndpointByMethod['delete']
export type AllEndpoints = EndpointByMethod[keyof EndpointByMethod]
// </EndpointByMethod.Shorthands>
// <ApiClientTypes>
export type EndpointParameters = {
body?: unknown
query?: Record<string, unknown>
header?: Record<string, unknown>
path?: Record<string, unknown>
}
export type MutationMethod = 'post' | 'put' | 'patch' | 'delete'
export type Method = 'get' | 'head' | 'options' | MutationMethod
type RequestFormat = 'json' | 'form-data' | 'form-url' | 'binary' | 'text'
export type DefaultEndpoint = {
parameters?: EndpointParameters | undefined
response: unknown
}
export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
operationId: string
method: Method
path: string
requestFormat: RequestFormat
parameters?: TConfig['parameters']
meta: {
alias: string
hasParameters: boolean
areParametersRequired: boolean
}
response: TConfig['response']
}
type Fetcher = (
method: Method,
url: string,
parameters?: EndpointParameters | undefined,
) => Promise<Endpoint['response']>
type RequiredKeys<T> = {
[P in keyof T]-?: undefined extends T[P] ? never : P
}[keyof T]
type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [config: T]
// </ApiClientTypes>
// <ApiClient>
export class ApiClient {
baseUrl: string = ''
constructor(public fetcher: Fetcher) {}
setBaseUrl(baseUrl: string) {
this.baseUrl = baseUrl
return this
}
// <ApiClient.get>
get<Path extends keyof GetEndpoints, TEndpoint extends GetEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<z.infer<TEndpoint['parameters']>>
): Promise<z.infer<TEndpoint['response']>> {
return this.fetcher('get', this.baseUrl + path, params[0]) as Promise<z.infer<TEndpoint['response']>>
}
// </ApiClient.get>
// <ApiClient.post>
post<Path extends keyof PostEndpoints, TEndpoint extends PostEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<z.infer<TEndpoint['parameters']>>
): Promise<z.infer<TEndpoint['response']>> {
return this.fetcher('post', this.baseUrl + path, params[0]) as Promise<z.infer<TEndpoint['response']>>
}
// </ApiClient.post>
// <ApiClient.put>
put<Path extends keyof PutEndpoints, TEndpoint extends PutEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<z.infer<TEndpoint['parameters']>>
): Promise<z.infer<TEndpoint['response']>> {
return this.fetcher('put', this.baseUrl + path, params[0]) as Promise<z.infer<TEndpoint['response']>>
}
// </ApiClient.put>
// <ApiClient.delete>
delete<Path extends keyof DeleteEndpoints, TEndpoint extends DeleteEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<z.infer<TEndpoint['parameters']>>
): Promise<z.infer<TEndpoint['response']>> {
return this.fetcher('delete', this.baseUrl + path, params[0]) as Promise<z.infer<TEndpoint['response']>>
}
// </ApiClient.delete>
}
export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
return new ApiClient(fetcher).setBaseUrl(baseUrl ?? '')
}
/**
Example usage:
const api = createApiClient((method, url, params) =>
fetch(url, { method, body: JSON.stringify(params) }).then((res) => res.json()),
);
api.get("/users").then((users) => console.log(users));
api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
*/
// </ApiClient
Valibot
sh
pnpx typed-openapi openapi.yaml -o apiClient.ts -r valibot
apiClient.ts
ts
import * as v from 'valibot'
export type Customer = v.InferOutput<typeof Customer>
export const Customer = v.object({
id: v.number(),
name: v.string(),
email: v.string(),
phone: v.optional(v.union([v.string(), v.undefined_()])),
})
export type CustomerInput = v.InferOutput<typeof CustomerInput>
export const CustomerInput = v.object({
name: v.string(),
email: v.string(),
phone: v.optional(v.union([v.string(), v.undefined_()])),
})
export type __ENDPOINTS_START__ = v.InferOutput<typeof __ENDPOINTS_START__>
export const __ENDPOINTS_START__ = v.object({})
export type get_Customers = v.InferOutput<typeof get_Customers>
export const get_Customers = v.object({
method: v.literal('GET'),
path: v.literal('/customers'),
requestFormat: v.literal('json'),
parameters: v.never(),
response: v.array(Customer),
})
export type post_Customers = v.InferOutput<typeof post_Customers>
export const post_Customers = v.object({
method: v.literal('POST'),
path: v.literal('/customers'),
requestFormat: v.literal('json'),
parameters: v.object({
body: CustomerInput,
}),
response: Customer,
})
export type get_CustomersId = v.InferOutput<typeof get_CustomersId>
export const get_CustomersId = v.object({
method: v.literal('GET'),
path: v.literal('/customers/{id}'),
requestFormat: v.literal('json'),
parameters: v.object({
path: v.object({
id: v.number(),
}),
}),
response: Customer,
})
export type put_CustomersId = v.InferOutput<typeof put_CustomersId>
export const put_CustomersId = v.object({
method: v.literal('PUT'),
path: v.literal('/customers/{id}'),
requestFormat: v.literal('json'),
parameters: v.object({
path: v.object({
id: v.number(),
}),
body: CustomerInput,
}),
response: Customer,
})
export type delete_CustomersId = v.InferOutput<typeof delete_CustomersId>
export const delete_CustomersId = v.object({
method: v.literal('DELETE'),
path: v.literal('/customers/{id}'),
requestFormat: v.literal('json'),
parameters: v.object({
path: v.object({
id: v.number(),
}),
}),
response: v.unknown(),
})
export type __ENDPOINTS_END__ = v.InferOutput<typeof __ENDPOINTS_END__>
export const __ENDPOINTS_END__ = v.object({})
// <EndpointByMethod>
export const EndpointByMethod = {
get: {
'/customers': get_Customers,
'/customers/{id}': get_CustomersId,
},
post: {
'/customers': post_Customers,
},
put: {
'/customers/{id}': put_CustomersId,
},
delete: {
'/customers/{id}': delete_CustomersId,
},
}
export type EndpointByMethod = typeof EndpointByMethod
// </EndpointByMethod>
// <EndpointByMethod.Shorthands>
export type GetEndpoints = EndpointByMethod['get']
export type PostEndpoints = EndpointByMethod['post']
export type PutEndpoints = EndpointByMethod['put']
export type DeleteEndpoints = EndpointByMethod['delete']
export type AllEndpoints = EndpointByMethod[keyof EndpointByMethod]
// </EndpointByMethod.Shorthands>
// <ApiClientTypes>
export type EndpointParameters = {
body?: unknown
query?: Record<string, unknown>
header?: Record<string, unknown>
path?: Record<string, unknown>
}
export type MutationMethod = 'post' | 'put' | 'patch' | 'delete'
export type Method = 'get' | 'head' | 'options' | MutationMethod
type RequestFormat = 'json' | 'form-data' | 'form-url' | 'binary' | 'text'
export type DefaultEndpoint = {
parameters?: EndpointParameters | undefined
response: unknown
}
export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
operationId: string
method: Method
path: string
requestFormat: RequestFormat
parameters?: TConfig['parameters']
meta: {
alias: string
hasParameters: boolean
areParametersRequired: boolean
}
response: TConfig['response']
}
type Fetcher = (
method: Method,
url: string,
parameters?: EndpointParameters | undefined,
) => Promise<Endpoint['response']>
type RequiredKeys<T> = {
[P in keyof T]-?: undefined extends T[P] ? never : P
}[keyof T]
type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [config: T]
// </ApiClientTypes>
// <ApiClient>
export class ApiClient {
baseUrl: string = ''
constructor(public fetcher: Fetcher) {}
setBaseUrl(baseUrl: string) {
this.baseUrl = baseUrl
return this
}
// <ApiClient.get>
get<Path extends keyof GetEndpoints, TEndpoint extends GetEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<v.InferOutput<TEndpoint>['parameters']>
): Promise<v.InferOutput<TEndpoint>['response']> {
return this.fetcher('get', this.baseUrl + path, params[0]) as Promise<v.InferOutput<TEndpoint>['response']>
}
// </ApiClient.get>
// <ApiClient.post>
post<Path extends keyof PostEndpoints, TEndpoint extends PostEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<v.InferOutput<TEndpoint>['parameters']>
): Promise<v.InferOutput<TEndpoint>['response']> {
return this.fetcher('post', this.baseUrl + path, params[0]) as Promise<v.InferOutput<TEndpoint>['response']>
}
// </ApiClient.post>
// <ApiClient.put>
put<Path extends keyof PutEndpoints, TEndpoint extends PutEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<v.InferOutput<TEndpoint>['parameters']>
): Promise<v.InferOutput<TEndpoint>['response']> {
return this.fetcher('put', this.baseUrl + path, params[0]) as Promise<v.InferOutput<TEndpoint>['response']>
}
// </ApiClient.put>
// <ApiClient.delete>
delete<Path extends keyof DeleteEndpoints, TEndpoint extends DeleteEndpoints[Path]>(
path: Path,
...params: MaybeOptionalArg<v.InferOutput<TEndpoint>['parameters']>
): Promise<v.InferOutput<TEndpoint>['response']> {
return this.fetcher('delete', this.baseUrl + path, params[0]) as Promise<v.InferOutput<TEndpoint>['response']>
}
// </ApiClient.delete>
}
export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
return new ApiClient(fetcher).setBaseUrl(baseUrl ?? '')
}
/**
Example usage:
const api = createApiClient((method, url, params) =>
fetch(url, { method, body: JSON.stringify(params) }).then((res) => res.json()),
);
api.get("/users").then((users) => console.log(users));
api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
*/
// </ApiClient