hey api orpc plugin

This commit is contained in:
Stephen Zhou
2026-01-24 23:11:27 +08:00
parent 1b8685d9a5
commit f3eaf5a13d
9 changed files with 1644 additions and 0 deletions

3
web/gen/index.ts Normal file
View File

@ -0,0 +1,3 @@
// This file is auto-generated by @hey-api/openapi-ts
export type { AddPetData, AddPetError, AddPetErrors, AddPetRequest, AddPetResponse, AddPetResponses, Address, ApiResponse, Cat, Category, CatWritable, ClientOptions, CreateUserData, CreateUserResponse, CreateUserResponses, CreateUsersWithListInputData, CreateUsersWithListInputResponse, CreateUsersWithListInputResponses, Customer, DeleteOrderData, DeleteOrderErrors, DeletePetData, DeletePetErrors, DeletePetResponse, DeletePetResponses, DeleteUserData, DeleteUserErrors, Dog, DogWritable, FindPetsByStatusData, FindPetsByStatusErrors, FindPetsByStatusResponse, FindPetsByStatusResponses, FindPetsByTagsData, FindPetsByTagsErrors, FindPetsByTagsResponse, FindPetsByTagsResponses, FullAddress, GetInventoryData, GetInventoryResponse, GetInventoryResponses, GetOrderByIdData, GetOrderByIdErrors, GetOrderByIdResponse, GetOrderByIdResponses, GetPetByIdData, GetPetByIdErrors, GetPetByIdResponse, GetPetByIdResponses, GetUserByNameData, GetUserByNameErrors, GetUserByNameResponse, GetUserByNameResponses, HappyCustomer, LoginUserData, LoginUserErrors, LoginUserResponse, LoginUserResponses, LogoutUserData, LogoutUserResponses, OpenapiCategory, Order, Page, PageSize, Pet, Pet2, PetWritable, PlaceOrderData, PlaceOrderErrors, PlaceOrderPatchData, PlaceOrderPatchErrors, PlaceOrderPatchResponse, PlaceOrderPatchResponses, PlaceOrderResponse, PlaceOrderResponses, Tag, UnhappyCustomer, UpdatePetData, UpdatePetErrors, UpdatePetResponse, UpdatePetResponses, UpdatePetWithFormData, UpdatePetWithFormErrors, UpdateUserData, UpdateUserResponses, UploadFileData, UploadFileResponse, UploadFileResponses, User, UserArray } from './types.gen'

137
web/gen/orpc.gen.ts Normal file
View File

@ -0,0 +1,137 @@
// This file is auto-generated by @hey-api/openapi-ts
import { oc } from '@orpc/contract'
import { zAddPetData, zAddPetResponse, zCreateUserData, zCreateUsersWithListInputData, zCreateUsersWithListInputResponse, zDeleteOrderData, zDeletePetData, zDeletePetResponse, zDeleteUserData, zFindPetsByStatusData, zFindPetsByStatusResponse, zFindPetsByTagsData, zFindPetsByTagsResponse, zGetInventoryResponse, zGetOrderByIdData, zGetOrderByIdResponse, zGetPetByIdData, zGetPetByIdResponse, zGetUserByNameData, zGetUserByNameResponse, zLoginUserData, zLoginUserResponse, zPlaceOrderData, zPlaceOrderPatchData, zPlaceOrderPatchResponse, zPlaceOrderResponse, zUpdatePetData, zUpdatePetResponse, zUpdatePetWithFormData, zUpdateUserData, zUploadFileData, zUploadFileResponse } from './zod.gen'
export const base = oc.$route({ inputStructure: 'detailed' })
/**
* Add a new pet to the store
*/
export const addPetContract = base.route({ path: '/pet', method: 'POST' }).input(zAddPetData).output(zAddPetResponse)
/**
* Update an existing pet by Id
*/
export const updatePetContract = base.route({ path: '/pet', method: 'PUT' }).input(zUpdatePetData).output(zUpdatePetResponse)
/**
* Multiple status values can be provided with comma separated strings
*/
export const findPetsByStatusContract = base.route({ path: '/pet/findByStatus', method: 'GET' }).input(zFindPetsByStatusData).output(zFindPetsByStatusResponse)
/**
* Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
*/
export const findPetsByTagsContract = base.route({ path: '/pet/findByTags', method: 'GET' }).input(zFindPetsByTagsData).output(zFindPetsByTagsResponse)
/**
* delete a pet
*/
export const deletePetContract = base.route({ path: '/pet/{petId}', method: 'DELETE' }).input(zDeletePetData).output(zDeletePetResponse)
/**
* Returns a single pet
*/
export const getPetByIdContract = base.route({ path: '/pet/{petId}', method: 'GET' }).input(zGetPetByIdData).output(zGetPetByIdResponse)
/**
* Updates a pet in the store with form data
*/
export const updatePetWithFormContract = base.route({ path: '/pet/{petId}', method: 'POST' }).input(zUpdatePetWithFormData)
/**
* uploads an image
*/
export const uploadFileContract = base.route({ path: '/pet/{petId}/uploadImage', method: 'POST' }).input(zUploadFileData).output(zUploadFileResponse)
/**
* Returns a map of status codes to quantities
*/
export const getInventoryContract = base.route({ path: '/store/inventory', method: 'GET' }).output(zGetInventoryResponse)
/**
* Place a new order in the store with patch
*/
export const placeOrderPatchContract = base.route({ path: '/store/order', method: 'PATCH' }).input(zPlaceOrderPatchData).output(zPlaceOrderPatchResponse)
/**
* Place a new order in the store
*/
export const placeOrderContract = base.route({ path: '/store/order', method: 'POST' }).input(zPlaceOrderData).output(zPlaceOrderResponse)
/**
* For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
*/
export const deleteOrderContract = base.route({ path: '/store/order/{orderId}', method: 'DELETE' }).input(zDeleteOrderData)
/**
* For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
*/
export const getOrderByIdContract = base.route({ path: '/store/order/{orderId}', method: 'GET' }).input(zGetOrderByIdData).output(zGetOrderByIdResponse)
/**
* This can only be done by the logged in user.
*/
export const createUserContract = base.route({ path: '/user', method: 'POST' }).input(zCreateUserData)
/**
* Creates list of users with given input array
*/
export const createUsersWithListInputContract = base.route({ path: '/user/createWithList', method: 'POST' }).input(zCreateUsersWithListInputData).output(zCreateUsersWithListInputResponse)
/**
* Logs user into the system
*/
export const loginUserContract = base.route({ path: '/user/login', method: 'GET' }).input(zLoginUserData).output(zLoginUserResponse)
/**
* Logs out current logged in user session
*/
export const logoutUserContract = base.route({ path: '/user/logout', method: 'GET' })
/**
* This can only be done by the logged in user.
*/
export const deleteUserContract = base.route({ path: '/user/{username}', method: 'DELETE' }).input(zDeleteUserData)
/**
* Get user by user name
*/
export const getUserByNameContract = base.route({ path: '/user/{username}', method: 'GET' }).input(zGetUserByNameData).output(zGetUserByNameResponse)
/**
* This can only be done by the logged in user.
*/
export const updateUserContract = base.route({ path: '/user/{username}', method: 'PUT' }).input(zUpdateUserData)
export const contracts = {
pet: {
addPetContract,
updatePetContract,
findPetsByStatusContract,
findPetsByTagsContract,
deletePetContract,
getPetByIdContract,
updatePetWithFormContract,
uploadFileContract,
},
store: {
getInventoryContract,
placeOrderPatchContract,
placeOrderContract,
deleteOrderContract,
getOrderByIdContract,
},
user: {
createUserContract,
createUsersWithListInputContract,
loginUserContract,
logoutUserContract,
deleteUserContract,
getUserByNameContract,
updateUserContract,
},
}
export type Contracts = typeof contracts

696
web/gen/types.gen.ts Normal file
View File

@ -0,0 +1,696 @@
// This file is auto-generated by @hey-api/openapi-ts
export type ClientOptions = {
baseUrl: 'https://petstore3.swagger.io/api/v3' | (string & {})
}
export type Order = {
id?: number
petId?: number
quantity?: number
shipDate?: string
/**
* Order Status
*/
status?: '' | string
/**
* HTTP Status
*/
http_status?: 200 | 400 | 500
complete?: boolean
}
export type Customer = {
id?: number
username?: string
address?: Array<Address>
}
export type HappyCustomer = Customer & {
isHappy?: true
}
export type UnhappyCustomer = Customer & {
reasonToBeUnhappy?: string
isHappy?: false
}
export type Address = {
streetName?: string
streetNumber?: string
city?: string
state?: string
zip?: string
}
export type Category = {
id?: number
name?: string
}
export type User = {
id?: number
username?: string
firstName?: string
lastName?: string
email?: string
password?: string
phone?: string
/**
* User Status
*/
userStatus?: number
}
export type Tag = {
id?: number
name?: string
}
export type Pet = (({
type: 'dog'
} & Dog) | ({
type: 'cat'
} & Cat)) & {
id?: number
type: 'dog' | 'cat'
name: string
category?: OpenapiCategory
photoUrls: Array<string>
readonly tags?: Array<Tag>
/**
* pet status in the store
*/
status?: 'available' | 'pending' | 'sold'
}
export type Cat = {
readonly type?: string
name?: string
}
export type Dog = {
readonly type?: string
bark?: string
}
export type FullAddress = Address & {
streetName: string
streetNumber: string
}
export type AddPetRequest = {
id?: number
name: string
category?: Category
photoUrls: Array<string>
tags?: Array<Tag>
/**
* pet status in the store
*/
status?: 'available' | 'pending' | 'sold' | 'in store'
}
export type ApiResponse = {
code?: number
type?: string
message?: string
}
export type OpenapiCategory = {
id?: number
name?: string
}
export type PetWritable = (({
type: 'DogWritable'
} & DogWritable) | ({
type: 'CatWritable'
} & CatWritable)) & {
id?: number
name: string
category?: OpenapiCategory
photoUrls: Array<string>
/**
* pet status in the store
*/
status?: 'available' | 'pending' | 'sold'
}
export type CatWritable = {
name?: string
}
export type DogWritable = {
bark?: string
}
/**
* to request with required page number or pagination
*/
export type Page = string
/**
* to request with required page size
*/
export type PageSize = string
/**
* Pet object that needs to be added to the store
*/
export type Pet2 = PetWritable
/**
* List of user object
*/
export type UserArray = Array<User>
export type AddPetData = {
/**
* Create a new pet in the store
*/
body: AddPetRequest
path?: never
query?: never
url: '/pet'
}
export type AddPetErrors = {
/**
* Pet not found
*/
405: {
code?: number
message?: string
}
}
export type AddPetError = AddPetErrors[keyof AddPetErrors]
export type AddPetResponses = {
/**
* Successful operation
*/
200: Pet
}
export type AddPetResponse = AddPetResponses[keyof AddPetResponses]
export type UpdatePetData = {
/**
* Update an existent pet in the store
*/
body: PetWritable
path?: never
query?: never
url: '/pet'
}
export type UpdatePetErrors = {
/**
* Invalid ID supplied
*/
400: unknown
/**
* Pet not found
*/
404: unknown
/**
* Validation exception
*/
405: unknown
}
export type UpdatePetResponses = {
/**
* Successful operation
*/
200: Pet
}
export type UpdatePetResponse = UpdatePetResponses[keyof UpdatePetResponses]
export type FindPetsByStatusData = {
body?: never
path?: never
query?: {
/**
* Status values that need to be considered for filter
*/
status?: 'available' | 'pending' | 'sold'
}
url: '/pet/findByStatus'
}
export type FindPetsByStatusErrors = {
/**
* Invalid status value
*/
400: unknown
}
export type FindPetsByStatusResponses = {
/**
* successful operation
*/
200: Array<Pet>
}
export type FindPetsByStatusResponse = FindPetsByStatusResponses[keyof FindPetsByStatusResponses]
export type FindPetsByTagsData = {
body?: never
path?: never
query?: {
/**
* Tags to filter by
*/
tags?: Array<string>
/**
* to request with required page number or pagination
*/
page?: string
/**
* to request with required page size
*/
pageSize?: string
}
url: '/pet/findByTags'
}
export type FindPetsByTagsErrors = {
/**
* Invalid tag value
*/
400: unknown
}
export type FindPetsByTagsResponses = {
/**
* successful operation
*/
200: Array<Pet>
}
export type FindPetsByTagsResponse = FindPetsByTagsResponses[keyof FindPetsByTagsResponses]
export type DeletePetData = {
body?: never
headers?: {
api_key?: string
}
path: {
/**
* Pet id to delete
*/
petId: number
}
query?: never
url: '/pet/{petId}'
}
export type DeletePetErrors = {
/**
* Invalid pet value
*/
400: unknown
}
export type DeletePetResponses = {
/**
* items
*/
200: Array<'TYPE1' | 'TYPE2' | 'TYPE3'>
}
export type DeletePetResponse = DeletePetResponses[keyof DeletePetResponses]
export type GetPetByIdData = {
body?: never
path: {
/**
* ID of pet to return
*/
petId: number
}
query?: never
url: '/pet/{petId}'
}
export type GetPetByIdErrors = {
/**
* Invalid ID supplied
*/
400: unknown
/**
* Pet not found
*/
404: unknown
}
export type GetPetByIdResponses = {
/**
* successful operation
*/
200: Pet
}
export type GetPetByIdResponse = GetPetByIdResponses[keyof GetPetByIdResponses]
export type UpdatePetWithFormData = {
body?: never
path: {
/**
* ID of pet that needs to be updated
*/
petId: number
}
query?: {
/**
* Name of pet that needs to be updated
*/
name?: string
/**
* Status of pet that needs to be updated
*/
status?: string
}
url: '/pet/{petId}'
}
export type UpdatePetWithFormErrors = {
/**
* Invalid input
*/
405: unknown
}
export type UploadFileData = {
body?: Blob | File
path: {
/**
* ID of pet to update
*/
petId: number
}
query?: {
/**
* Additional Metadata
*/
additionalMetadata?: string
}
url: '/pet/{petId}/uploadImage'
}
export type UploadFileResponses = {
/**
* successful operation
*/
200: ApiResponse
}
export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]
export type GetInventoryData = {
body?: never
path?: never
query?: never
url: '/store/inventory'
}
export type GetInventoryResponses = {
/**
* successful operation
*/
200: {
[key: string]: number
}
}
export type GetInventoryResponse = GetInventoryResponses[keyof GetInventoryResponses]
export type PlaceOrderPatchData = {
body?: Order
path?: never
query?: never
url: '/store/order'
}
export type PlaceOrderPatchErrors = {
/**
* Invalid input
*/
405: unknown
}
export type PlaceOrderPatchResponses = {
/**
* successful operation
*/
200: Order
}
export type PlaceOrderPatchResponse = PlaceOrderPatchResponses[keyof PlaceOrderPatchResponses]
export type PlaceOrderData = {
/**
* Order description
*/
body?: Order
path?: never
query?: never
url: '/store/order'
}
export type PlaceOrderErrors = {
/**
* Invalid input
*/
405: unknown
}
export type PlaceOrderResponses = {
/**
* successful operation
*/
200: Order
}
export type PlaceOrderResponse = PlaceOrderResponses[keyof PlaceOrderResponses]
export type DeleteOrderData = {
body?: never
path: {
/**
* ID of the order that needs to be deleted
*/
orderId: number
}
query?: never
url: '/store/order/{orderId}'
}
export type DeleteOrderErrors = {
/**
* Invalid ID supplied
*/
400: unknown
/**
* Order not found
*/
404: unknown
}
export type GetOrderByIdData = {
body?: never
path: {
/**
* ID of order that needs to be fetched
*/
orderId: number
}
query?: never
url: '/store/order/{orderId}'
}
export type GetOrderByIdErrors = {
/**
* Invalid ID supplied
*/
400: unknown
/**
* Order not found
*/
404: unknown
}
export type GetOrderByIdResponses = {
/**
* successful operation
*/
200: Order
}
export type GetOrderByIdResponse = GetOrderByIdResponses[keyof GetOrderByIdResponses]
export type CreateUserData = {
/**
* Created user object
*/
body?: User
path?: never
query?: never
url: '/user'
}
export type CreateUserResponses = {
/**
* successful operation
*/
default: User
}
export type CreateUserResponse = CreateUserResponses[keyof CreateUserResponses]
export type CreateUsersWithListInputData = {
body?: Array<User>
path?: never
query?: never
url: '/user/createWithList'
}
export type CreateUsersWithListInputResponses = {
/**
* Successful operation
*/
200: User
/**
* successful operation
*/
default: unknown
}
export type CreateUsersWithListInputResponse = CreateUsersWithListInputResponses[keyof CreateUsersWithListInputResponses]
export type LoginUserData = {
body?: never
path?: never
query?: {
/**
* The user name for login
*/
username?: string
/**
* The password for login in clear text
*/
password?: string
}
url: '/user/login'
}
export type LoginUserErrors = {
/**
* Invalid username/password supplied
*/
400: unknown
}
export type LoginUserResponses = {
/**
* successful operation
*/
200: string
}
export type LoginUserResponse = LoginUserResponses[keyof LoginUserResponses]
export type LogoutUserData = {
body?: never
path?: never
query?: never
url: '/user/logout'
}
export type LogoutUserResponses = {
/**
* successful operation
*/
default: unknown
}
export type DeleteUserData = {
body?: never
path: {
/**
* The name that needs to be deleted
*/
username: string
}
query?: never
url: '/user/{username}'
}
export type DeleteUserErrors = {
/**
* Invalid username supplied
*/
400: unknown
/**
* User not found
*/
404: unknown
}
export type GetUserByNameData = {
body?: never
path: {
/**
* The name that needs to be fetched. Use user1 for testing.
*/
username: string
}
query?: never
url: '/user/{username}'
}
export type GetUserByNameErrors = {
/**
* Invalid username supplied
*/
400: unknown
/**
* User not found
*/
404: unknown
}
export type GetUserByNameResponses = {
/**
* successful operation
*/
200: User
}
export type GetUserByNameResponse = GetUserByNameResponses[keyof GetUserByNameResponses]
export type UpdateUserData = {
/**
* Update an existent user in the store
*/
body?: User
path: {
/**
* name that need to be deleted
*/
username: string
}
query?: never
url: '/user/{username}'
}
export type UpdateUserResponses = {
/**
* successful operation
*/
default: unknown
}

524
web/gen/zod.gen.ts Normal file
View File

@ -0,0 +1,524 @@
// This file is auto-generated by @hey-api/openapi-ts
import { z } from 'zod'
export const zOrder = z.object({
id: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional(),
petId: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional(),
quantity: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).optional(),
shipDate: z.string().datetime().optional(),
status: z.union([
z.literal(''),
z.string().email(),
]).optional(),
http_status: z.union([
z.literal(200),
z.literal(400),
z.literal(500),
]).describe('HTTP Status').optional(),
complete: z.boolean().optional(),
})
export type OrderZodType = z.infer<typeof zOrder>
export const zAddress = z.object({
streetName: z.string().optional(),
streetNumber: z.string().optional(),
city: z.string().optional(),
state: z.string().optional(),
zip: z.string().optional(),
})
export type AddressZodType = z.infer<typeof zAddress>
export const zCustomer = z.object({
id: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional(),
username: z.string().optional(),
address: z.array(zAddress).optional(),
})
export type CustomerZodType = z.infer<typeof zCustomer>
export const zHappyCustomer = zCustomer.and(z.object({
isHappy: z.literal(true).optional(),
}))
export type HappyCustomerZodType = z.infer<typeof zHappyCustomer>
export const zUnhappyCustomer = zCustomer.and(z.object({
reasonToBeUnhappy: z.string().optional(),
isHappy: z.literal(false).optional(),
}))
export type UnhappyCustomerZodType = z.infer<typeof zUnhappyCustomer>
export const zCategory = z.object({
id: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional(),
name: z.string().optional(),
})
export type CategoryZodType = z.infer<typeof zCategory>
export const zUser = z.object({
id: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional(),
username: z.string().optional(),
firstName: z.string().optional(),
lastName: z.string().optional(),
email: z.string().optional(),
password: z.string().optional(),
phone: z.string().optional(),
userStatus: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).describe('User Status').optional(),
})
export type UserZodType = z.infer<typeof zUser>
export const zTag = z.object({
id: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional(),
name: z.string().optional(),
})
export type TagZodType = z.infer<typeof zTag>
export const zCat = z.object({
type: z.string().min(1).readonly().optional(),
name: z.string().optional(),
})
export type CatZodType = z.infer<typeof zCat>
export const zDog = z.object({
type: z.string().min(1).readonly().optional(),
bark: z.string().optional(),
})
export type DogZodType = z.infer<typeof zDog>
export const zFullAddress = zAddress.and(z.object({
streetName: z.string(),
streetNumber: z.string(),
}))
export type FullAddressZodType = z.infer<typeof zFullAddress>
export const zAddPetRequest = z.object({
id: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional(),
name: z.string(),
category: zCategory.optional(),
photoUrls: z.array(z.string()),
tags: z.array(zTag).optional(),
status: z.enum([
'available',
'pending',
'sold',
'in store',
]).describe('pet status in the store').optional(),
})
export type AddPetRequestZodType = z.infer<typeof zAddPetRequest>
export const zApiResponse = z.object({
code: z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' }).optional(),
type: z.string().optional(),
message: z.string().optional(),
})
export type ApiResponseZodType = z.infer<typeof zApiResponse>
export const zOpenapiCategory = z.object({
id: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional(),
name: z.string().optional(),
})
export type OpenapiCategoryZodType = z.infer<typeof zOpenapiCategory>
export const zPet = z.intersection(z.union([
z.object({
type: z.literal('dog'),
}).and(zDog),
z.object({
type: z.literal('cat'),
}).and(zCat),
]), z.object({
id: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional(),
type: z.enum(['dog', 'cat']),
name: z.string(),
category: zOpenapiCategory.optional(),
photoUrls: z.array(z.string()),
tags: z.array(zTag).readonly().optional(),
status: z.enum([
'available',
'pending',
'sold',
]).describe('pet status in the store').optional(),
}))
export type PetZodType = z.infer<typeof zPet>
export const zCatWritable = z.object({
name: z.string().optional(),
})
export type CatWritableZodType = z.infer<typeof zCatWritable>
export const zDogWritable = z.object({
bark: z.string().optional(),
})
export type DogWritableZodType = z.infer<typeof zDogWritable>
export const zPetWritable = z.intersection(z.union([
z.object({
type: z.literal('DogWritable'),
}).and(zDogWritable),
z.object({
type: z.literal('CatWritable'),
}).and(zCatWritable),
]), z.object({
id: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional(),
name: z.string(),
category: zOpenapiCategory.optional(),
photoUrls: z.array(z.string()),
status: z.enum([
'available',
'pending',
'sold',
]).describe('pet status in the store').optional(),
}))
export type PetWritableZodType = z.infer<typeof zPetWritable>
/**
* to request with required page number or pagination
*/
export const zPage = z.string().describe('to request with required page number or pagination')
export type PageZodType = z.infer<typeof zPage>
/**
* to request with required page size
*/
export const zPageSize = z.string().describe('to request with required page size')
export type PageSizeZodType = z.infer<typeof zPageSize>
/**
* Pet object that needs to be added to the store
*/
export const zPet2 = zPetWritable
export type PetZodType2 = z.infer<typeof zPet2>
/**
* List of user object
*/
export const zUserArray = z.array(zUser).describe('List of user object')
export type UserArrayZodType = z.infer<typeof zUserArray>
export const zAddPetData = z.object({
body: zAddPetRequest,
path: z.never().optional(),
query: z.never().optional(),
})
export type AddPetDataZodType = z.infer<typeof zAddPetData>
/**
* Successful operation
*/
export const zAddPetResponse = zPet
export type AddPetResponseZodType = z.infer<typeof zAddPetResponse>
export const zUpdatePetData = z.object({
body: zPetWritable,
path: z.never().optional(),
query: z.never().optional(),
})
export type UpdatePetDataZodType = z.infer<typeof zUpdatePetData>
/**
* Successful operation
*/
export const zUpdatePetResponse = zPet
export type UpdatePetResponseZodType = z.infer<typeof zUpdatePetResponse>
export const zFindPetsByStatusData = z.object({
body: z.never().optional(),
path: z.never().optional(),
query: z.object({
status: z.enum([
'available',
'pending',
'sold',
]).describe('Status values that need to be considered for filter').optional(),
}).optional(),
})
export type FindPetsByStatusDataZodType = z.infer<typeof zFindPetsByStatusData>
/**
* successful operation
*/
export const zFindPetsByStatusResponse = z.array(zPet).describe('successful operation')
export type FindPetsByStatusResponseZodType = z.infer<typeof zFindPetsByStatusResponse>
export const zFindPetsByTagsData = z.object({
body: z.never().optional(),
path: z.never().optional(),
query: z.object({
tags: z.array(z.string()).describe('Tags to filter by').optional(),
page: z.string().describe('to request with required page number or pagination').optional(),
pageSize: z.string().describe('to request with required page size').optional(),
}).optional(),
})
export type FindPetsByTagsDataZodType = z.infer<typeof zFindPetsByTagsData>
/**
* successful operation
*/
export const zFindPetsByTagsResponse = z.array(zPet).describe('successful operation')
export type FindPetsByTagsResponseZodType = z.infer<typeof zFindPetsByTagsResponse>
export const zDeletePetData = z.object({
body: z.never().optional(),
path: z.object({
petId: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).describe('Pet id to delete'),
}),
query: z.never().optional(),
headers: z.object({
api_key: z.string().optional(),
}).optional(),
})
export type DeletePetDataZodType = z.infer<typeof zDeletePetData>
/**
* items
*/
export const zDeletePetResponse = z.array(z.enum([
'TYPE1',
'TYPE2',
'TYPE3',
])).describe('items')
export type DeletePetResponseZodType = z.infer<typeof zDeletePetResponse>
export const zGetPetByIdData = z.object({
body: z.never().optional(),
path: z.object({
petId: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).describe('ID of pet to return'),
}),
query: z.never().optional(),
})
export type GetPetByIdDataZodType = z.infer<typeof zGetPetByIdData>
/**
* successful operation
*/
export const zGetPetByIdResponse = zPet
export type GetPetByIdResponseZodType = z.infer<typeof zGetPetByIdResponse>
export const zUpdatePetWithFormData = z.object({
body: z.never().optional(),
path: z.object({
petId: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).describe('ID of pet that needs to be updated'),
}),
query: z.object({
name: z.string().describe('Name of pet that needs to be updated').optional(),
status: z.string().describe('Status of pet that needs to be updated').optional(),
}).optional(),
})
export type UpdatePetWithFormDataZodType = z.infer<typeof zUpdatePetWithFormData>
export const zUploadFileData = z.object({
body: z.string().optional(),
path: z.object({
petId: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).describe('ID of pet to update'),
}),
query: z.object({
additionalMetadata: z.string().describe('Additional Metadata').optional(),
}).optional(),
})
export type UploadFileDataZodType = z.infer<typeof zUploadFileData>
/**
* successful operation
*/
export const zUploadFileResponse = zApiResponse
export type UploadFileResponseZodType = z.infer<typeof zUploadFileResponse>
export const zGetInventoryData = z.object({
body: z.never().optional(),
path: z.never().optional(),
query: z.never().optional(),
})
export type GetInventoryDataZodType = z.infer<typeof zGetInventoryData>
/**
* successful operation
*/
export const zGetInventoryResponse = z.record(z.number().int().min(-2147483648, { message: 'Invalid value: Expected int32 to be >= -2147483648' }).max(2147483647, { message: 'Invalid value: Expected int32 to be <= 2147483647' })).describe('successful operation')
export type GetInventoryResponseZodType = z.infer<typeof zGetInventoryResponse>
export const zPlaceOrderPatchData = z.object({
body: zOrder.optional(),
path: z.never().optional(),
query: z.never().optional(),
})
export type PlaceOrderPatchDataZodType = z.infer<typeof zPlaceOrderPatchData>
/**
* successful operation
*/
export const zPlaceOrderPatchResponse = zOrder
export type PlaceOrderPatchResponseZodType = z.infer<typeof zPlaceOrderPatchResponse>
export const zPlaceOrderData = z.object({
body: zOrder.optional(),
path: z.never().optional(),
query: z.never().optional(),
})
export type PlaceOrderDataZodType = z.infer<typeof zPlaceOrderData>
/**
* successful operation
*/
export const zPlaceOrderResponse = zOrder
export type PlaceOrderResponseZodType = z.infer<typeof zPlaceOrderResponse>
export const zDeleteOrderData = z.object({
body: z.never().optional(),
path: z.object({
orderId: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).describe('ID of the order that needs to be deleted'),
}),
query: z.never().optional(),
})
export type DeleteOrderDataZodType = z.infer<typeof zDeleteOrderData>
export const zGetOrderByIdData = z.object({
body: z.never().optional(),
path: z.object({
orderId: z.coerce.bigint().min(BigInt('-9223372036854775808'), { message: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { message: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).describe('ID of order that needs to be fetched'),
}),
query: z.never().optional(),
})
export type GetOrderByIdDataZodType = z.infer<typeof zGetOrderByIdData>
/**
* successful operation
*/
export const zGetOrderByIdResponse = zOrder
export type GetOrderByIdResponseZodType = z.infer<typeof zGetOrderByIdResponse>
export const zCreateUserData = z.object({
body: zUser.optional(),
path: z.never().optional(),
query: z.never().optional(),
})
export type CreateUserDataZodType = z.infer<typeof zCreateUserData>
/**
* successful operation
*/
export const zCreateUserResponse = zUser
export type CreateUserResponseZodType = z.infer<typeof zCreateUserResponse>
export const zCreateUsersWithListInputData = z.object({
body: z.array(zUser).optional(),
path: z.never().optional(),
query: z.never().optional(),
})
export type CreateUsersWithListInputDataZodType = z.infer<typeof zCreateUsersWithListInputData>
export const zCreateUsersWithListInputResponse = z.union([
zUser,
z.unknown().describe('successful operation'),
])
export type CreateUsersWithListInputResponseZodType = z.infer<typeof zCreateUsersWithListInputResponse>
export const zLoginUserData = z.object({
body: z.never().optional(),
path: z.never().optional(),
query: z.object({
username: z.string().describe('The user name for login').optional(),
password: z.string().describe('The password for login in clear text').optional(),
}).optional(),
})
export type LoginUserDataZodType = z.infer<typeof zLoginUserData>
/**
* successful operation
*/
export const zLoginUserResponse = z.string().describe('successful operation')
export type LoginUserResponseZodType = z.infer<typeof zLoginUserResponse>
export const zLogoutUserData = z.object({
body: z.never().optional(),
path: z.never().optional(),
query: z.never().optional(),
})
export type LogoutUserDataZodType = z.infer<typeof zLogoutUserData>
export const zDeleteUserData = z.object({
body: z.never().optional(),
path: z.object({
username: z.string().describe('The name that needs to be deleted'),
}),
query: z.never().optional(),
})
export type DeleteUserDataZodType = z.infer<typeof zDeleteUserData>
export const zGetUserByNameData = z.object({
body: z.never().optional(),
path: z.object({
username: z.string().describe('The name that needs to be fetched. Use user1 for testing. '),
}),
query: z.never().optional(),
})
export type GetUserByNameDataZodType = z.infer<typeof zGetUserByNameData>
/**
* successful operation
*/
export const zGetUserByNameResponse = zUser
export type GetUserByNameResponseZodType = z.infer<typeof zGetUserByNameResponse>
export const zUpdateUserData = z.object({
body: zUser.optional(),
path: z.object({
username: z.string().describe('name that need to be deleted'),
}),
query: z.never().optional(),
})
export type UpdateUserDataZodType = z.infer<typeof zUpdateUserData>

View File

@ -1,5 +1,7 @@
import { defineConfig } from '@hey-api/openapi-ts'
import { defineConfig as defineOrpcConfig } from './plugins/hey-api-orpc'
export default defineConfig({
input: './open-api/petStore.yaml',
output: './gen',
@ -15,5 +17,9 @@ export default defineConfig({
infer: true,
},
},
defineOrpcConfig({
output: 'orpc',
generateRouter: true,
}),
],
})

View File

@ -0,0 +1,19 @@
import type { OrpcPlugin } from './types'
import { definePluginConfig } from '@hey-api/openapi-ts'
import { handler } from './plugin'
export const defaultConfig: OrpcPlugin['Config'] = {
config: {
baseName: 'base',
exportFromIndex: false,
generateRouter: true,
output: 'orpc',
},
dependencies: ['@hey-api/typescript', 'zod'],
handler,
name: 'orpc',
}
export const defineConfig = definePluginConfig(defaultConfig)

View File

@ -0,0 +1,2 @@
export { defaultConfig, defineConfig } from './config'
export type { Config, OrpcPlugin, ResolvedConfig } from './types'

View File

@ -0,0 +1,221 @@
import type { IR } from '@hey-api/openapi-ts'
import type { OrpcPlugin } from './types'
import { $ } from '@hey-api/openapi-ts'
function capitalizeFirst(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1)
}
function toZodSchemaName(operationId: string, type: 'data' | 'response'): string {
const pascalName = capitalizeFirst(operationId)
return type === 'data' ? `z${pascalName}Data` : `z${pascalName}Response`
}
type OperationInfo = {
id: string
method: string
path: string
description?: string
deprecated?: boolean
tags: string[]
hasInput: boolean
hasOutput: boolean
zodDataSchema: string
zodResponseSchema: string
}
function collectOperation(operation: IR.OperationObject): OperationInfo {
const id = operation.id || `${operation.method}_${operation.path.replace(/[{}/]/g, '_')}`
const hasPathParams = Boolean(operation.parameters?.path && Object.keys(operation.parameters.path).length > 0)
const hasQueryParams = Boolean(operation.parameters?.query && Object.keys(operation.parameters.query).length > 0)
const hasBody = Boolean(operation.body)
const hasInput = hasPathParams || hasQueryParams || hasBody
// Check if operation has a successful response with actual content
// Look for 2xx responses that have a schema with mediaType (indicating response body)
let hasOutput = false
if (operation.responses) {
for (const [statusCode, response] of Object.entries(operation.responses)) {
// Check for 2xx success responses with actual content
if (statusCode.startsWith('2') && response?.mediaType && response?.schema) {
hasOutput = true
break
}
}
}
return {
deprecated: operation.deprecated,
description: operation.description || operation.summary,
hasInput,
hasOutput,
id,
method: operation.method.toUpperCase(),
path: operation.path,
tags: operation.tags ? [...operation.tags] : ['default'],
zodDataSchema: toZodSchemaName(id, 'data'),
zodResponseSchema: toZodSchemaName(id, 'response'),
}
}
export const handler: OrpcPlugin['Handler'] = ({ plugin }) => {
const config = plugin.config
const operations: OperationInfo[] = []
const zodImports = new Set<string>()
// Collect all operations using hey-api's forEach
plugin.forEach('operation', (event) => {
const info = collectOperation(event.operation)
operations.push(info)
// Collect zod imports
if (info.hasInput) {
zodImports.add(info.zodDataSchema)
}
if (info.hasOutput) {
zodImports.add(info.zodResponseSchema)
}
})
// Register external symbols for imports
const symbolOc = plugin.symbol('oc', {
exported: false,
external: '@orpc/contract',
})
// Register zod schema symbols (they come from zod plugin)
const zodSchemaSymbols: Record<string, ReturnType<typeof plugin.symbol>> = {}
for (const schemaName of zodImports) {
zodSchemaSymbols[schemaName] = plugin.symbol(schemaName, {
exported: false,
external: './zod.gen',
})
}
// Create base contract: export const base = oc.$route({ inputStructure: 'detailed' })
const baseSymbol = plugin.symbol(config.baseName, {
exported: true,
meta: {
category: 'schema',
},
})
const baseNode = $.const(baseSymbol)
.export()
.assign(
$(symbolOc)
.attr('$route')
.call(
$.object()
.prop('inputStructure', $.literal('detailed')),
),
)
plugin.node(baseNode)
// Create contract for each operation
// Store symbols for later use in contracts object
const contractSymbols: Record<string, ReturnType<typeof plugin.symbol>> = {}
for (const op of operations) {
const contractSymbol = plugin.symbol(`${op.id}Contract`, {
exported: true,
meta: {
category: 'schema',
},
})
contractSymbols[op.id] = contractSymbol
// Build the call chain: base.route({...}).input(...).output(...)
let expression = $(baseSymbol)
.attr('route')
.call(
$.object()
.prop('path', $.literal(op.path))
.prop('method', $.literal(op.method)),
)
// .input(zodDataSchema) if has input
if (op.hasInput) {
expression = expression
.attr('input')
.call($(zodSchemaSymbols[op.zodDataSchema]))
}
// .output(zodResponseSchema) if has output
if (op.hasOutput) {
expression = expression
.attr('output')
.call($(zodSchemaSymbols[op.zodResponseSchema]))
}
const contractNode = $.const(contractSymbol)
.export()
.$if(op.description || op.deprecated, (node) => {
const docLines: string[] = []
if (op.description) {
docLines.push(op.description)
}
if (op.deprecated) {
docLines.push('@deprecated')
}
return node.doc(docLines)
})
.assign(expression)
plugin.node(contractNode)
}
// Create contracts object export if enabled
if (config.generateRouter) {
// Group operations by tag
const operationsByTag = new Map<string, OperationInfo[]>()
for (const op of operations) {
const tag = op.tags[0]
if (!operationsByTag.has(tag)) {
operationsByTag.set(tag, [])
}
operationsByTag.get(tag)!.push(op)
}
// Build contracts object
const contractsObject = $.object()
for (const [tag, tagOps] of operationsByTag) {
const tagKey = tag.charAt(0).toLowerCase() + tag.slice(1)
const tagObject = $.object()
for (const op of tagOps) {
const contractSymbol = contractSymbols[op.id]
if (contractSymbol) {
tagObject.prop(`${op.id}Contract`, $(contractSymbol))
}
}
contractsObject.prop(tagKey, tagObject)
}
const contractsSymbol = plugin.symbol('contracts', {
exported: true,
meta: {
category: 'schema',
},
})
const contractsNode = $.const(contractsSymbol)
.export()
.assign(contractsObject)
plugin.node(contractsNode)
// Create type export: export type Contracts = typeof contracts
const contractsTypeSymbol = plugin.symbol('Contracts', {
exported: true,
meta: {
category: 'type',
},
})
const contractsTypeNode = $.type.alias(contractsTypeSymbol)
.export()
.type($.type.query($(contractsSymbol)))
plugin.node(contractsTypeNode)
}
}

View File

@ -0,0 +1,36 @@
import type { DefinePlugin } from '@hey-api/openapi-ts'
export type Config = { name: 'orpc' } & {
/**
* Name of the generated file.
* @default 'orpc'
*/
output?: string
/**
* The name of the base contract variable.
* @default 'base'
*/
baseName?: string
/**
* Whether to generate a contracts object that combines all contracts.
* @default true
*/
generateRouter?: boolean
/**
* Whether to export from index file.
* @default false
*/
exportFromIndex?: boolean
}
export type ResolvedConfig = Config & {
output: string
baseName: string
generateRouter: boolean
exportFromIndex: boolean
}
export type OrpcPlugin = DefinePlugin<Config, ResolvedConfig>