fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: clean up fetch client API

Lubos 805e7688 10c786df

+296 -437
+3 -3
examples/openapi-ts-fetch/src/App.tsx
··· 1 1 import './App.css'; 2 2 3 3 import { createClient } from '@hey-api/client-fetch'; 4 - // import { useState } from 'react'; 5 4 5 + // import { useState } from 'react'; 6 6 import { $Email } from './client/schemas.gen'; 7 7 import { 8 8 buyMuseumTickets, ··· 24 24 }, 25 25 }); 26 26 if (error) { 27 - console.log(error.title) 27 + console.log(error.title); 28 28 } 29 - console.log(data) 29 + console.log(data); 30 30 // setPet(data); 31 31 }; 32 32
+16 -14
examples/openapi-ts-fetch/src/client/services.gen.ts
··· 33 33 * Get museum hours 34 34 * Get upcoming museum operating hours. 35 35 */ 36 - export const getMuseumHours = (options?: Options<GetMuseumHoursData>) => client.get<GetMuseumHoursResponse2, GetMuseumHoursError>({ 36 + export const getMuseumHours = (options?: Options<GetMuseumHoursData>) => 37 + client.get<GetMuseumHoursResponse2, GetMuseumHoursError>({ 37 38 ...options, 38 39 url: '/museum-hours', 39 40 }); ··· 42 43 * Create special events 43 44 * Creates a new special event for the museum. 44 45 */ 45 - export const createSpecialEvent = ( 46 - options: Options<CreateSpecialEventData>, 47 - ) => client.post<CreateSpecialEventResponse, CreateSpecialEventError>({ 46 + export const createSpecialEvent = (options: Options<CreateSpecialEventData>) => 47 + client.post<CreateSpecialEventResponse, CreateSpecialEventError>({ 48 48 ...options, 49 49 url: '/special-events', 50 50 }); ··· 53 53 * List special events 54 54 * Return a list of upcoming special events at the museum. 55 55 */ 56 - export const listSpecialEvents = (options?: Options<ListSpecialEventsData>) => client.get<ListSpecialEventsResponse2, ListSpecialEventsError>({ 56 + export const listSpecialEvents = (options?: Options<ListSpecialEventsData>) => 57 + client.get<ListSpecialEventsResponse2, ListSpecialEventsError>({ 57 58 ...options, 58 59 url: '/special-events', 59 60 }); ··· 62 63 * Get special event 63 64 * Get details about a special event. 64 65 */ 65 - export const getSpecialEvent = (options: Options<GetSpecialEventData>) => client.get<GetSpecialEventResponse, GetSpecialEventError>({ 66 + export const getSpecialEvent = (options: Options<GetSpecialEventData>) => 67 + client.get<GetSpecialEventResponse, GetSpecialEventError>({ 66 68 ...options, 67 69 url: '/special-events/{eventId}', 68 70 }); ··· 71 73 * Update special event 72 74 * Update the details of a special event. 73 75 */ 74 - export const updateSpecialEvent = ( 75 - options: Options<UpdateSpecialEventData>, 76 - ) => client.patch<UpdateSpecialEventResponse, UpdateSpecialEventError>({ 76 + export const updateSpecialEvent = (options: Options<UpdateSpecialEventData>) => 77 + client.patch<UpdateSpecialEventResponse, UpdateSpecialEventError>({ 77 78 ...options, 78 79 url: '/special-events/{eventId}', 79 80 }); ··· 82 83 * Delete special event 83 84 * Delete a special event from the collection. Allows museum to cancel planned events. 84 85 */ 85 - export const deleteSpecialEvent = ( 86 - options: Options<DeleteSpecialEventData>, 87 - ) => client.delete<DeleteSpecialEventResponse, DeleteSpecialEventError>({ 86 + export const deleteSpecialEvent = (options: Options<DeleteSpecialEventData>) => 87 + client.delete<DeleteSpecialEventResponse, DeleteSpecialEventError>({ 88 88 ...options, 89 89 url: '/special-events/{eventId}', 90 90 }); ··· 93 93 * Buy museum tickets 94 94 * Purchase museum tickets for general entry or special events. 95 95 */ 96 - export const buyMuseumTickets = (options: Options<BuyMuseumTicketsData>) => client.post<BuyMuseumTicketsResponse2, BuyMuseumTicketsError>({ 96 + export const buyMuseumTickets = (options: Options<BuyMuseumTicketsData>) => 97 + client.post<BuyMuseumTicketsResponse2, BuyMuseumTicketsError>({ 97 98 ...options, 98 99 url: '/tickets', 99 100 }); ··· 102 103 * Get ticket QR code 103 104 * Return an image of your ticket with scannable QR code. Used for event entry. 104 105 */ 105 - export const getTicketCode = (options: Options<GetTicketCodeData>) => client.get<GetTicketCodeResponse2, GetTicketCodeError>({ 106 + export const getTicketCode = (options: Options<GetTicketCodeData>) => 107 + client.get<GetTicketCodeResponse2, GetTicketCodeError>({ 106 108 ...options, 107 109 url: '/tickets/{ticketId}/qr', 108 110 });
-3
packages/client-fetch/package.json
··· 49 49 }, 50 50 "engines": { 51 51 "node": "^18.0.0 || >=20.0.0" 52 - }, 53 - "dependencies": { 54 - "@hey-api/client-core": "workspace:*" 55 52 } 56 53 }
+78 -222
packages/client-fetch/src/index.ts
··· 1 - import type { 2 - ApiRequestOptions, 3 - ApiResult, 4 - OnCancel, 5 - OpenAPIConfig, 6 - } from '@hey-api/client-core'; 7 - import { 8 - base64, 9 - CancelablePromise, 10 - catchErrorCodes, 11 - getFormData, 12 - getUrl as _getUrl, 13 - isBlob, 14 - isFormData, 15 - isString, 16 - isStringWithValue, 17 - resolve, 18 - } from '@hey-api/client-core'; 19 - 20 1 import type { Config, Req, RequestResult } from './types'; 21 2 import { 22 3 createDefaultConfig, ··· 26 7 mergeHeaders, 27 8 } from './utils'; 28 9 29 - const getHeaders = async ( 30 - config: OpenAPIConfig, 31 - options: ApiRequestOptions, 32 - ): Promise<Headers> => { 33 - const [token, username, password, additionalHeaders] = await Promise.all([ 34 - resolve(options, config.TOKEN), 35 - resolve(options, config.USERNAME), 36 - resolve(options, config.PASSWORD), 37 - resolve(options, config.HEADERS), 38 - ]); 39 - 40 - const headers = Object.entries({ 41 - Accept: 'application/json', 42 - ...additionalHeaders, 43 - ...options.headers, 44 - }) 45 - .filter(([, value]) => value !== undefined && value !== null) 46 - .reduce( 47 - (headers, [key, value]) => ({ 48 - ...headers, 49 - [key]: String(value), 50 - }), 51 - {} as Record<string, string>, 52 - ); 53 - 54 - if (isStringWithValue(token)) { 55 - headers['Authorization'] = `Bearer ${token}`; 56 - } 57 - 58 - if (isStringWithValue(username) && isStringWithValue(password)) { 59 - const credentials = base64(`${username}:${password}`); 60 - headers['Authorization'] = `Basic ${credentials}`; 61 - } 62 - 63 - if (options.body !== undefined) { 64 - if (options.mediaType) { 65 - headers['Content-Type'] = options.mediaType; 66 - } else if (isBlob(options.body)) { 67 - headers['Content-Type'] = options.body.type || 'application/octet-stream'; 68 - } else if (isString(options.body)) { 69 - headers['Content-Type'] = 'text/plain'; 70 - } else if (!isFormData(options.body)) { 71 - headers['Content-Type'] = 'application/json'; 72 - } 73 - } 74 - 75 - return new Headers(headers); 76 - }; 77 - 78 - const getRequestBody = (options: ApiRequestOptions): unknown => { 79 - if (options.body !== undefined) { 80 - if ( 81 - options.mediaType?.includes('application/json') || 82 - options.mediaType?.includes('+json') 83 - ) { 84 - return JSON.stringify(options.body); 85 - } else if ( 86 - isString(options.body) || 87 - isBlob(options.body) || 88 - isFormData(options.body) 89 - ) { 90 - return options.body; 91 - } else { 92 - return JSON.stringify(options.body); 93 - } 94 - } 95 - return undefined; 96 - }; 97 - 98 - const sendRequest = async ( 99 - config: OpenAPIConfig, 100 - options: ApiRequestOptions, 101 - url: string, 102 - body: any, 103 - formData: FormData | undefined, 104 - headers: Headers, 105 - onCancel: OnCancel, 106 - ): Promise<Response> => { 107 - const controller = new AbortController(); 108 - 109 - let request: RequestInit = { 110 - body: body ?? formData, 111 - headers, 112 - method: options.method, 113 - signal: controller.signal, 114 - }; 115 - 116 - if (config.WITH_CREDENTIALS) { 117 - request.credentials = config.CREDENTIALS; 118 - } 119 - 120 - for (const fn of config.interceptors.request._fns) { 121 - request = await fn(request); 122 - } 123 - 124 - onCancel(() => controller.abort()); 125 - 126 - return await fetch(url, request); 127 - }; 128 - 129 - const getResponseHeader = ( 130 - response: Response, 131 - responseHeader?: string, 132 - ): string | undefined => { 133 - if (responseHeader) { 134 - const content = response.headers.get(responseHeader); 135 - if (isString(content)) { 136 - return content; 137 - } 138 - } 139 - return undefined; 140 - }; 141 - 142 - const getResponseBody = async (response: Response): Promise<unknown> => { 143 - if (response.status !== 204) { 144 - try { 145 - const contentType = response.headers.get('Content-Type'); 146 - if (contentType) { 147 - const binaryTypes = [ 148 - 'application/octet-stream', 149 - 'application/pdf', 150 - 'application/zip', 151 - 'audio/', 152 - 'image/', 153 - 'video/', 154 - ]; 155 - if ( 156 - contentType.includes('application/json') || 157 - contentType.includes('+json') 158 - ) { 159 - return await response.json(); 160 - } else if (binaryTypes.some((type) => contentType.includes(type))) { 161 - return await response.blob(); 162 - } else if (contentType.includes('multipart/form-data')) { 163 - return await response.formData(); 164 - } else if (contentType.includes('text/')) { 165 - return await response.text(); 166 - } 167 - } 168 - } catch (error) { 169 - console.error(error); 170 - } 171 - } 172 - return undefined; 173 - }; 10 + // const getHeaders = async ( 11 + // config: OpenAPIConfig, 12 + // options: ApiRequestOptions, 13 + // ): Promise<Headers> => { 14 + // const [token, username, password, additionalHeaders] = await Promise.all([ 15 + // resolve(options, config.TOKEN), 16 + // resolve(options, config.USERNAME), 17 + // resolve(options, config.PASSWORD), 18 + // resolve(options, config.HEADERS), 19 + // ]); 174 20 175 - /** 176 - * Request method 177 - * @param config The OpenAPI configuration object 178 - * @param options The request options from the service 179 - * @returns CancelablePromise<T> 180 - * @throws ApiError 181 - */ 182 - export const request = <T>( 183 - config: OpenAPIConfig, 184 - options: ApiRequestOptions, 185 - ): CancelablePromise<T> => 186 - new CancelablePromise(async (resolve, reject, onCancel) => { 187 - try { 188 - const url = _getUrl(config, options); 189 - const formData = getFormData(options); 190 - const body = getRequestBody(options); 191 - const headers = await getHeaders(config, options); 21 + // const headers = Object.entries({ 22 + // Accept: 'application/json', 23 + // ...additionalHeaders, 24 + // ...options.headers, 25 + // }) 26 + // .filter(([, value]) => value !== undefined && value !== null) 27 + // .reduce( 28 + // (headers, [key, value]) => ({ 29 + // ...headers, 30 + // [key]: String(value), 31 + // }), 32 + // {} as Record<string, string>, 33 + // ); 192 34 193 - if (!onCancel.isCancelled) { 194 - let response = await sendRequest( 195 - config, 196 - options, 197 - url, 198 - body, 199 - formData, 200 - headers, 201 - onCancel, 202 - ); 35 + // if (isStringWithValue(token)) { 36 + // headers['Authorization'] = `Bearer ${token}`; 37 + // } 203 38 204 - for (const fn of config.interceptors.response._fns) { 205 - response = await fn(response); 206 - } 207 - 208 - const responseBody = await getResponseBody(response); 209 - const responseHeader = getResponseHeader( 210 - response, 211 - options.responseHeader, 212 - ); 39 + // if (isStringWithValue(username) && isStringWithValue(password)) { 40 + // const credentials = base64(`${username}:${password}`); 41 + // headers['Authorization'] = `Basic ${credentials}`; 42 + // } 213 43 214 - const result: ApiResult = { 215 - body: responseHeader ?? responseBody, 216 - ok: response.ok, 217 - status: response.status, 218 - statusText: response.statusText, 219 - url, 220 - }; 44 + // if (options.body !== undefined) { 45 + // if (options.mediaType) { 46 + // headers['Content-Type'] = options.mediaType; 47 + // } else if (isBlob(options.body)) { 48 + // headers['Content-Type'] = options.body.type || 'application/octet-stream'; 49 + // } else if (isString(options.body)) { 50 + // headers['Content-Type'] = 'text/plain'; 51 + // } else if (!isFormData(options.body)) { 52 + // headers['Content-Type'] = 'application/json'; 53 + // } 54 + // } 221 55 222 - catchErrorCodes(options, result); 56 + // return new Headers(headers); 57 + // }; 223 58 224 - resolve(result.body); 225 - } 226 - } catch (error) { 227 - reject(error); 228 - } 229 - }); 59 + // const getResponseBody = async (response: Response): Promise<unknown> => { 60 + // const contentType = response.headers.get('Content-Type'); 61 + // if (contentType) { 62 + // const binaryTypes = [ 63 + // 'application/octet-stream', 64 + // 'application/pdf', 65 + // 'application/zip', 66 + // 'audio/', 67 + // 'image/', 68 + // 'video/', 69 + // ]; 70 + // if ( 71 + // contentType.includes('application/json') || 72 + // contentType.includes('+json') 73 + // ) { 74 + // return await response.json(); 75 + // } else if (binaryTypes.some((type) => contentType.includes(type))) { 76 + // return await response.blob(); 77 + // } else if (contentType.includes('multipart/form-data')) { 78 + // return await response.formData(); 79 + // } else if (contentType.includes('text/')) { 80 + // return await response.text(); 81 + // } 82 + // } 83 + // }; 230 84 231 85 type Options = Omit<Req, 'method'>; 232 86 ··· 309 163 response = await fn(response, request, opts); 310 164 } 311 165 166 + const result = { 167 + request, 168 + response, 169 + }; 170 + 312 171 // return empty objects so truthy checks for data/error succeed 313 172 if ( 314 173 response.status === 204 || ··· 318 177 return { 319 178 // @ts-ignore 320 179 data: {}, 321 - response, 180 + ...result, 322 181 }; 323 182 } 324 183 return { 325 184 // @ts-ignore 326 185 error: {}, 327 - response, 186 + ...result, 328 187 }; 329 188 } 330 189 ··· 333 192 return { 334 193 // @ts-ignore 335 194 data: response.body, 336 - response, 195 + ...result, 337 196 }; 338 197 } 339 198 return { 340 199 data: await response[opts.parseAs](), 341 - response, 200 + ...result, 342 201 }; 343 202 } 344 203 ··· 351 210 return { 352 211 // @ts-ignore 353 212 error, 354 - response, 213 + ...result, 355 214 }; 356 - 357 - // TODO: add abort function 358 - // TODO: return original request as result.request 359 215 }; 360 216 361 217 type Interceptors = {
+2 -3
packages/client-fetch/src/node/index.ts
··· 1 - export { client, createClient, request } from '../'; 2 - export type { Config, Options } from '../types'; 3 - export type { CancelablePromise } from '@hey-api/client-core'; 1 + export { client, createClient } from '../'; 2 + export type { Options } from '../types';
+24 -2
packages/client-fetch/src/types.ts
··· 1 - import type { ApiRequestOptions } from '@hey-api/client-core'; 2 - 3 1 import type { 4 2 BodySerializer, 5 3 FetchOptions, ··· 7 5 QuerySerializerOptions, 8 6 } from './utils'; 9 7 8 + type ApiRequestOptions = { 9 + readonly body?: any; 10 + readonly cookies?: Record<string, unknown>; 11 + readonly errors?: Record<number, string>; 12 + readonly formData?: Record<string, unknown>; 13 + readonly headers?: Record<string, unknown>; 14 + readonly mediaType?: string; 15 + readonly method: 16 + | 'CONNECT' 17 + | 'DELETE' 18 + | 'GET' 19 + | 'HEAD' 20 + | 'OPTIONS' 21 + | 'PATCH' 22 + | 'POST' 23 + | 'PUT' 24 + | 'TRACE'; 25 + readonly path?: Record<string, unknown>; 26 + readonly query?: Record<string, unknown>; 27 + readonly responseHeader?: string; 28 + readonly url: string; 29 + }; 30 + 10 31 interface FetchConfig extends FetchOptions { 11 32 /** 12 33 * custom fetch ··· 26 47 interface RequestResponse<Data = unknown, Error = unknown> { 27 48 error?: Error; 28 49 data?: Data; 50 + request: Request; 29 51 response: Response; 30 52 } 31 53
+12 -1
packages/openapi-ts/src/openApi/common/interfaces/client.ts
··· 31 31 code: number | 'default'; 32 32 } 33 33 34 + export type Method = 35 + | 'CONNECT' 36 + | 'DELETE' 37 + | 'GET' 38 + | 'HEAD' 39 + | 'OPTIONS' 40 + | 'PATCH' 41 + | 'POST' 42 + | 'PUT' 43 + | 'TRACE'; 44 + 34 45 export interface Operation extends OperationParameters { 35 46 deprecated: boolean; 36 47 description: string | null; 37 48 errors: OperationResponse[]; 38 - method: 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT'; 49 + method: Method; 39 50 /** 40 51 * Method name. Methods contain the request logic. 41 52 */
+1
packages/openapi-ts/src/openApi/v3/interfaces/OpenApiPath.ts
··· 6 6 * {@link} https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#path-item-object 7 7 */ 8 8 export interface OpenApiPath { 9 + connect?: OpenApiOperation; 9 10 delete?: OpenApiOperation; 10 11 description?: string; 11 12 get?: OpenApiOperation;
+2
packages/openapi-ts/src/openApi/v3/parser/getServices.ts
··· 5 5 import { getOperation } from './operation'; 6 6 7 7 const allowedServiceMethods = [ 8 + 'connect', 8 9 'delete', 9 10 'get', 10 11 'head', ··· 12 13 'patch', 13 14 'post', 14 15 'put', 16 + 'trace', 15 17 ] as const; 16 18 17 19 const getNewService = (operation: Operation): Service => ({
+8 -6
packages/openapi-ts/src/utils/write/services.ts
··· 323 323 compiler.return.functionCall({ 324 324 args: [options], 325 325 name: `client.${operation.method.toLocaleLowerCase()}`, 326 - types: errorType && responseType 327 - ? [responseType, errorType] 328 - : errorType 329 - ? ['unknown', errorType] 330 - : responseType 331 - ? [responseType] : [], 326 + types: 327 + errorType && responseType 328 + ? [responseType, errorType] 329 + : errorType 330 + ? ['unknown', errorType] 331 + : responseType 332 + ? [responseType] 333 + : [], 332 334 }), 333 335 ]; 334 336 }
+89 -83
packages/openapi-ts/src/utils/write/types.ts
··· 4 4 type Node, 5 5 TypeScriptFile, 6 6 } from '../../compiler'; 7 - import type { Model, OperationParameter, Service } from '../../openApi'; 7 + import type { Model, OperationParameter } from '../../openApi'; 8 + import type { Method } from '../../openApi/common/interfaces/client'; 8 9 import type { Client } from '../../types/client'; 9 10 import { getConfig } from '../config'; 10 11 import { enumKey, enumUnionType, enumValue } from '../enum'; 11 12 import { escapeComment } from '../escape'; 12 13 import { sortByName, sorterByName } from '../sort'; 13 - import { operationDataTypeName, operationErrorTypeName, operationResponseTypeName } from './services'; 14 + import { 15 + operationDataTypeName, 16 + operationErrorTypeName, 17 + operationResponseTypeName, 18 + } from './services'; 14 19 import { toType, uniqueTypeName } from './type'; 15 20 16 21 type OnNode = (node: Node) => void; ··· 200 205 } 201 206 }; 202 207 203 - const processServiceTypes = (client: Client, onNode: OnNode) => { 204 - type ResMap = Map<number | 'default', Model>; 205 - type MethodMap = Map<'req' | 'res', ResMap | OperationParameter[]>; 206 - type MethodKey = Service['operations'][number]['method']; 207 - type PathMap = Map<MethodKey | '$ref', MethodMap | string>; 208 + interface MethodMap { 209 + $ref?: string; 210 + req?: OperationParameter[]; 211 + res?: Record<number | string, Model>; 212 + } 213 + 214 + type PathMap = { 215 + [method in Method]?: MethodMap; 216 + }; 208 217 209 - const pathsMap = new Map<string, PathMap>(); 218 + type PathsMap = Record<string, PathMap>; 219 + 220 + const processServiceTypes = (client: Client, onNode: OnNode) => { 221 + const pathsMap: PathsMap = {}; 210 222 211 223 const config = getConfig(); 212 224 ··· 217 229 const hasErr = operation.errors.length; 218 230 219 231 if (hasReq || hasRes || hasErr) { 220 - let pathMap = pathsMap.get(operation.path); 221 - if (!pathMap) { 222 - pathsMap.set(operation.path, new Map()); 223 - pathMap = pathsMap.get(operation.path)!; 232 + if (!pathsMap[operation.path]) { 233 + pathsMap[operation.path] = {}; 224 234 } 225 - pathMap.set('$ref', operation.name); 235 + const pathMap = pathsMap[operation.path]!; 226 236 227 - let methodMap = pathMap.get(operation.method); 228 - if (!methodMap) { 229 - pathMap.set(operation.method, new Map()); 230 - methodMap = pathMap.get(operation.method)!; 231 - } 232 - 233 - if (typeof methodMap === 'string') { 234 - return; 237 + if (!pathMap[operation.method]) { 238 + pathMap[operation.method] = {}; 235 239 } 240 + const methodMap = pathMap[operation.method]!; 241 + methodMap.$ref = operation.name; 236 242 237 243 if (hasReq) { 238 244 const bodyParameter = operation.parameters ··· 300 306 ) 301 307 : sortByName([...operation.parameters]); 302 308 303 - methodMap.set('req', operationProperties); 309 + methodMap.req = operationProperties; 304 310 305 311 // create type export for operation data 306 312 generateType({ ··· 322 328 } 323 329 324 330 if (hasRes) { 325 - let resMap = methodMap.get('res'); 326 - if (!resMap) { 327 - methodMap.set('res', new Map()); 328 - resMap = methodMap.get('res')!; 331 + if (!methodMap.res) { 332 + methodMap.res = {}; 329 333 } 330 334 331 - if (Array.isArray(resMap)) { 335 + if (Array.isArray(methodMap.res)) { 332 336 return; 333 337 } 334 338 335 339 operation.results.forEach((result) => { 336 - resMap.set(result.code, result); 340 + methodMap.res![result.code] = result; 337 341 }); 338 342 339 343 // create type export for operation response ··· 377 381 export: 'all-of', 378 382 isRequired: true, 379 383 // TODO: improve error type detection 380 - properties: operation.errors.filter((result) => result.code === 'default' || (result.code >= 400 && result.code < 600)), 384 + properties: operation.errors.filter( 385 + (result) => 386 + result.code === 'default' || 387 + (result.code >= 400 && result.code < 600), 388 + ), 381 389 }), 382 390 }); 383 391 } 384 392 } 385 393 386 394 if (hasErr) { 387 - let resMap = methodMap.get('res'); 388 - if (!resMap) { 389 - methodMap.set('res', new Map()); 390 - resMap = methodMap.get('res')!; 395 + if (!methodMap.res) { 396 + methodMap.res = {}; 391 397 } 392 398 393 - if (Array.isArray(resMap)) { 399 + if (Array.isArray(methodMap.res)) { 394 400 return; 395 401 } 396 402 397 403 operation.errors.forEach((error) => { 398 - resMap.set(error.code, error); 404 + methodMap.res![error.code] = error; 399 405 }); 400 406 } 401 407 } 402 408 }); 403 409 }); 404 410 405 - const properties = Array.from(pathsMap).map(([path, pathMap]) => { 406 - const pathParameters = Array.from(pathMap) 407 - .map(([method, methodMap]) => { 408 - if (method === '$ref' || typeof methodMap === 'string') { 409 - return; 411 + const properties = Object.entries(pathsMap).map(([path, pathMap]) => { 412 + const pathParameters = Object.entries(pathMap) 413 + .map(([_method, methodMap]) => { 414 + const method = _method as Method; 415 + 416 + let methodParameters: Model[] = []; 417 + 418 + if (methodMap.req) { 419 + const operationName = methodMap.$ref!; 420 + const { name: base } = uniqueTypeName({ 421 + client, 422 + meta: { 423 + // TODO: this should be exact ref to operation for consistency, 424 + // but name should work too as operation ID is unique 425 + $ref: operationName, 426 + name: operationName, 427 + }, 428 + nameTransformer: operationDataTypeName, 429 + }); 430 + const reqKey: Model = { 431 + ...emptyModel, 432 + base, 433 + export: 'reference', 434 + isRequired: true, 435 + name: 'req', 436 + properties: [], 437 + type: base, 438 + }; 439 + methodParameters = [...methodParameters, reqKey]; 410 440 } 411 441 412 - const methodParameters = Array.from(methodMap).map( 413 - ([name, baseOrResMap]) => { 414 - if (name === 'req') { 415 - const operationName = pathMap.get('$ref') as string; 416 - const { name: base } = uniqueTypeName({ 417 - client, 418 - meta: { 419 - // TODO: this should be exact ref to operation for consistency, 420 - // but name should work too as operation ID is unique 421 - $ref: operationName, 422 - name: operationName, 423 - }, 424 - nameTransformer: operationDataTypeName, 425 - }); 426 - const reqKey: Model = { 442 + if (methodMap.res) { 443 + const reqResParameters = Object.entries(methodMap.res).map( 444 + ([code, base]) => { 445 + // TODO: move query params into separate query key 446 + const value: Model = { 427 447 ...emptyModel, 428 - base, 429 - export: 'reference', 448 + ...base, 430 449 isRequired: true, 431 - name, 432 - properties: [], 433 - type: base, 450 + name: String(code), 434 451 }; 435 - return reqKey; 436 - } 437 - const reqResParameters = Array.isArray(baseOrResMap) 438 - ? baseOrResMap 439 - : Array.from(baseOrResMap).map(([code, base]) => { 440 - // TODO: move query params into separate query key 441 - const value: Model = { 442 - ...emptyModel, 443 - ...base, 444 - isRequired: true, 445 - name: String(code), 446 - }; 447 - return value; 448 - }); 452 + return value; 453 + }, 454 + ); 455 + 456 + const resKey: Model = { 457 + ...emptyModel, 458 + isRequired: true, 459 + name: 'res', 460 + properties: reqResParameters, 461 + }; 462 + methodParameters = [...methodParameters, resKey]; 463 + } 449 464 450 - const reqResKey: Model = { 451 - ...emptyModel, 452 - isRequired: true, 453 - name, 454 - properties: reqResParameters, 455 - }; 456 - return reqResKey; 457 - }, 458 - ); 459 465 const methodKey: Model = { 460 466 ...emptyModel, 461 467 isRequired: true,
+6 -6
packages/openapi-ts/test/__snapshots__/test/generated/v2/types.gen.ts.snap
··· 744 744 }; 745 745 '/api/v{api-version}/defaults': { 746 746 get: { 747 - req: CallToTestOrderOfParamsData; 747 + req: CallWithDefaultParametersData; 748 748 }; 749 749 post: { 750 - req: CallToTestOrderOfParamsData; 750 + req: CallWithDefaultOptionalParametersData; 751 751 }; 752 752 put: { 753 753 req: CallToTestOrderOfParamsData; ··· 825 825 */ 826 826 202: ModelThatExtendsExtends; 827 827 /** 828 - * Message for default response 829 - */ 830 - default: ModelWithString; 831 - /** 832 828 * Message for 500 error 833 829 */ 834 830 500: ModelWithStringError; ··· 840 836 * Message for 502 error 841 837 */ 842 838 502: ModelWithStringError; 839 + /** 840 + * Message for default response 841 + */ 842 + default: ModelWithString; 843 843 }; 844 844 }; 845 845 };
+12 -12
packages/openapi-ts/test/__snapshots__/test/generated/v3/types.gen.ts.snap
··· 1383 1383 }; 1384 1384 '/api/v{api-version}/parameters/': { 1385 1385 get: { 1386 - req: PostCallWithOptionalParamData; 1386 + req: GetCallWithOptionalParamData; 1387 1387 }; 1388 1388 post: { 1389 1389 req: PostCallWithOptionalParamData; ··· 1411 1411 }; 1412 1412 '/api/v{api-version}/defaults': { 1413 1413 get: { 1414 - req: CallToTestOrderOfParamsData; 1414 + req: CallWithDefaultParametersData; 1415 1415 }; 1416 1416 post: { 1417 - req: CallToTestOrderOfParamsData; 1417 + req: CallWithDefaultOptionalParametersData; 1418 1418 }; 1419 1419 put: { 1420 1420 req: CallToTestOrderOfParamsData; ··· 1461 1461 */ 1462 1462 201: ModelWithString; 1463 1463 /** 1464 - * Message for default response 1465 - */ 1466 - default: ModelWithBoolean; 1467 - /** 1468 1464 * Message for 500 error 1469 1465 */ 1470 1466 500: ModelWithStringError; ··· 1476 1472 * Message for 502 error 1477 1473 */ 1478 1474 502: ModelWithStringError; 1475 + /** 1476 + * Message for default response 1477 + */ 1478 + default: ModelWithBoolean; 1479 1479 }; 1480 1480 }; 1481 1481 put: { ··· 1497 1497 */ 1498 1498 202: ModelThatExtendsExtends; 1499 1499 /** 1500 - * Message for default response 1501 - */ 1502 - default: ModelWithString; 1503 - /** 1504 1500 * Message for 500 error 1505 1501 */ 1506 1502 500: ModelWithStringError; ··· 1512 1508 * Message for 502 error 1513 1509 */ 1514 1510 502: ModelWithStringError; 1511 + /** 1512 + * Message for default response 1513 + */ 1514 + default: ModelWithString; 1515 1515 }; 1516 1516 }; 1517 1517 }; ··· 1616 1616 }; 1617 1617 '/api/v{api-version}/multipart': { 1618 1618 post: { 1619 - req: ; 1619 + req: MultipartRequestData; 1620 1620 }; 1621 1621 get: { 1622 1622 res: {
+12 -12
packages/openapi-ts/test/__snapshots__/test/generated/v3_angular/types.gen.ts.snap
··· 1303 1303 }; 1304 1304 '/api/v{api-version}/parameters/': { 1305 1305 get: { 1306 - req: PostCallWithOptionalParamData; 1306 + req: GetCallWithOptionalParamData; 1307 1307 }; 1308 1308 post: { 1309 1309 req: PostCallWithOptionalParamData; ··· 1331 1331 }; 1332 1332 '/api/v{api-version}/defaults': { 1333 1333 get: { 1334 - req: CallToTestOrderOfParamsData; 1334 + req: CallWithDefaultParametersData; 1335 1335 }; 1336 1336 post: { 1337 - req: CallToTestOrderOfParamsData; 1337 + req: CallWithDefaultOptionalParametersData; 1338 1338 }; 1339 1339 put: { 1340 1340 req: CallToTestOrderOfParamsData; ··· 1381 1381 */ 1382 1382 201: ModelWithString; 1383 1383 /** 1384 - * Message for default response 1385 - */ 1386 - default: ModelWithBoolean; 1387 - /** 1388 1384 * Message for 500 error 1389 1385 */ 1390 1386 500: ModelWithStringError; ··· 1396 1392 * Message for 502 error 1397 1393 */ 1398 1394 502: ModelWithStringError; 1395 + /** 1396 + * Message for default response 1397 + */ 1398 + default: ModelWithBoolean; 1399 1399 }; 1400 1400 }; 1401 1401 put: { ··· 1417 1417 */ 1418 1418 202: ModelThatExtendsExtends; 1419 1419 /** 1420 - * Message for default response 1421 - */ 1422 - default: ModelWithString; 1423 - /** 1424 1420 * Message for 500 error 1425 1421 */ 1426 1422 500: ModelWithStringError; ··· 1432 1428 * Message for 502 error 1433 1429 */ 1434 1430 502: ModelWithStringError; 1431 + /** 1432 + * Message for default response 1433 + */ 1434 + default: ModelWithString; 1435 1435 }; 1436 1436 }; 1437 1437 }; ··· 1536 1536 }; 1537 1537 '/api/v{api-version}/multipart': { 1538 1538 post: { 1539 - req: ; 1539 + req: MultipartRequestData; 1540 1540 }; 1541 1541 get: { 1542 1542 res: {
+12 -12
packages/openapi-ts/test/__snapshots__/test/generated/v3_client/types.gen.ts.snap
··· 1303 1303 }; 1304 1304 '/api/v{api-version}/parameters/': { 1305 1305 get: { 1306 - req: PostCallWithOptionalParamData; 1306 + req: GetCallWithOptionalParamData; 1307 1307 }; 1308 1308 post: { 1309 1309 req: PostCallWithOptionalParamData; ··· 1331 1331 }; 1332 1332 '/api/v{api-version}/defaults': { 1333 1333 get: { 1334 - req: CallToTestOrderOfParamsData; 1334 + req: CallWithDefaultParametersData; 1335 1335 }; 1336 1336 post: { 1337 - req: CallToTestOrderOfParamsData; 1337 + req: CallWithDefaultOptionalParametersData; 1338 1338 }; 1339 1339 put: { 1340 1340 req: CallToTestOrderOfParamsData; ··· 1381 1381 */ 1382 1382 201: ModelWithString; 1383 1383 /** 1384 - * Message for default response 1385 - */ 1386 - default: ModelWithBoolean; 1387 - /** 1388 1384 * Message for 500 error 1389 1385 */ 1390 1386 500: ModelWithStringError; ··· 1396 1392 * Message for 502 error 1397 1393 */ 1398 1394 502: ModelWithStringError; 1395 + /** 1396 + * Message for default response 1397 + */ 1398 + default: ModelWithBoolean; 1399 1399 }; 1400 1400 }; 1401 1401 put: { ··· 1417 1417 */ 1418 1418 202: ModelThatExtendsExtends; 1419 1419 /** 1420 - * Message for default response 1421 - */ 1422 - default: ModelWithString; 1423 - /** 1424 1420 * Message for 500 error 1425 1421 */ 1426 1422 500: ModelWithStringError; ··· 1432 1428 * Message for 502 error 1433 1429 */ 1434 1430 502: ModelWithStringError; 1431 + /** 1432 + * Message for default response 1433 + */ 1434 + default: ModelWithString; 1435 1435 }; 1436 1436 }; 1437 1437 }; ··· 1536 1536 }; 1537 1537 '/api/v{api-version}/multipart': { 1538 1538 post: { 1539 - req: ; 1539 + req: MultipartRequestData; 1540 1540 }; 1541 1541 get: { 1542 1542 res: {
+12 -12
packages/openapi-ts/test/__snapshots__/test/generated/v3_enums_typescript/types.gen.ts.snap
··· 1358 1358 }; 1359 1359 '/api/v{api-version}/parameters/': { 1360 1360 get: { 1361 - req: PostCallWithOptionalParamData; 1361 + req: GetCallWithOptionalParamData; 1362 1362 }; 1363 1363 post: { 1364 1364 req: PostCallWithOptionalParamData; ··· 1386 1386 }; 1387 1387 '/api/v{api-version}/defaults': { 1388 1388 get: { 1389 - req: CallToTestOrderOfParamsData; 1389 + req: CallWithDefaultParametersData; 1390 1390 }; 1391 1391 post: { 1392 - req: CallToTestOrderOfParamsData; 1392 + req: CallWithDefaultOptionalParametersData; 1393 1393 }; 1394 1394 put: { 1395 1395 req: CallToTestOrderOfParamsData; ··· 1436 1436 */ 1437 1437 201: ModelWithString; 1438 1438 /** 1439 - * Message for default response 1440 - */ 1441 - default: ModelWithBoolean; 1442 - /** 1443 1439 * Message for 500 error 1444 1440 */ 1445 1441 500: ModelWithStringError; ··· 1451 1447 * Message for 502 error 1452 1448 */ 1453 1449 502: ModelWithStringError; 1450 + /** 1451 + * Message for default response 1452 + */ 1453 + default: ModelWithBoolean; 1454 1454 }; 1455 1455 }; 1456 1456 put: { ··· 1472 1472 */ 1473 1473 202: ModelThatExtendsExtends; 1474 1474 /** 1475 - * Message for default response 1476 - */ 1477 - default: ModelWithString; 1478 - /** 1479 1475 * Message for 500 error 1480 1476 */ 1481 1477 500: ModelWithStringError; ··· 1487 1483 * Message for 502 error 1488 1484 */ 1489 1485 502: ModelWithStringError; 1486 + /** 1487 + * Message for default response 1488 + */ 1489 + default: ModelWithString; 1490 1490 }; 1491 1491 }; 1492 1492 }; ··· 1591 1591 }; 1592 1592 '/api/v{api-version}/multipart': { 1593 1593 post: { 1594 - req: ; 1594 + req: MultipartRequestData; 1595 1595 }; 1596 1596 get: { 1597 1597 res: {
+2 -2
packages/openapi-ts/test/__snapshots__/test/generated/v3_legacy_positional_args/types.gen.ts.snap
··· 104 104 export type $OpenApiTs = { 105 105 '/api/v{api-version}/defaults': { 106 106 get: { 107 - req: CallToTestOrderOfParamsData; 107 + req: CallWithDefaultParametersData; 108 108 }; 109 109 post: { 110 - req: CallToTestOrderOfParamsData; 110 + req: CallWithDefaultOptionalParametersData; 111 111 }; 112 112 put: { 113 113 req: CallToTestOrderOfParamsData;
+2 -2
packages/openapi-ts/test/__snapshots__/test/generated/v3_options/types.gen.ts.snap
··· 104 104 export type $OpenApiTs = { 105 105 '/api/v{api-version}/defaults': { 106 106 get: { 107 - req: CallToTestOrderOfParamsData; 107 + req: CallWithDefaultParametersData; 108 108 }; 109 109 post: { 110 - req: CallToTestOrderOfParamsData; 110 + req: CallWithDefaultOptionalParametersData; 111 111 }; 112 112 put: { 113 113 req: CallToTestOrderOfParamsData;
+3 -42
pnpm-lock.yaml
··· 181 181 version: 5.4.5 182 182 vite: 183 183 specifier: 5.2.11 184 - version: 5.2.11 184 + version: 5.2.11(@types/node@20.12.8) 185 185 186 186 packages/client-axios: 187 187 dependencies: ··· 195 195 196 196 packages/client-core: {} 197 197 198 - packages/client-fetch: 199 - dependencies: 200 - '@hey-api/client-core': 201 - specifier: workspace:* 202 - version: link:../client-core 198 + packages/client-fetch: {} 203 199 204 200 packages/openapi-ts: 205 201 dependencies: ··· 4018 4014 '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.0) 4019 4015 '@types/babel__core': 7.20.5 4020 4016 react-refresh: 0.14.0 4021 - vite: 5.2.11 4017 + vite: 5.2.11(@types/node@20.12.8) 4022 4018 transitivePeerDependencies: 4023 4019 - supports-color 4024 4020 dev: true ··· 10670 10666 optional: true 10671 10667 dependencies: 10672 10668 '@types/node': 20.12.8 10673 - esbuild: 0.20.2 10674 - postcss: 8.4.38 10675 - rollup: 4.17.2 10676 - optionalDependencies: 10677 - fsevents: 2.3.3 10678 - dev: true 10679 - 10680 - /vite@5.2.11: 10681 - resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==} 10682 - engines: {node: ^18.0.0 || >=20.0.0} 10683 - hasBin: true 10684 - peerDependencies: 10685 - '@types/node': ^18.0.0 || >=20.0.0 10686 - less: '*' 10687 - lightningcss: ^1.21.0 10688 - sass: '*' 10689 - stylus: '*' 10690 - sugarss: '*' 10691 - terser: ^5.4.0 10692 - peerDependenciesMeta: 10693 - '@types/node': 10694 - optional: true 10695 - less: 10696 - optional: true 10697 - lightningcss: 10698 - optional: true 10699 - sass: 10700 - optional: true 10701 - stylus: 10702 - optional: true 10703 - sugarss: 10704 - optional: true 10705 - terser: 10706 - optional: true 10707 - dependencies: 10708 10669 esbuild: 0.20.2 10709 10670 postcss: 8.4.38 10710 10671 rollup: 4.17.2