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

chore: make createConfig, CreateClientConfig, and Config accept ClientOptions generic

Lubos bb6d46ae 5fa45725

+151 -92
+8
.changeset/unlucky-moles-dance.md
··· 1 + --- 2 + '@hey-api/client-axios': patch 3 + '@hey-api/client-fetch': patch 4 + '@hey-api/client-next': patch 5 + '@hey-api/client-nuxt': patch 6 + --- 7 + 8 + fix: make createConfig, CreateClientConfig, and Config accept ClientOptions generic
+1
packages/client-axios/src/client.ts
··· 59 59 const { auth, ...optsWithoutAuth } = opts; 60 60 const response = await _axios({ 61 61 ...optsWithoutAuth, 62 + baseURL: opts.baseURL as string, 62 63 data: opts.body, 63 64 headers: opts.headers as RawAxiosRequestHeaders, 64 65 // let `paramsSerializer()` handle query params if it exists
+1
packages/client-axios/src/index.ts
··· 1 1 export { createClient } from './client'; 2 2 export type { 3 3 Client, 4 + ClientOptions, 4 5 Config, 5 6 CreateClientConfig, 6 7 Options,
+18 -5
packages/client-axios/src/types.ts
··· 11 11 CreateAxiosDefaults, 12 12 } from 'axios'; 13 13 14 - export interface Config<ThrowOnError extends boolean = boolean> 15 - extends Omit<CreateAxiosDefaults, 'auth' | 'headers' | 'method'>, 14 + export interface Config<T extends ClientOptions = ClientOptions> 15 + extends Omit<CreateAxiosDefaults, 'auth' | 'baseURL' | 'headers' | 'method'>, 16 16 CoreConfig { 17 17 /** 18 18 * Axios implementation. You can use this option to provide a custom ··· 21 21 * @default axios 22 22 */ 23 23 axios?: AxiosStatic; 24 + /** 25 + * Base URL for all requests made by this client. 26 + */ 27 + baseURL?: T['baseURL']; 24 28 /** 25 29 * An object containing any HTTP headers that you want to pre-populate your 26 30 * `Headers` object with. ··· 44 48 * 45 49 * @default false 46 50 */ 47 - throwOnError?: ThrowOnError; 51 + throwOnError?: T['throwOnError']; 48 52 } 49 53 50 54 export interface RequestOptions< 51 55 ThrowOnError extends boolean = boolean, 52 56 Url extends string = string, 53 - > extends Config<ThrowOnError> { 57 + > extends Config<{ 58 + throwOnError: ThrowOnError; 59 + }> { 54 60 /** 55 61 * Any body that you want to add to your request. 56 62 * ··· 76 82 | (AxiosResponse<TData> & { error: undefined }) 77 83 | (AxiosError<TError> & { data: undefined; error: TError }) 78 84 >; 85 + 86 + export interface ClientOptions { 87 + baseURL?: string; 88 + throwOnError?: boolean; 89 + } 79 90 80 91 type MethodFn = < 81 92 TData = unknown, ··· 117 128 * `setConfig()`. This is useful for example if you're using Next.js 118 129 * to ensure your client always has the correct values. 119 130 */ 120 - export type CreateClientConfig = (override?: Config) => Config; 131 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 132 + override?: Config<ClientOptions & T>, 133 + ) => Config<Required<ClientOptions> & T>; 121 134 122 135 export interface TDataShape { 123 136 body?: unknown;
+4 -8
packages/client-axios/src/utils.ts
··· 10 10 serializePrimitiveParam, 11 11 } from '@hey-api/client-core'; 12 12 13 - import type { 14 - Client, 15 - Config, 16 - CreateClientConfig, 17 - RequestOptions, 18 - } from './types'; 13 + import type { Client, ClientOptions, Config, RequestOptions } from './types'; 19 14 20 15 interface PathSerializer { 21 16 path: Record<string, unknown>; ··· 285 280 return mergedHeaders; 286 281 }; 287 282 288 - export const createConfig: CreateClientConfig = (override = {}) => ({ 289 - baseURL: '', 283 + export const createConfig = <T extends ClientOptions = ClientOptions>( 284 + override: Config<ClientOptions & T> = {}, 285 + ): Config<Required<ClientOptions> & T> => ({ 290 286 ...override, 291 287 });
+1
packages/client-fetch/src/index.ts
··· 1 1 export { createClient } from './client'; 2 2 export type { 3 3 Client, 4 + ClientOptions, 4 5 Config, 5 6 CreateClientConfig, 6 7 Options,
+14 -7
packages/client-fetch/src/types.ts
··· 6 6 7 7 import type { Middleware } from './utils'; 8 8 9 - export interface Config<ThrowOnError extends boolean = boolean> 9 + export interface Config<T extends ClientOptions = ClientOptions> 10 10 extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 11 11 CoreConfig { 12 12 /** 13 13 * Base URL for all requests made by this client. 14 - * 15 - * @default '' 16 14 */ 17 - baseUrl?: string; 15 + baseUrl?: T['baseUrl']; 18 16 /** 19 17 * Fetch API implementation. You can use this option to provide a custom 20 18 * fetch instance. ··· 36 34 * 37 35 * @default false 38 36 */ 39 - throwOnError?: ThrowOnError; 37 + throwOnError?: T['throwOnError']; 40 38 } 41 39 42 40 export interface RequestOptions< 43 41 ThrowOnError extends boolean = boolean, 44 42 Url extends string = string, 45 - > extends Config<ThrowOnError> { 43 + > extends Config<{ 44 + throwOnError: ThrowOnError; 45 + }> { 46 46 /** 47 47 * Any body that you want to add to your request. 48 48 * ··· 83 83 } 84 84 >; 85 85 86 + export interface ClientOptions { 87 + baseUrl?: string; 88 + throwOnError?: boolean; 89 + } 90 + 86 91 type MethodFn = < 87 92 TData = unknown, 88 93 TError = unknown, ··· 123 128 * `setConfig()`. This is useful for example if you're using Next.js 124 129 * to ensure your client always has the correct values. 125 130 */ 126 - export type CreateClientConfig = (override?: Config) => Config; 131 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 132 + override?: Config<ClientOptions & T>, 133 + ) => Config<Required<ClientOptions> & T>; 127 134 128 135 export interface TDataShape { 129 136 body?: unknown;
+7 -11
packages/client-fetch/src/utils.ts
··· 10 10 serializePrimitiveParam, 11 11 } from '@hey-api/client-core'; 12 12 13 - import type { 14 - Client, 15 - Config, 16 - CreateClientConfig, 17 - RequestOptions, 18 - } from './types'; 13 + import type { Client, ClientOptions, Config, RequestOptions } from './types'; 19 14 20 15 interface PathSerializer { 21 16 path: Record<string, unknown>; ··· 234 229 235 230 export const buildUrl: Client['buildUrl'] = (options) => { 236 231 const url = getUrl({ 237 - baseUrl: options.baseUrl ?? '', 232 + baseUrl: options.baseUrl as string, 238 233 path: options.path, 239 234 query: options.query, 240 235 querySerializer: ··· 253 248 querySerializer, 254 249 url: _url, 255 250 }: { 256 - baseUrl: string; 251 + baseUrl?: string; 257 252 path?: Record<string, unknown>; 258 253 query?: Record<string, unknown>; 259 254 querySerializer: QuerySerializer; 260 255 url: string; 261 256 }) => { 262 257 const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 263 - let url = baseUrl + pathUrl; 258 + let url = (baseUrl ?? '') + pathUrl; 264 259 if (path) { 265 260 url = defaultPathSerializer({ path, url }); 266 261 } ··· 397 392 'Content-Type': 'application/json', 398 393 }; 399 394 400 - export const createConfig: CreateClientConfig = (override = {}) => ({ 395 + export const createConfig = <T extends ClientOptions = ClientOptions>( 396 + override: Config<ClientOptions & T> = {}, 397 + ): Config<Required<ClientOptions> & T> => ({ 401 398 ...jsonBodySerializer, 402 - baseUrl: '', 403 399 headers: defaultHeaders, 404 400 parseAs: 'auto', 405 401 querySerializer: defaultQuerySerializer,
+1
packages/client-next/src/index.ts
··· 1 1 export { createClient } from './client'; 2 2 export type { 3 3 Client, 4 + ClientOptions, 4 5 Config, 5 6 CreateClientConfig, 6 7 Options,
+14 -7
packages/client-next/src/types.ts
··· 6 6 7 7 import type { Middleware } from './utils'; 8 8 9 - export interface Config<ThrowOnError extends boolean = boolean> 9 + export interface Config<T extends ClientOptions = ClientOptions> 10 10 extends Omit<RequestInit, 'body' | 'headers' | 'method'>, 11 11 CoreConfig { 12 12 /** 13 13 * Base URL for all requests made by this client. 14 - * 15 - * @default '' 16 14 */ 17 - baseUrl?: string; 15 + baseUrl?: T['baseUrl']; 18 16 /** 19 17 * Fetch API implementation. You can use this option to provide a custom 20 18 * fetch instance. ··· 36 34 * 37 35 * @default false 38 36 */ 39 - throwOnError?: ThrowOnError; 37 + throwOnError?: T['throwOnError']; 40 38 } 41 39 42 40 export interface RequestOptions< 43 41 ThrowOnError extends boolean = boolean, 44 42 Url extends string = string, 45 - > extends Config<ThrowOnError> { 43 + > extends Config<{ 44 + throwOnError: ThrowOnError; 45 + }> { 46 46 /** 47 47 * Any body that you want to add to your request. 48 48 * ··· 81 81 } 82 82 >; 83 83 84 + export interface ClientOptions { 85 + baseUrl?: string; 86 + throwOnError?: boolean; 87 + } 88 + 84 89 type MethodFn = < 85 90 TData = unknown, 86 91 TError = unknown, ··· 121 126 * `setConfig()`. This is useful for example if you're using Next.js 122 127 * to ensure your client always has the correct values. 123 128 */ 124 - export type CreateClientConfig = (override?: Config) => Config; 129 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 130 + override?: Config<ClientOptions & T>, 131 + ) => Config<Required<ClientOptions> & T>; 125 132 126 133 export interface TDataShape { 127 134 body?: unknown;
+7 -11
packages/client-next/src/utils.ts
··· 10 10 serializePrimitiveParam, 11 11 } from '@hey-api/client-core'; 12 12 13 - import type { 14 - Client, 15 - Config, 16 - CreateClientConfig, 17 - RequestOptions, 18 - } from './types'; 13 + import type { Client, ClientOptions, Config, RequestOptions } from './types'; 19 14 20 15 interface PathSerializer { 21 16 path: Record<string, unknown>; ··· 234 229 235 230 export const buildUrl: Client['buildUrl'] = (options) => { 236 231 const url = getUrl({ 237 - baseUrl: options.baseUrl ?? '', 232 + baseUrl: options.baseUrl as string, 238 233 path: options.path, 239 234 query: options.query, 240 235 querySerializer: ··· 253 248 querySerializer, 254 249 url: _url, 255 250 }: { 256 - baseUrl: string; 251 + baseUrl?: string; 257 252 path?: Record<string, unknown>; 258 253 query?: Record<string, unknown>; 259 254 querySerializer: QuerySerializer; 260 255 url: string; 261 256 }) => { 262 257 const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 263 - let url = baseUrl + pathUrl; 258 + let url = (baseUrl ?? '') + pathUrl; 264 259 if (path) { 265 260 url = defaultPathSerializer({ path, url }); 266 261 } ··· 386 381 'Content-Type': 'application/json', 387 382 }; 388 383 389 - export const createConfig: CreateClientConfig = (override = {}) => ({ 384 + export const createConfig = <T extends ClientOptions = ClientOptions>( 385 + override: Config<ClientOptions & T> = {}, 386 + ): Config<Required<ClientOptions> & T> => ({ 390 387 ...jsonBodySerializer, 391 - baseUrl: '', 392 388 headers: defaultHeaders, 393 389 parseAs: 'auto', 394 390 querySerializer: defaultQuerySerializer,
+1
packages/client-nuxt/src/index.ts
··· 1 1 export { createClient } from './client'; 2 2 export type { 3 3 Client, 4 + ClientOptions, 4 5 Composable, 5 6 Config, 6 7 CreateClientConfig,
+9 -5
packages/client-nuxt/src/types.ts
··· 30 30 : NonNullable<TData[K]> | Ref<NonNullable<TData[K]>>; 31 31 }; 32 32 33 - export interface Config 33 + export interface Config<T extends ClientOptions = ClientOptions> 34 34 extends Omit< 35 35 FetchOptions<unknown>, 36 36 'baseURL' | 'body' | 'headers' | 'method' | 'query' ··· 39 39 Omit<CoreConfig, 'querySerializer'> { 40 40 /** 41 41 * Base URL for all requests made by this client. 42 - * 43 - * @default '' 44 42 */ 45 - baseURL?: string; 43 + baseURL?: T['baseURL']; 46 44 /** 47 45 * A function for serializing request query parameters. By default, arrays 48 46 * will be exploded in form style, objects will be exploded in deepObject ··· 93 91 ? ReturnType<typeof useLazyFetch<TData | null, TError>> 94 92 : never; 95 93 94 + export interface ClientOptions { 95 + baseURL?: string; 96 + } 97 + 96 98 type MethodFn = < 97 99 TComposable extends Composable, 98 100 TData = unknown, ··· 118 120 * `setConfig()`. This is useful for example if you're using Next.js 119 121 * to ensure your client always has the correct values. 120 122 */ 121 - export type CreateClientConfig = (override?: Config) => Config; 123 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 124 + override?: Config<ClientOptions & T>, 125 + ) => Config<Required<ClientOptions> & T>; 122 126 123 127 export interface TDataShape { 124 128 body?: unknown;
+7 -6
packages/client-nuxt/src/utils.ts
··· 13 13 ArraySeparatorStyle, 14 14 BuildUrlOptions, 15 15 Client, 16 + ClientOptions, 16 17 Config, 17 - CreateClientConfig, 18 18 QuerySerializer, 19 19 RequestOptions, 20 20 } from './types'; ··· 190 190 191 191 export const buildUrl: Client['buildUrl'] = (options) => { 192 192 const url = getUrl({ 193 - baseUrl: options.baseURL ?? '', 193 + baseUrl: options.baseURL as string, 194 194 path: options.path, 195 195 query: options.query, 196 196 querySerializer: ··· 209 209 querySerializer, 210 210 url: _url, 211 211 }: Pick<BuildUrlOptions, 'path' | 'query' | 'url'> & { 212 - baseUrl: string; 212 + baseUrl?: string; 213 213 querySerializer: QuerySerializer; 214 214 }) => { 215 215 const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 216 - let url = baseUrl + pathUrl; 216 + let url = (baseUrl ?? '') + pathUrl; 217 217 if (path) { 218 218 url = defaultPathSerializer({ path, url }); 219 219 } ··· 302 302 'Content-Type': 'application/json', 303 303 }; 304 304 305 - export const createConfig: CreateClientConfig = (override = {}) => ({ 305 + export const createConfig = <T extends ClientOptions = ClientOptions>( 306 + override: Config<ClientOptions & T> = {}, 307 + ): Config<Required<ClientOptions> & T> => ({ 306 308 ...jsonBodySerializer, 307 - baseURL: '', 308 309 headers: defaultHeaders, 309 310 querySerializer: defaultQuerySerializer, 310 311 ...override,
+36 -31
packages/openapi-ts/src/ir/types.d.ts
··· 1 1 import type { JsonSchemaDraft2020_12 } from '../openApi/3.1.x/types/json-schema-draft-2020-12'; 2 - import type { SecuritySchemeObject } from '../openApi/3.1.x/types/spec'; 2 + import type { 3 + SecuritySchemeObject, 4 + ServerObject, 5 + } from '../openApi/3.1.x/types/spec'; 3 6 import type { IRContext } from './context'; 4 7 import type { IRMediaType } from './mediaType'; 8 + 9 + interface IRBodyObject { 10 + mediaType: string; 11 + /** 12 + * Does body control pagination? We handle only simple values 13 + * for now, up to 1 nested field. 14 + */ 15 + pagination?: boolean | string; 16 + required?: boolean; 17 + schema: IRSchemaObject; 18 + type?: IRMediaType; 19 + } 5 20 6 21 interface IRComponentsObject { 7 22 parameters?: Record<string, IRParameterObject>; ··· 9 24 schemas?: Record<string, IRSchemaObject>; 10 25 } 11 26 12 - interface IRPathsObject { 13 - [path: `/${string}`]: IRPathItemObject; 14 - } 15 - 16 - interface IRPathItemObject { 17 - delete?: IROperationObject; 18 - get?: IROperationObject; 19 - head?: IROperationObject; 20 - options?: IROperationObject; 21 - patch?: IROperationObject; 22 - post?: IROperationObject; 23 - put?: IROperationObject; 24 - trace?: IROperationObject; 25 - } 26 - 27 27 interface IROperationObject { 28 28 body?: IRBodyObject; 29 29 deprecated?: boolean; ··· 34 34 path: keyof IRPathsObject; 35 35 responses?: IRResponsesObject; 36 36 security?: ReadonlyArray<IRSecurityObject>; 37 - // TODO: parser - add more properties 38 - // servers?: ReadonlyArray<ServerObject>; 37 + servers?: ReadonlyArray<IRServerObject>; 39 38 summary?: string; 40 39 tags?: ReadonlyArray<string>; 41 - } 42 - 43 - interface IRBodyObject { 44 - mediaType: string; 45 - /** 46 - * Does body control pagination? We handle only simple values 47 - * for now, up to 1 nested field. 48 - */ 49 - pagination?: boolean | string; 50 - required?: boolean; 51 - schema: IRSchemaObject; 52 - type?: IRMediaType; 53 40 } 54 41 55 42 interface IRParametersObject { ··· 92 79 | 'pipeDelimited' 93 80 | 'simple' 94 81 | 'spaceDelimited'; 82 + } 83 + 84 + interface IRPathsObject { 85 + [path: `/${string}`]: IRPathItemObject; 86 + } 87 + 88 + interface IRPathItemObject { 89 + delete?: IROperationObject; 90 + get?: IROperationObject; 91 + head?: IROperationObject; 92 + options?: IROperationObject; 93 + patch?: IROperationObject; 94 + post?: IROperationObject; 95 + put?: IROperationObject; 96 + trace?: IROperationObject; 95 97 } 96 98 97 99 interface IRRequestBodyObject ··· 188 190 189 191 type IRSecurityObject = SecuritySchemeObject; 190 192 193 + type IRServerObject = ServerObject; 194 + 191 195 interface IRModel { 192 196 components?: IRComponentsObject; 193 197 paths?: IRPathsObject; 194 - servers?: ReadonlyArray<unknown>; 198 + servers?: ReadonlyArray<IRServerObject>; 195 199 } 196 200 197 201 export namespace IR { ··· 209 213 export type ResponsesObject = IRResponsesObject; 210 214 export type SchemaObject = IRSchemaObject; 211 215 export type SecurityObject = IRSecurityObject; 216 + export type ServerObject = IRServerObject; 212 217 }
+1 -1
packages/openapi-ts/test/openapi-ts.config.ts
··· 6 6 // exclude: '^#/components/schemas/ModelWithCircularReference$', 7 7 // include: 8 8 // '^(#/components/schemas/import|#/paths/api/v{api-version}/simple/options)$', 9 - path: './packages/openapi-ts/test/spec/3.0.x/security-open-id-connect.yaml', 9 + path: './packages/openapi-ts/test/spec/3.1.x/servers.yaml', 10 10 // path: './test/spec/v3-transforms.json', 11 11 // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', 12 12 // path: 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml',
+21
packages/openapi-ts/test/spec/3.1.x/servers.yaml
··· 1 + openapi: 3.1.1 2 + info: 3 + title: OpenAPI 3.1.1 servers example 4 + version: 1 5 + servers: 6 + - url: https://development.gigantic-server.com/v1 7 + description: Development server 8 + - url: https://staging.gigantic-server.com/v1 9 + description: Staging server 10 + - url: https://api.gigantic-server.com/v1 11 + description: Production server 12 + paths: 13 + /foo: 14 + get: 15 + responses: 16 + '200': 17 + content: 18 + '*/*': 19 + schema: 20 + type: string 21 + description: OK