From f3eaf5a13de07487c10d4c3b5bf7add192c5d3f9 Mon Sep 17 00:00:00 2001
From: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
Date: Sat, 24 Jan 2026 23:11:27 +0800
Subject: [PATCH] hey api orpc plugin
---
web/gen/index.ts | 3 +
web/gen/orpc.gen.ts | 137 ++++++
web/gen/types.gen.ts | 696 +++++++++++++++++++++++++++++
web/gen/zod.gen.ts | 524 ++++++++++++++++++++++
web/openapi-ts.config.ts | 6 +
web/plugins/hey-api-orpc/config.ts | 19 +
web/plugins/hey-api-orpc/index.ts | 2 +
web/plugins/hey-api-orpc/plugin.ts | 221 +++++++++
web/plugins/hey-api-orpc/types.ts | 36 ++
9 files changed, 1644 insertions(+)
create mode 100644 web/gen/index.ts
create mode 100644 web/gen/orpc.gen.ts
create mode 100644 web/gen/types.gen.ts
create mode 100644 web/gen/zod.gen.ts
create mode 100644 web/plugins/hey-api-orpc/config.ts
create mode 100644 web/plugins/hey-api-orpc/index.ts
create mode 100644 web/plugins/hey-api-orpc/plugin.ts
create mode 100644 web/plugins/hey-api-orpc/types.ts
diff --git a/web/gen/index.ts b/web/gen/index.ts
new file mode 100644
index 0000000000..7b519fe51c
--- /dev/null
+++ b/web/gen/index.ts
@@ -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'
diff --git a/web/gen/orpc.gen.ts b/web/gen/orpc.gen.ts
new file mode 100644
index 0000000000..e1167ba4eb
--- /dev/null
+++ b/web/gen/orpc.gen.ts
@@ -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
diff --git a/web/gen/types.gen.ts b/web/gen/types.gen.ts
new file mode 100644
index 0000000000..1af881a152
--- /dev/null
+++ b/web/gen/types.gen.ts
@@ -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
+}
+
+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
+ readonly tags?: Array
+ /**
+ * 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
+ tags?: Array
+ /**
+ * 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
+ /**
+ * 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
+
+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
+}
+
+export type FindPetsByStatusResponse = FindPetsByStatusResponses[keyof FindPetsByStatusResponses]
+
+export type FindPetsByTagsData = {
+ body?: never
+ path?: never
+ query?: {
+ /**
+ * Tags to filter by
+ */
+ tags?: Array
+ /**
+ * 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
+}
+
+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
+ 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
+}
diff --git a/web/gen/zod.gen.ts b/web/gen/zod.gen.ts
new file mode 100644
index 0000000000..8198a96619
--- /dev/null
+++ b/web/gen/zod.gen.ts
@@ -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
+
+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
+
+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
+
+export const zHappyCustomer = zCustomer.and(z.object({
+ isHappy: z.literal(true).optional(),
+}))
+
+export type HappyCustomerZodType = z.infer
+
+export const zUnhappyCustomer = zCustomer.and(z.object({
+ reasonToBeUnhappy: z.string().optional(),
+ isHappy: z.literal(false).optional(),
+}))
+
+export type UnhappyCustomerZodType = z.infer
+
+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
+
+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
+
+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
+
+export const zCat = z.object({
+ type: z.string().min(1).readonly().optional(),
+ name: z.string().optional(),
+})
+
+export type CatZodType = z.infer
+
+export const zDog = z.object({
+ type: z.string().min(1).readonly().optional(),
+ bark: z.string().optional(),
+})
+
+export type DogZodType = z.infer
+
+export const zFullAddress = zAddress.and(z.object({
+ streetName: z.string(),
+ streetNumber: z.string(),
+}))
+
+export type FullAddressZodType = z.infer
+
+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
+
+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
+
+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
+
+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
+
+export const zCatWritable = z.object({
+ name: z.string().optional(),
+})
+
+export type CatWritableZodType = z.infer
+
+export const zDogWritable = z.object({
+ bark: z.string().optional(),
+})
+
+export type DogWritableZodType = z.infer
+
+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
+
+/**
+ * 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
+
+/**
+ * to request with required page size
+ */
+export const zPageSize = z.string().describe('to request with required page size')
+
+export type PageSizeZodType = z.infer
+
+/**
+ * Pet object that needs to be added to the store
+ */
+export const zPet2 = zPetWritable
+
+export type PetZodType2 = z.infer
+
+/**
+ * List of user object
+ */
+export const zUserArray = z.array(zUser).describe('List of user object')
+
+export type UserArrayZodType = z.infer
+
+export const zAddPetData = z.object({
+ body: zAddPetRequest,
+ path: z.never().optional(),
+ query: z.never().optional(),
+})
+
+export type AddPetDataZodType = z.infer
+
+/**
+ * Successful operation
+ */
+export const zAddPetResponse = zPet
+
+export type AddPetResponseZodType = z.infer
+
+export const zUpdatePetData = z.object({
+ body: zPetWritable,
+ path: z.never().optional(),
+ query: z.never().optional(),
+})
+
+export type UpdatePetDataZodType = z.infer
+
+/**
+ * Successful operation
+ */
+export const zUpdatePetResponse = zPet
+
+export type UpdatePetResponseZodType = z.infer
+
+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
+
+/**
+ * successful operation
+ */
+export const zFindPetsByStatusResponse = z.array(zPet).describe('successful operation')
+
+export type FindPetsByStatusResponseZodType = z.infer
+
+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
+
+/**
+ * successful operation
+ */
+export const zFindPetsByTagsResponse = z.array(zPet).describe('successful operation')
+
+export type FindPetsByTagsResponseZodType = z.infer
+
+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
+
+/**
+ * items
+ */
+export const zDeletePetResponse = z.array(z.enum([
+ 'TYPE1',
+ 'TYPE2',
+ 'TYPE3',
+])).describe('items')
+
+export type DeletePetResponseZodType = z.infer
+
+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
+
+/**
+ * successful operation
+ */
+export const zGetPetByIdResponse = zPet
+
+export type GetPetByIdResponseZodType = z.infer
+
+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
+
+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
+
+/**
+ * successful operation
+ */
+export const zUploadFileResponse = zApiResponse
+
+export type UploadFileResponseZodType = z.infer
+
+export const zGetInventoryData = z.object({
+ body: z.never().optional(),
+ path: z.never().optional(),
+ query: z.never().optional(),
+})
+
+export type GetInventoryDataZodType = z.infer
+
+/**
+ * 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
+
+export const zPlaceOrderPatchData = z.object({
+ body: zOrder.optional(),
+ path: z.never().optional(),
+ query: z.never().optional(),
+})
+
+export type PlaceOrderPatchDataZodType = z.infer
+
+/**
+ * successful operation
+ */
+export const zPlaceOrderPatchResponse = zOrder
+
+export type PlaceOrderPatchResponseZodType = z.infer
+
+export const zPlaceOrderData = z.object({
+ body: zOrder.optional(),
+ path: z.never().optional(),
+ query: z.never().optional(),
+})
+
+export type PlaceOrderDataZodType = z.infer
+
+/**
+ * successful operation
+ */
+export const zPlaceOrderResponse = zOrder
+
+export type PlaceOrderResponseZodType = z.infer
+
+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
+
+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
+
+/**
+ * successful operation
+ */
+export const zGetOrderByIdResponse = zOrder
+
+export type GetOrderByIdResponseZodType = z.infer
+
+export const zCreateUserData = z.object({
+ body: zUser.optional(),
+ path: z.never().optional(),
+ query: z.never().optional(),
+})
+
+export type CreateUserDataZodType = z.infer
+
+/**
+ * successful operation
+ */
+export const zCreateUserResponse = zUser
+
+export type CreateUserResponseZodType = z.infer
+
+export const zCreateUsersWithListInputData = z.object({
+ body: z.array(zUser).optional(),
+ path: z.never().optional(),
+ query: z.never().optional(),
+})
+
+export type CreateUsersWithListInputDataZodType = z.infer
+
+export const zCreateUsersWithListInputResponse = z.union([
+ zUser,
+ z.unknown().describe('successful operation'),
+])
+
+export type CreateUsersWithListInputResponseZodType = z.infer
+
+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
+
+/**
+ * successful operation
+ */
+export const zLoginUserResponse = z.string().describe('successful operation')
+
+export type LoginUserResponseZodType = z.infer
+
+export const zLogoutUserData = z.object({
+ body: z.never().optional(),
+ path: z.never().optional(),
+ query: z.never().optional(),
+})
+
+export type LogoutUserDataZodType = z.infer
+
+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
+
+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
+
+/**
+ * successful operation
+ */
+export const zGetUserByNameResponse = zUser
+
+export type GetUserByNameResponseZodType = z.infer
+
+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
diff --git a/web/openapi-ts.config.ts b/web/openapi-ts.config.ts
index 3aaaa2daee..6641db0b0e 100644
--- a/web/openapi-ts.config.ts
+++ b/web/openapi-ts.config.ts
@@ -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,
+ }),
],
})
diff --git a/web/plugins/hey-api-orpc/config.ts b/web/plugins/hey-api-orpc/config.ts
new file mode 100644
index 0000000000..1cda602e9d
--- /dev/null
+++ b/web/plugins/hey-api-orpc/config.ts
@@ -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)
diff --git a/web/plugins/hey-api-orpc/index.ts b/web/plugins/hey-api-orpc/index.ts
new file mode 100644
index 0000000000..f2f65bcd22
--- /dev/null
+++ b/web/plugins/hey-api-orpc/index.ts
@@ -0,0 +1,2 @@
+export { defaultConfig, defineConfig } from './config'
+export type { Config, OrpcPlugin, ResolvedConfig } from './types'
diff --git a/web/plugins/hey-api-orpc/plugin.ts b/web/plugins/hey-api-orpc/plugin.ts
new file mode 100644
index 0000000000..1713778800
--- /dev/null
+++ b/web/plugins/hey-api-orpc/plugin.ts
@@ -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()
+
+ // 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> = {}
+ 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> = {}
+
+ 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()
+ 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)
+ }
+}
diff --git a/web/plugins/hey-api-orpc/types.ts b/web/plugins/hey-api-orpc/types.ts
new file mode 100644
index 0000000000..b9bdcbf653
--- /dev/null
+++ b/web/plugins/hey-api-orpc/types.ts
@@ -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