From 7023a42c463f2bf5bb085d0dff9319acf80fde56 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 16:54:53 +0700 Subject: [PATCH 01/26] feat(ofetch): init core client func & config --- .../src/plugins/@hey-api/client-ofetch/api.ts | 25 + .../@hey-api/client-ofetch/bundle/client.ts | 273 +++++++++ .../@hey-api/client-ofetch/bundle/index.ts | 23 + .../@hey-api/client-ofetch/bundle/types.ts | 298 ++++++++++ .../@hey-api/client-ofetch/bundle/utils.ts | 536 ++++++++++++++++++ .../plugins/@hey-api/client-ofetch/config.ts | 23 + .../plugins/@hey-api/client-ofetch/index.ts | 3 + .../plugins/@hey-api/client-ofetch/types.d.ts | 19 + 8 files changed, 1200 insertions(+) create mode 100644 packages/openapi-ts/src/plugins/@hey-api/client-ofetch/api.ts create mode 100644 packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/client.ts create mode 100644 packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/index.ts create mode 100644 packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts create mode 100644 packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts create mode 100644 packages/openapi-ts/src/plugins/@hey-api/client-ofetch/config.ts create mode 100644 packages/openapi-ts/src/plugins/@hey-api/client-ofetch/index.ts create mode 100644 packages/openapi-ts/src/plugins/@hey-api/client-ofetch/types.d.ts diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/api.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/api.ts new file mode 100644 index 000000000..a54a8ec1b --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/api.ts @@ -0,0 +1,25 @@ +import type { ICodegenSymbolSelector } from '@hey-api/codegen-core'; + +import type { Plugin } from '../../types'; + +type SelectorType = 'client'; + +export type IApi = { + /** + * @param type Selector type. + * @param value Depends on `type`: + * - `client`: never + * @returns Selector array + */ + getSelector: (type: SelectorType, value?: string) => ICodegenSymbolSelector; +}; + +export class Api implements IApi { + constructor(public meta: Plugin.Name<'@hey-api/client-ofetch'>) {} + + getSelector( + ...args: ReadonlyArray + ): ICodegenSymbolSelector { + return [this.meta.name, ...(args as ICodegenSymbolSelector)]; + } +} diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/client.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/client.ts new file mode 100644 index 000000000..29d6467d0 --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/client.ts @@ -0,0 +1,273 @@ +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../../client-core/bundle/serverSentEvents'; +import type { HttpMethod } from '../../client-core/bundle/types'; +import { getValidRequestBody } from '../../client-core/bundle/utils'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/index.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/index.ts new file mode 100644 index 000000000..a237c773b --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/index.ts @@ -0,0 +1,23 @@ +export type { Auth } from '../../client-core/bundle/auth'; +export type { QuerySerializerOptions } from '../../client-core/bundle/bodySerializer'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../../client-core/bundle/bodySerializer'; +export { buildClientParams } from '../../client-core/bundle/params'; +export { createClient } from './client'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types'; +export { createConfig, mergeHeaders } from './utils'; diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts new file mode 100644 index 000000000..b6e236789 --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts @@ -0,0 +1,298 @@ +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../../client-core/bundle/auth'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../../client-core/bundle/serverSentEvents'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../../client-core/bundle/types'; +import type { Middleware } from './utils'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts new file mode 100644 index 000000000..2e7db688d --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts @@ -0,0 +1,536 @@ +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../../client-core/bundle/auth'; +import type { QuerySerializerOptions } from '../../client-core/bundle/bodySerializer'; +import { jsonBodySerializer } from '../../client-core/bundle/bodySerializer'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../../client-core/bundle/pathSerializer'; +import { getUrl } from '../../client-core/bundle/utils'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => + ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + }) as OfetchOptions; + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/config.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/config.ts new file mode 100644 index 000000000..6cfaa83bf --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/config.ts @@ -0,0 +1,23 @@ +import { definePluginConfig } from '../../shared/utils/config'; +import { clientDefaultConfig, clientDefaultMeta } from '../client-core/config'; +import { clientPluginHandler } from '../client-core/plugin'; +import { Api } from './api'; +import type { HeyApiClientOfetchPlugin } from './types'; + +export const defaultConfig: HeyApiClientOfetchPlugin['Config'] = { + ...clientDefaultMeta, + api: new Api({ + name: '@hey-api/client-ofetch', + }), + config: { + ...clientDefaultConfig, + throwOnError: false, + }, + handler: clientPluginHandler, + name: '@hey-api/client-ofetch', +}; + +/** + * Type helper for `@hey-api/client-ofetch` plugin, returns {@link Plugin.Config} object + */ +export const defineConfig = definePluginConfig(defaultConfig); diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/index.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/index.ts new file mode 100644 index 000000000..bc402f684 --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/index.ts @@ -0,0 +1,3 @@ +export type { Client as OfetchClient } from './bundle/types'; +export { defaultConfig, defineConfig } from './config'; +export type { HeyApiClientOfetchPlugin } from './types'; diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/types.d.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/types.d.ts new file mode 100644 index 000000000..633236a4e --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/types.d.ts @@ -0,0 +1,19 @@ +import type { DefinePlugin, Plugin } from '../../types'; +import type { Client } from '../client-core/types'; +import type { IApi } from './api'; + +export type UserConfig = Plugin.Name<'@hey-api/client-ofetch'> & + Client.Config & { + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: boolean; + }; + +export type HeyApiClientOfetchPlugin = DefinePlugin< + UserConfig, + UserConfig, + IApi +>; From ea0088a519132c8bdd189a15daeb22149da4cff4 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 16:57:38 +0700 Subject: [PATCH 02/26] feat(core): init `client-ofetch` support in plugin system --- packages/openapi-ts/src/index.ts | 1 + .../openapi-ts/src/plugins/@hey-api/client-core/types.d.ts | 2 ++ packages/openapi-ts/src/plugins/config.ts | 4 ++++ packages/openapi-ts/src/plugins/types.d.ts | 1 + 4 files changed, 8 insertions(+) diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index 8e3a33617..fcf954b5d 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -131,6 +131,7 @@ export type { Client } from './plugins/@hey-api/client-core/types'; export type { FetchClient } from './plugins/@hey-api/client-fetch'; export type { NextClient } from './plugins/@hey-api/client-next'; export type { NuxtClient } from './plugins/@hey-api/client-nuxt'; +export type { OfetchClient } from './plugins/@hey-api/client-ofetch'; export type { ExpressionTransformer } from './plugins/@hey-api/transformers/expressions'; export type { TypeTransformer } from './plugins/@hey-api/transformers/types'; export { definePluginConfig } from './plugins/shared/utils/config'; diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-core/types.d.ts b/packages/openapi-ts/src/plugins/@hey-api/client-core/types.d.ts index 83c85223d..97fcc7371 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-core/types.d.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-core/types.d.ts @@ -4,11 +4,13 @@ import type { HeyApiClientAxiosPlugin } from '../client-axios'; import type { HeyApiClientFetchPlugin } from '../client-fetch'; import type { HeyApiClientNextPlugin } from '../client-next'; import type { HeyApiClientNuxtPlugin } from '../client-nuxt'; +import type { HeyApiClientOfetchPlugin } from '../client-ofetch'; export interface PluginHandler { (...args: Parameters): void; (...args: Parameters): void; (...args: Parameters): void; + (...args: Parameters): void; (...args: Parameters): void; (...args: Parameters): void; } diff --git a/packages/openapi-ts/src/plugins/config.ts b/packages/openapi-ts/src/plugins/config.ts index 8ae31f780..d8460ecec 100644 --- a/packages/openapi-ts/src/plugins/config.ts +++ b/packages/openapi-ts/src/plugins/config.ts @@ -10,6 +10,8 @@ import type { HeyApiClientNextPlugin } from './@hey-api/client-next'; import { defaultConfig as heyApiClientNext } from './@hey-api/client-next'; import type { HeyApiClientNuxtPlugin } from './@hey-api/client-nuxt'; import { defaultConfig as heyApiClientNuxt } from './@hey-api/client-nuxt'; +import type { HeyApiClientOfetchPlugin } from './@hey-api/client-ofetch'; +import { defaultConfig as heyApiClientOfetch } from './@hey-api/client-ofetch'; import type { HeyApiClientLegacyAngularPlugin } from './@hey-api/legacy-angular'; import { defaultConfig as heyApiLegacyAngular } from './@hey-api/legacy-angular'; import type { HeyApiClientLegacyAxiosPlugin } from './@hey-api/legacy-axios'; @@ -55,6 +57,7 @@ export interface PluginConfigMap { '@hey-api/client-fetch': HeyApiClientFetchPlugin['Types']; '@hey-api/client-next': HeyApiClientNextPlugin['Types']; '@hey-api/client-nuxt': HeyApiClientNuxtPlugin['Types']; + '@hey-api/client-ofetch': HeyApiClientOfetchPlugin['Types']; '@hey-api/schemas': HeyApiSchemasPlugin['Types']; '@hey-api/sdk': HeyApiSdkPlugin['Types']; '@hey-api/transformers': HeyApiTransformersPlugin['Types']; @@ -84,6 +87,7 @@ export const defaultPluginConfigs: { '@hey-api/client-fetch': heyApiClientFetch, '@hey-api/client-next': heyApiClientNext, '@hey-api/client-nuxt': heyApiClientNuxt, + '@hey-api/client-ofetch': heyApiClientOfetch, '@hey-api/schemas': heyApiSchemas, '@hey-api/sdk': heyApiSdk, '@hey-api/transformers': heyApiTransformers, diff --git a/packages/openapi-ts/src/plugins/types.d.ts b/packages/openapi-ts/src/plugins/types.d.ts index fc0c76cb7..79f92e220 100644 --- a/packages/openapi-ts/src/plugins/types.d.ts +++ b/packages/openapi-ts/src/plugins/types.d.ts @@ -12,6 +12,7 @@ export type PluginClientNames = | '@hey-api/client-fetch' | '@hey-api/client-next' | '@hey-api/client-nuxt' + | '@hey-api/client-ofetch' | 'legacy/angular' | 'legacy/axios' | 'legacy/fetch' From aebfbdde858504e049625753ed43f7fa058e04dc Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 16:58:56 +0700 Subject: [PATCH 03/26] chore: init `ofetch` dep --- packages/openapi-ts/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/openapi-ts/package.json b/packages/openapi-ts/package.json index 1121395c4..d2418c0ac 100644 --- a/packages/openapi-ts/package.json +++ b/packages/openapi-ts/package.json @@ -122,6 +122,7 @@ "glob": "10.4.3", "node-fetch": "3.3.2", "nuxt": "3.14.1592", + "ofetch": "^1.4.0", "prettier": "3.4.2", "rxjs": "7.8.1", "ts-node": "10.9.2", From 5305752ab241cb243a9409a3d9b33ac7596f562e Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 17:02:20 +0700 Subject: [PATCH 04/26] feat(build): add `client-ofetch` to `pluginNames` --- packages/openapi-ts/tsup.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/openapi-ts/tsup.config.ts b/packages/openapi-ts/tsup.config.ts index 88817b911..31c377e97 100644 --- a/packages/openapi-ts/tsup.config.ts +++ b/packages/openapi-ts/tsup.config.ts @@ -39,6 +39,7 @@ export default defineConfig((options) => ({ 'client-axios', 'client-core', 'client-fetch', + 'client-ofetch', 'client-next', 'client-nuxt', ]; From a1807fe4ba8256d435a4b5904935b7cc29734dad Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 17:04:19 +0700 Subject: [PATCH 05/26] feat: initialize `ofetch` example project --- examples/openapi-ts-ofetch/env.d.ts | 1 + examples/openapi-ts-ofetch/index.html | 12 + .../openapi-ts-ofetch/openapi-ts.config.ts | 20 + examples/openapi-ts-ofetch/package.json | 45 ++ examples/openapi-ts-ofetch/postcss.config.js | 6 + examples/openapi-ts-ofetch/src/App.vue | 190 +++++ .../openapi-ts-ofetch/src/assets/main.css | 7 + .../src/client/client.gen.ts | 28 + .../src/client/client/client.gen.ts | 236 ++++++ .../src/client/client/index.ts | 25 + .../src/client/client/types.gen.ts | 300 ++++++++ .../src/client/client/utils.gen.ts | 532 +++++++++++++ .../src/client/core/auth.gen.ts | 42 ++ .../src/client/core/bodySerializer.gen.ts | 92 +++ .../src/client/core/params.gen.ts | 153 ++++ .../src/client/core/pathSerializer.gen.ts | 181 +++++ .../src/client/core/serverSentEvents.gen.ts | 264 +++++++ .../src/client/core/types.gen.ts | 118 +++ .../src/client/core/utils.gen.ts | 143 ++++ .../openapi-ts-ofetch/src/client/index.ts | 4 + .../src/client/schemas.gen.ts | 188 +++++ .../openapi-ts-ofetch/src/client/sdk.gen.ts | 467 ++++++++++++ .../openapi-ts-ofetch/src/client/types.gen.ts | 699 ++++++++++++++++++ examples/openapi-ts-ofetch/src/main.ts | 18 + examples/openapi-ts-ofetch/tailwind.config.ts | 9 + examples/openapi-ts-ofetch/tsconfig.app.json | 14 + examples/openapi-ts-ofetch/tsconfig.json | 11 + examples/openapi-ts-ofetch/tsconfig.node.json | 19 + examples/openapi-ts-ofetch/vite.config.ts | 14 + 29 files changed, 3838 insertions(+) create mode 100644 examples/openapi-ts-ofetch/env.d.ts create mode 100644 examples/openapi-ts-ofetch/index.html create mode 100644 examples/openapi-ts-ofetch/openapi-ts.config.ts create mode 100644 examples/openapi-ts-ofetch/package.json create mode 100644 examples/openapi-ts-ofetch/postcss.config.js create mode 100644 examples/openapi-ts-ofetch/src/App.vue create mode 100644 examples/openapi-ts-ofetch/src/assets/main.css create mode 100644 examples/openapi-ts-ofetch/src/client/client.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/client/client.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/client/index.ts create mode 100644 examples/openapi-ts-ofetch/src/client/client/types.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/client/utils.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/core/auth.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/core/bodySerializer.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/core/params.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/core/pathSerializer.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/core/serverSentEvents.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/core/types.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/core/utils.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/index.ts create mode 100644 examples/openapi-ts-ofetch/src/client/schemas.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/sdk.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/client/types.gen.ts create mode 100644 examples/openapi-ts-ofetch/src/main.ts create mode 100644 examples/openapi-ts-ofetch/tailwind.config.ts create mode 100644 examples/openapi-ts-ofetch/tsconfig.app.json create mode 100644 examples/openapi-ts-ofetch/tsconfig.json create mode 100644 examples/openapi-ts-ofetch/tsconfig.node.json create mode 100644 examples/openapi-ts-ofetch/vite.config.ts diff --git a/examples/openapi-ts-ofetch/env.d.ts b/examples/openapi-ts-ofetch/env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/examples/openapi-ts-ofetch/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/openapi-ts-ofetch/index.html b/examples/openapi-ts-ofetch/index.html new file mode 100644 index 000000000..c90490343 --- /dev/null +++ b/examples/openapi-ts-ofetch/index.html @@ -0,0 +1,12 @@ + + + + + + Hey API — ofetch example + + +
+ + + diff --git a/examples/openapi-ts-ofetch/openapi-ts.config.ts b/examples/openapi-ts-ofetch/openapi-ts.config.ts new file mode 100644 index 000000000..e0d23642c --- /dev/null +++ b/examples/openapi-ts-ofetch/openapi-ts.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from '@hey-api/openapi-ts'; + +export default defineConfig({ + input: + 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', + output: { + format: 'prettier', + lint: 'eslint', + path: './src/client', + }, + plugins: [ + '@hey-api/client-ofetch', + '@hey-api/schemas', + '@hey-api/sdk', + { + enums: 'javascript', + name: '@hey-api/typescript', + }, + ], +}); diff --git a/examples/openapi-ts-ofetch/package.json b/examples/openapi-ts-ofetch/package.json new file mode 100644 index 000000000..6427931e6 --- /dev/null +++ b/examples/openapi-ts-ofetch/package.json @@ -0,0 +1,45 @@ +{ + "name": "@example/openapi-ts-ofetch", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx --fix --ignore-path .gitignore", + "openapi-ts": "openapi-ts", + "preview": "vite preview", + "typecheck": "vue-tsc --build --force" + }, + "dependencies": { + "ofetch": "1.4.1", + "vue": "3.5.21" + }, + "devDependencies": { + "@config/vite-base": "workspace:*", + "@hey-api/openapi-ts": "workspace:*", + "@rushstack/eslint-patch": "1.10.5", + "@tsconfig/node20": "20.1.4", + "@types/jsdom": "21.1.7", + "@types/node": "22.10.5", + "@vitejs/plugin-vue": "5.2.1", + "@vitejs/plugin-vue-jsx": "4.1.1", + "@vue/eslint-config-prettier": "10.1.0", + "@vue/eslint-config-typescript": "14.2.0", + "@vue/test-utils": "2.4.6", + "@vue/tsconfig": "0.7.0", + "autoprefixer": "10.4.20", + "eslint": "9.17.0", + "eslint-plugin-vue": "9.32.0", + "jsdom": "23.0.0", + "npm-run-all2": "6.2.0", + "postcss": "8.4.41", + "prettier": "3.4.2", + "tailwindcss": "3.4.9", + "typescript": "5.8.3", + "vite": "7.1.2", + "vite-plugin-vue-devtools": "7.7.0", + "vitest": "3.1.1", + "vue-tsc": "2.2.0" + } +} diff --git a/examples/openapi-ts-ofetch/postcss.config.js b/examples/openapi-ts-ofetch/postcss.config.js new file mode 100644 index 000000000..9eef821c4 --- /dev/null +++ b/examples/openapi-ts-ofetch/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + autoprefixer: {}, + tailwindcss: {}, + }, +}; diff --git a/examples/openapi-ts-ofetch/src/App.vue b/examples/openapi-ts-ofetch/src/App.vue new file mode 100644 index 000000000..d48b4c201 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/App.vue @@ -0,0 +1,190 @@ + + + diff --git a/examples/openapi-ts-ofetch/src/assets/main.css b/examples/openapi-ts-ofetch/src/assets/main.css new file mode 100644 index 000000000..0a2ac6aac --- /dev/null +++ b/examples/openapi-ts-ofetch/src/assets/main.css @@ -0,0 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + @apply bg-[#111113]; +} diff --git a/examples/openapi-ts-ofetch/src/client/client.gen.ts b/examples/openapi-ts-ofetch/src/client/client.gen.ts new file mode 100644 index 000000000..f1e680045 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/client.gen.ts @@ -0,0 +1,28 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { + type ClientOptions as DefaultClientOptions, + type Config, + createClient, + createConfig, +} from './client'; +import type { ClientOptions } from './types.gen'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = + ( + override?: Config, + ) => Config & T>; + +export const client = createClient( + createConfig({ + baseUrl: 'https://petstore3.swagger.io/api/v3', + }), +); diff --git a/examples/openapi-ts-ofetch/src/client/client/client.gen.ts b/examples/openapi-ts-ofetch/src/client/client/client.gen.ts new file mode 100644 index 000000000..d2482df58 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/client/client.gen.ts @@ -0,0 +1,236 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: any; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + const beforeRequest = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + const request: Client['request'] = async (options) => { + const { + networkBody: initialNetworkBody, + opts, + url, + } = await beforeRequest(options as any); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + let networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Attempt to reflect possible signal/body changes (safely) + + const reqBody = (request as any).body as unknown; + let effectiveRetry = opts.retry; + if (reqBody !== undefined && reqBody !== null) { + if (isRepeatableBody(reqBody)) { + networkBody = reqBody as BodyInit; + } else { + networkBody = reqBody as BodyInit; + // Disable retries for non-repeatable bodies + effectiveRetry = 0 as any; + } + } + + opts.signal = (request as any).signal as AbortSignal | undefined; + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildOfetchOptions( + opts as ResolvedRequestOptions, + networkBody ?? undefined, + effectiveRetry as any, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await beforeRequest(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/examples/openapi-ts-ofetch/src/client/client/index.ts b/examples/openapi-ts-ofetch/src/client/client/index.ts new file mode 100644 index 000000000..318a84b6a --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/examples/openapi-ts-ofetch/src/client/client/types.gen.ts b/examples/openapi-ts-ofetch/src/client/client/types.gen.ts new file mode 100644 index 000000000..e4925b81b --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/examples/openapi-ts-ofetch/src/client/client/utils.gen.ts b/examples/openapi-ts-ofetch/src/client/client/utils.gen.ts new file mode 100644 index 000000000..17c601b39 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/client/utils.gen.ts @@ -0,0 +1,532 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => { + const responseType = mapParseAsToResponseType( + opts.parseAs, + opts.responseType, + ); + return { + agent: opts.agent as OfetchOptions['agent'], + body: body as any, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + query: undefined, // URL already includes query + responseType, + retry: (retryOverride ?? + (opts.retry as OfetchOptions['retry'])) as OfetchOptions['retry'], + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions; +}; + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/examples/openapi-ts-ofetch/src/client/core/auth.gen.ts b/examples/openapi-ts-ofetch/src/client/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/examples/openapi-ts-ofetch/src/client/core/bodySerializer.gen.ts b/examples/openapi-ts-ofetch/src/client/core/bodySerializer.gen.ts new file mode 100644 index 000000000..49cd8925e --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/examples/openapi-ts-ofetch/src/client/core/params.gen.ts b/examples/openapi-ts-ofetch/src/client/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/examples/openapi-ts-ofetch/src/client/core/pathSerializer.gen.ts b/examples/openapi-ts-ofetch/src/client/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/examples/openapi-ts-ofetch/src/client/core/serverSentEvents.gen.ts b/examples/openapi-ts-ofetch/src/client/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..f8fd78e28 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/examples/openapi-ts-ofetch/src/client/core/types.gen.ts b/examples/openapi-ts-ofetch/src/client/core/types.gen.ts new file mode 100644 index 000000000..643c070c9 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/examples/openapi-ts-ofetch/src/client/core/utils.gen.ts b/examples/openapi-ts-ofetch/src/client/core/utils.gen.ts new file mode 100644 index 000000000..0b5389d08 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/examples/openapi-ts-ofetch/src/client/index.ts b/examples/openapi-ts-ofetch/src/client/index.ts new file mode 100644 index 000000000..f796d2cc8 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './sdk.gen'; +export * from './types.gen'; diff --git a/examples/openapi-ts-ofetch/src/client/schemas.gen.ts b/examples/openapi-ts-ofetch/src/client/schemas.gen.ts new file mode 100644 index 000000000..646632e83 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/schemas.gen.ts @@ -0,0 +1,188 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export const OrderSchema = { + properties: { + complete: { + type: 'boolean', + }, + id: { + example: 10, + format: 'int64', + type: 'integer', + }, + petId: { + example: 198772, + format: 'int64', + type: 'integer', + }, + quantity: { + example: 7, + format: 'int32', + type: 'integer', + }, + shipDate: { + format: 'date-time', + type: 'string', + }, + status: { + description: 'Order Status', + enum: ['placed', 'approved', 'delivered'], + example: 'approved', + type: 'string', + }, + }, + type: 'object', + 'x-swagger-router-model': 'io.swagger.petstore.model.Order', + xml: { + name: 'order', + }, +} as const; + +export const CategorySchema = { + properties: { + id: { + example: 1, + format: 'int64', + type: 'integer', + }, + name: { + example: 'Dogs', + type: 'string', + }, + }, + type: 'object', + 'x-swagger-router-model': 'io.swagger.petstore.model.Category', + xml: { + name: 'category', + }, +} as const; + +export const UserSchema = { + properties: { + email: { + example: 'john@email.com', + type: 'string', + }, + firstName: { + example: 'John', + type: 'string', + }, + id: { + example: 10, + format: 'int64', + type: 'integer', + }, + lastName: { + example: 'James', + type: 'string', + }, + password: { + example: '12345', + type: 'string', + }, + phone: { + example: '12345', + type: 'string', + }, + userStatus: { + description: 'User Status', + example: 1, + format: 'int32', + type: 'integer', + }, + username: { + example: 'theUser', + type: 'string', + }, + }, + type: 'object', + 'x-swagger-router-model': 'io.swagger.petstore.model.User', + xml: { + name: 'user', + }, +} as const; + +export const TagSchema = { + properties: { + id: { + format: 'int64', + type: 'integer', + }, + name: { + type: 'string', + }, + }, + type: 'object', + 'x-swagger-router-model': 'io.swagger.petstore.model.Tag', + xml: { + name: 'tag', + }, +} as const; + +export const PetSchema = { + properties: { + category: { + $ref: '#/components/schemas/Category', + }, + id: { + example: 10, + format: 'int64', + type: 'integer', + }, + name: { + example: 'doggie', + type: 'string', + }, + photoUrls: { + items: { + type: 'string', + xml: { + name: 'photoUrl', + }, + }, + type: 'array', + xml: { + wrapped: true, + }, + }, + status: { + description: 'pet status in the store', + enum: ['available', 'pending', 'sold'], + type: 'string', + }, + tags: { + items: { + $ref: '#/components/schemas/Tag', + }, + type: 'array', + xml: { + wrapped: true, + }, + }, + }, + required: ['name', 'photoUrls'], + type: 'object', + 'x-swagger-router-model': 'io.swagger.petstore.model.Pet', + xml: { + name: 'pet', + }, +} as const; + +export const ApiResponseSchema = { + properties: { + code: { + format: 'int32', + type: 'integer', + }, + message: { + type: 'string', + }, + type: { + type: 'string', + }, + }, + type: 'object', + xml: { + name: '##default', + }, +} as const; diff --git a/examples/openapi-ts-ofetch/src/client/sdk.gen.ts b/examples/openapi-ts-ofetch/src/client/sdk.gen.ts new file mode 100644 index 000000000..214fbf715 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/sdk.gen.ts @@ -0,0 +1,467 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Client, Options as ClientOptions, TDataShape } from './client'; +import { client } from './client.gen'; +import type { + AddPetData, + AddPetErrors, + AddPetResponses, + CreateUserData, + CreateUserErrors, + CreateUserResponses, + CreateUsersWithListInputData, + CreateUsersWithListInputErrors, + CreateUsersWithListInputResponses, + DeleteOrderData, + DeleteOrderErrors, + DeleteOrderResponses, + DeletePetData, + DeletePetErrors, + DeletePetResponses, + DeleteUserData, + DeleteUserErrors, + DeleteUserResponses, + FindPetsByStatusData, + FindPetsByStatusErrors, + FindPetsByStatusResponses, + FindPetsByTagsData, + FindPetsByTagsErrors, + FindPetsByTagsResponses, + GetInventoryData, + GetInventoryErrors, + GetInventoryResponses, + GetOrderByIdData, + GetOrderByIdErrors, + GetOrderByIdResponses, + GetPetByIdData, + GetPetByIdErrors, + GetPetByIdResponses, + GetUserByNameData, + GetUserByNameErrors, + GetUserByNameResponses, + LoginUserData, + LoginUserErrors, + LoginUserResponses, + LogoutUserData, + LogoutUserErrors, + LogoutUserResponses, + PlaceOrderData, + PlaceOrderErrors, + PlaceOrderResponses, + UpdatePetData, + UpdatePetErrors, + UpdatePetResponses, + UpdatePetWithFormData, + UpdatePetWithFormErrors, + UpdatePetWithFormResponses, + UpdateUserData, + UpdateUserErrors, + UpdateUserResponses, + UploadFileData, + UploadFileErrors, + UploadFileResponses, +} from './types.gen'; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, +> = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * Add a new pet to the store. + * Add a new pet to the store. + */ +export const addPet = ( + options: Options, +) => + (options.client ?? client).post({ + security: [ + { + scheme: 'bearer', + type: 'http', + }, + ], + url: '/pet', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + +/** + * Update an existing pet. + * Update an existing pet by Id. + */ +export const updatePet = ( + options: Options, +) => + (options.client ?? client).put< + UpdatePetResponses, + UpdatePetErrors, + ThrowOnError + >({ + security: [ + { + scheme: 'bearer', + type: 'http', + }, + ], + url: '/pet', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + +/** + * Finds Pets by status. + * Multiple status values can be provided with comma separated strings. + */ +export const findPetsByStatus = ( + options: Options, +) => + (options.client ?? client).get< + FindPetsByStatusResponses, + FindPetsByStatusErrors, + ThrowOnError + >({ + security: [ + { + scheme: 'bearer', + type: 'http', + }, + ], + url: '/pet/findByStatus', + ...options, + }); + +/** + * Finds Pets by tags. + * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + */ +export const findPetsByTags = ( + options: Options, +) => + (options.client ?? client).get< + FindPetsByTagsResponses, + FindPetsByTagsErrors, + ThrowOnError + >({ + security: [ + { + scheme: 'bearer', + type: 'http', + }, + ], + url: '/pet/findByTags', + ...options, + }); + +/** + * Deletes a pet. + * Delete a pet. + */ +export const deletePet = ( + options: Options, +) => + (options.client ?? client).delete< + DeletePetResponses, + DeletePetErrors, + ThrowOnError + >({ + security: [ + { + scheme: 'bearer', + type: 'http', + }, + ], + url: '/pet/{petId}', + ...options, + }); + +/** + * Find pet by ID. + * Returns a single pet. + */ +export const getPetById = ( + options: Options, +) => + (options.client ?? client).get< + GetPetByIdResponses, + GetPetByIdErrors, + ThrowOnError + >({ + security: [ + { + name: 'api_key', + type: 'apiKey', + }, + { + scheme: 'bearer', + type: 'http', + }, + ], + url: '/pet/{petId}', + ...options, + }); + +/** + * Updates a pet in the store with form data. + * Updates a pet resource based on the form data. + */ +export const updatePetWithForm = ( + options: Options, +) => + (options.client ?? client).post< + UpdatePetWithFormResponses, + UpdatePetWithFormErrors, + ThrowOnError + >({ + security: [ + { + scheme: 'bearer', + type: 'http', + }, + ], + url: '/pet/{petId}', + ...options, + }); + +/** + * Uploads an image. + * Upload image of the pet. + */ +export const uploadFile = ( + options: Options, +) => + (options.client ?? client).post< + UploadFileResponses, + UploadFileErrors, + ThrowOnError + >({ + bodySerializer: null, + security: [ + { + scheme: 'bearer', + type: 'http', + }, + ], + url: '/pet/{petId}/uploadImage', + ...options, + headers: { + 'Content-Type': 'application/octet-stream', + ...options.headers, + }, + }); + +/** + * Returns pet inventories by status. + * Returns a map of status codes to quantities. + */ +export const getInventory = ( + options?: Options, +) => + (options?.client ?? client).get< + GetInventoryResponses, + GetInventoryErrors, + ThrowOnError + >({ + security: [ + { + name: 'api_key', + type: 'apiKey', + }, + ], + url: '/store/inventory', + ...options, + }); + +/** + * Place an order for a pet. + * Place a new order in the store. + */ +export const placeOrder = ( + options?: Options, +) => + (options?.client ?? client).post< + PlaceOrderResponses, + PlaceOrderErrors, + ThrowOnError + >({ + url: '/store/order', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + }); + +/** + * Delete purchase order by identifier. + * For valid response try integer IDs with value < 1000. Anything above 1000 or non-integers will generate API errors. + */ +export const deleteOrder = ( + options: Options, +) => + (options.client ?? client).delete< + DeleteOrderResponses, + DeleteOrderErrors, + ThrowOnError + >({ + url: '/store/order/{orderId}', + ...options, + }); + +/** + * Find purchase order by ID. + * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. + */ +export const getOrderById = ( + options: Options, +) => + (options.client ?? client).get< + GetOrderByIdResponses, + GetOrderByIdErrors, + ThrowOnError + >({ + url: '/store/order/{orderId}', + ...options, + }); + +/** + * Create user. + * This can only be done by the logged in user. + */ +export const createUser = ( + options?: Options, +) => + (options?.client ?? client).post< + CreateUserResponses, + CreateUserErrors, + ThrowOnError + >({ + url: '/user', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + }); + +/** + * Creates list of users with given input array. + * Creates list of users with given input array. + */ +export const createUsersWithListInput = ( + options?: Options, +) => + (options?.client ?? client).post< + CreateUsersWithListInputResponses, + CreateUsersWithListInputErrors, + ThrowOnError + >({ + url: '/user/createWithList', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + }); + +/** + * Logs user into the system. + * Log into the system. + */ +export const loginUser = ( + options?: Options, +) => + (options?.client ?? client).get< + LoginUserResponses, + LoginUserErrors, + ThrowOnError + >({ + url: '/user/login', + ...options, + }); + +/** + * Logs out current logged in user session. + * Log user out of the system. + */ +export const logoutUser = ( + options?: Options, +) => + (options?.client ?? client).get< + LogoutUserResponses, + LogoutUserErrors, + ThrowOnError + >({ + url: '/user/logout', + ...options, + }); + +/** + * Delete user resource. + * This can only be done by the logged in user. + */ +export const deleteUser = ( + options: Options, +) => + (options.client ?? client).delete< + DeleteUserResponses, + DeleteUserErrors, + ThrowOnError + >({ + url: '/user/{username}', + ...options, + }); + +/** + * Get user by user name. + * Get user detail based on username. + */ +export const getUserByName = ( + options: Options, +) => + (options.client ?? client).get< + GetUserByNameResponses, + GetUserByNameErrors, + ThrowOnError + >({ + url: '/user/{username}', + ...options, + }); + +/** + * Update user resource. + * This can only be done by the logged in user. + */ +export const updateUser = ( + options: Options, +) => + (options.client ?? client).put< + UpdateUserResponses, + UpdateUserErrors, + ThrowOnError + >({ + url: '/user/{username}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + }); diff --git a/examples/openapi-ts-ofetch/src/client/types.gen.ts b/examples/openapi-ts-ofetch/src/client/types.gen.ts new file mode 100644 index 000000000..992c17fb2 --- /dev/null +++ b/examples/openapi-ts-ofetch/src/client/types.gen.ts @@ -0,0 +1,699 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Order = { + complete?: boolean; + id?: number; + petId?: number; + quantity?: number; + shipDate?: string; + /** + * Order Status + */ + status?: 'placed' | 'approved' | 'delivered'; +}; + +export type Category = { + id?: number; + name?: string; +}; + +export type User = { + email?: string; + firstName?: string; + id?: number; + lastName?: string; + password?: string; + phone?: string; + /** + * User Status + */ + userStatus?: number; + username?: string; +}; + +export type Tag = { + id?: number; + name?: string; +}; + +export type Pet = { + category?: Category; + id?: number; + name: string; + photoUrls: Array; + /** + * pet status in the store + */ + status?: 'available' | 'pending' | 'sold'; + tags?: Array; +}; + +export type ApiResponse = { + code?: number; + message?: string; + type?: string; +}; + +export type Pet2 = Pet; + +/** + * List of user object + */ +export type UserArray = Array; + +export type AddPetData = { + /** + * Create a new pet in the store + */ + body: Pet; + path?: never; + query?: never; + url: '/pet'; +}; + +export type AddPetErrors = { + /** + * Invalid input + */ + 400: unknown; + /** + * Validation exception + */ + 422: unknown; + /** + * Unexpected error + */ + default: unknown; +}; + +export type AddPetResponses = { + /** + * Successful operation + */ + 200: Pet; +}; + +export type AddPetResponse = AddPetResponses[keyof AddPetResponses]; + +export type UpdatePetData = { + /** + * Update an existent pet in the store + */ + body: Pet; + path?: never; + query?: never; + url: '/pet'; +}; + +export type UpdatePetErrors = { + /** + * Invalid ID supplied + */ + 400: unknown; + /** + * Pet not found + */ + 404: unknown; + /** + * Validation exception + */ + 422: unknown; + /** + * Unexpected error + */ + default: 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; + /** + * Unexpected error + */ + default: 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; + }; + url: '/pet/findByTags'; +}; + +export type FindPetsByTagsErrors = { + /** + * Invalid tag value + */ + 400: unknown; + /** + * Unexpected error + */ + default: 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; + /** + * Unexpected error + */ + default: unknown; +}; + +export type DeletePetResponses = { + /** + * Pet deleted + */ + 200: unknown; +}; + +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; + /** + * Unexpected error + */ + default: 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 + */ + 400: unknown; + /** + * Unexpected error + */ + default: unknown; +}; + +export type UpdatePetWithFormResponses = { + /** + * successful operation + */ + 200: Pet; +}; + +export type UpdatePetWithFormResponse = + UpdatePetWithFormResponses[keyof UpdatePetWithFormResponses]; + +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 UploadFileErrors = { + /** + * No file uploaded + */ + 400: unknown; + /** + * Pet not found + */ + 404: unknown; + /** + * Unexpected error + */ + default: unknown; +}; + +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 GetInventoryErrors = { + /** + * Unexpected error + */ + default: unknown; +}; + +export type GetInventoryResponses = { + /** + * successful operation + */ + 200: { + [key: string]: number; + }; +}; + +export type GetInventoryResponse = + GetInventoryResponses[keyof GetInventoryResponses]; + +export type PlaceOrderData = { + body?: Order; + path?: never; + query?: never; + url: '/store/order'; +}; + +export type PlaceOrderErrors = { + /** + * Invalid input + */ + 400: unknown; + /** + * Validation exception + */ + 422: unknown; + /** + * Unexpected error + */ + default: 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; + /** + * Unexpected error + */ + default: unknown; +}; + +export type DeleteOrderResponses = { + /** + * order deleted + */ + 200: 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; + /** + * Unexpected error + */ + default: 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 CreateUserErrors = { + /** + * Unexpected error + */ + default: unknown; +}; + +export type CreateUserResponses = { + /** + * successful operation + */ + 200: User; +}; + +export type CreateUserResponse = CreateUserResponses[keyof CreateUserResponses]; + +export type CreateUsersWithListInputData = { + body?: Array; + path?: never; + query?: never; + url: '/user/createWithList'; +}; + +export type CreateUsersWithListInputErrors = { + /** + * Unexpected error + */ + default: unknown; +}; + +export type CreateUsersWithListInputResponses = { + /** + * Successful operation + */ + 200: User; +}; + +export type CreateUsersWithListInputResponse = + CreateUsersWithListInputResponses[keyof CreateUsersWithListInputResponses]; + +export type LoginUserData = { + body?: never; + path?: never; + query?: { + /** + * The password for login in clear text + */ + password?: string; + /** + * The user name for login + */ + username?: string; + }; + url: '/user/login'; +}; + +export type LoginUserErrors = { + /** + * Invalid username/password supplied + */ + 400: unknown; + /** + * Unexpected error + */ + default: 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 LogoutUserErrors = { + /** + * Unexpected error + */ + default: unknown; +}; + +export type LogoutUserResponses = { + /** + * successful operation + */ + 200: 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; + /** + * Unexpected error + */ + default: unknown; +}; + +export type DeleteUserResponses = { + /** + * User deleted + */ + 200: 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; + /** + * Unexpected error + */ + default: 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 UpdateUserErrors = { + /** + * bad request + */ + 400: unknown; + /** + * user not found + */ + 404: unknown; + /** + * Unexpected error + */ + default: unknown; +}; + +export type UpdateUserResponses = { + /** + * successful operation + */ + 200: unknown; +}; + +export type ClientOptions = { + baseUrl: 'https://petstore3.swagger.io/api/v3' | (string & {}); +}; diff --git a/examples/openapi-ts-ofetch/src/main.ts b/examples/openapi-ts-ofetch/src/main.ts new file mode 100644 index 000000000..a4e1dc9de --- /dev/null +++ b/examples/openapi-ts-ofetch/src/main.ts @@ -0,0 +1,18 @@ +import './assets/main.css'; + +import { createApp } from 'vue'; + +import App from './App.vue'; +import { client } from './client/client.gen'; + +// configure internal service client +client.setConfig({ + // set default base url for requests + baseUrl: 'https://petstore3.swagger.io/api/v3', + // set default headers for requests + headers: { + Authorization: 'Bearer ', + }, +}); + +createApp(App).mount('#app'); diff --git a/examples/openapi-ts-ofetch/tailwind.config.ts b/examples/openapi-ts-ofetch/tailwind.config.ts new file mode 100644 index 000000000..4cbe78a03 --- /dev/null +++ b/examples/openapi-ts-ofetch/tailwind.config.ts @@ -0,0 +1,9 @@ +import type { Config } from 'tailwindcss'; + +export default { + content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], + plugins: [], + theme: { + extend: {}, + }, +} satisfies Config; diff --git a/examples/openapi-ts-ofetch/tsconfig.app.json b/examples/openapi-ts-ofetch/tsconfig.app.json new file mode 100644 index 000000000..81efb53e9 --- /dev/null +++ b/examples/openapi-ts-ofetch/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["./env.d.ts", "./src/**/*", "./src/**/*.vue"], + "exclude": ["./src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/examples/openapi-ts-ofetch/tsconfig.json b/examples/openapi-ts-ofetch/tsconfig.json new file mode 100644 index 000000000..66b5e5703 --- /dev/null +++ b/examples/openapi-ts-ofetch/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/examples/openapi-ts-ofetch/tsconfig.node.json b/examples/openapi-ts-ofetch/tsconfig.node.json new file mode 100644 index 000000000..f09406303 --- /dev/null +++ b/examples/openapi-ts-ofetch/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*" + ], + "compilerOptions": { + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/examples/openapi-ts-ofetch/vite.config.ts b/examples/openapi-ts-ofetch/vite.config.ts new file mode 100644 index 000000000..9f2a06a37 --- /dev/null +++ b/examples/openapi-ts-ofetch/vite.config.ts @@ -0,0 +1,14 @@ +import { fileURLToPath, URL } from 'node:url'; + +import { createViteConfig } from '@config/vite-base'; +import vue from '@vitejs/plugin-vue'; + +// https://vitejs.dev/config/ +export default createViteConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, +}); From 168e424d9d4cec1af9f97c1d85ce34fd4cfa0f6b Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 17:05:48 +0700 Subject: [PATCH 06/26] docs(ofetch): init --- docs/.vitepress/config/en.ts | 4 + docs/openapi-ts/clients.md | 1 + docs/openapi-ts/clients/ofetch.md | 341 ++++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 docs/openapi-ts/clients/ofetch.md diff --git a/docs/.vitepress/config/en.ts b/docs/.vitepress/config/en.ts index d3c9213ac..ebabcce14 100644 --- a/docs/.vitepress/config/en.ts +++ b/docs/.vitepress/config/en.ts @@ -103,6 +103,10 @@ export default defineConfig({ link: '/openapi-ts/clients/nuxt', text: 'Nuxt', }, + { + link: '/openapi-ts/clients/ofetch', + text: 'ofetch', + }, { link: '/openapi-ts/clients/effect', text: 'Effect soon', diff --git a/docs/openapi-ts/clients.md b/docs/openapi-ts/clients.md index 2d6944a71..eed7b5016 100644 --- a/docs/openapi-ts/clients.md +++ b/docs/openapi-ts/clients.md @@ -30,6 +30,7 @@ Hey API natively supports the following clients. - [Axios](/openapi-ts/clients/axios) - [Next.js](/openapi-ts/clients/next-js) - [Nuxt](/openapi-ts/clients/nuxt) +- [ofetch](/openapi-ts/clients/ofetch) - [Effect](/openapi-ts/clients/effect) Soon - [Legacy](/openapi-ts/clients/legacy) diff --git a/docs/openapi-ts/clients/ofetch.md b/docs/openapi-ts/clients/ofetch.md new file mode 100644 index 000000000..5a8c59549 --- /dev/null +++ b/docs/openapi-ts/clients/ofetch.md @@ -0,0 +1,341 @@ +--- +title: ofetch Client +description: Generate a type-safe ofetch client from OpenAPI with the ofetch client for openapi-ts. Fully compatible with validators, transformers, and all core features. +--- + + + +# ofetch + +### About + +[ofetch](https://github.com/unjs/ofetch) is a lightweight wrapper around the Fetch API that adds useful defaults and features such as automatic response parsing, request/response hooks, and Node.js support. + +The ofetch client for Hey API generates a type-safe client from your OpenAPI spec, fully compatible with validators, transformers, and all core features. + +## Features + +- seamless integration with `@hey-api/openapi-ts` ecosystem +- type-safe response data and errors +- response data validation and transformation +- access to the original request and response +- granular request and response customization options +- minimal learning curve thanks to extending the underlying technology +- support bundling inside the generated output + +## Installation + +In your [configuration](/openapi-ts/get-started), add `@hey-api/client-ofetch` to your plugins and you'll be ready to generate client artifacts. :tada: + +::: code-group + +```js [config] +export default { + input: 'hey-api/backend', // sign up at app.heyapi.dev + output: 'src/client', + plugins: ['@hey-api/client-ofetch'], // [!code ++] +}; +``` + +```sh [cli] +npx @hey-api/openapi-ts \ + -i hey-api/backend \ + -o src/client \ + -c @hey-api/client-ofetch # [!code ++] +``` + +::: + +## Configuration + +The ofetch client is built as a thin wrapper on top of ofetch, extending its functionality to work with Hey API. If you're already familiar with ofetch, configuring your client will feel like working directly with ofetch. + +When we installed the client above, it created a [`client.gen.ts`](/openapi-ts/output#client) file. You will most likely want to configure the exported `client` instance. There are two ways to do that. + +### `setConfig()` + +This is the simpler approach. You can call the `setConfig()` method at the beginning of your application or anytime you need to update the client configuration. You can pass any ofetch configuration option to `setConfig()` (including ofetch hooks), and even your own [ofetch](#custom-ofetch) instance. + +```js +import { client } from 'client/client.gen'; + +client.setConfig({ + baseUrl: 'https://example.com', +}); +``` + +The disadvantage of this approach is that your code may call the `client` instance before it's configured for the first time. Depending on your use case, you might need to use the second approach. + +### Runtime API + +Since `client.gen.ts` is a generated file, we can't directly modify it. Instead, we can tell our configuration to use a custom file implementing the Runtime API. We do that by specifying the `runtimeConfigPath` option. + +```js +export default { + input: 'hey-api/backend', // sign up at app.heyapi.dev + output: 'src/client', + plugins: [ + { + name: '@hey-api/client-ofetch', + runtimeConfigPath: './src/hey-api.ts', // [!code ++] + }, + ], +}; +``` + +In our custom file, we need to export a `createClientConfig()` method. This function is a simple wrapper allowing us to override configuration values. + +::: code-group + +```ts [hey-api.ts] +import type { CreateClientConfig } from './client/client.gen'; + +export const createClientConfig: CreateClientConfig = (config) => ({ + ...config, + baseUrl: 'https://example.com', +}); +``` + +::: + +With this approach, `client.gen.ts` will call `createClientConfig()` before initializing the `client` instance. If needed, you can still use `setConfig()` to update the client configuration later. + +### `createClient()` + +You can also create your own client instance. You can use it to manually send requests or point it to a different domain. + +```js +import { createClient } from './client/client'; + +const myClient = createClient({ + baseUrl: 'https://example.com', +}); +``` + +You can also pass this instance to any SDK function through the `client` option. This will override the default instance from `client.gen.ts`. + +```js +const response = await getFoo({ + client: myClient, +}); +``` + +### SDKs + +Alternatively, you can pass the client configuration options to each SDK function. This is useful if you don't want to create a client instance for one-off use cases. + +```js +const response = await getFoo({ + baseUrl: 'https://example.com', // <-- override default configuration +}); +``` + +## Interceptors + +Interceptors (middleware) can be used to modify requests before they're sent or responses before they're returned to your application. + +The ofetch client supports two complementary options: + +- Built-in interceptors exposed via `client.interceptors` (request/response/error) — same API across Hey API clients. +- Native ofetch hooks passed through config: `onRequest`, `onRequestError`, `onResponse`, `onResponseError`. + +Below is an example request interceptor using the built-in API. + +::: code-group + +```js [use] +import { client } from 'client/client.gen'; +// Supports async functions +async function myInterceptor(request) { + // do something + return request; +} +interceptorId = client.interceptors.request.use(myInterceptor); +``` + +```js [eject] +import { client } from 'client/client.gen'; + +// eject interceptor by interceptor id +client.interceptors.request.eject(interceptorId); + +// eject interceptor by reference to interceptor function +client.interceptors.request.eject(myInterceptor); +``` + +```js [update] +import { client } from 'client/client.gen'; + +async function myNewInterceptor(request) { + // do something + return request; +} +// update interceptor by interceptor id +client.interceptors.request.update(interceptorId, myNewInterceptor); + +// update interceptor by reference to interceptor function +client.interceptors.request.update(myInterceptor, myNewInterceptor); +``` + +::: + +and an example response interceptor + +::: code-group + +```js [use] +import { client } from 'client/client.gen'; +async function myInterceptor(response) { + // do something + return response; +} +// Supports async functions +interceptorId = client.interceptors.response.use(myInterceptor); +``` + +```js [eject] +import { client } from 'client/client.gen'; + +// eject interceptor by interceptor id +client.interceptors.response.eject(interceptorId); + +// eject interceptor by reference to interceptor function +client.interceptors.response.eject(myInterceptor); +``` + +```js [update] +import { client } from 'client/client.gen'; + +async function myNewInterceptor(response) { + // do something + return response; +} +// update interceptor by interceptor id +client.interceptors.response.update(interceptorId, myNewInterceptor); + +// update interceptor by reference to interceptor function +client.interceptors.response.update(myInterceptor, myNewInterceptor); +``` + +::: + +You can also use native ofetch hooks by passing them in config: + +```js +import { client } from 'client/client.gen'; + +client.setConfig({ + onRequest: ({ options }) => { + // mutate ofetch options (headers, query, etc.) + }, + onResponse: ({ response }) => { + // inspect/transform the raw Response + }, + onRequestError: (ctx) => { + // handle request errors + }, + onResponseError: (ctx) => { + // handle response errors + }, +}); +``` + +::: tip +To eject, you must provide the id or reference of the interceptor passed to `use()`, the id is the value returned by `use()` and `update()`. +::: + +## Auth + +The SDKs include auth mechanisms for every endpoint. You will want to configure the `auth` field to pass the right token for each request. The `auth` field can be a string or a function returning a string representing the token. The returned value will be attached only to requests that require auth. + +```js +import { client } from 'client/client.gen'; + +client.setConfig({ + auth: () => '', // [!code ++] + baseUrl: 'https://example.com', +}); +``` + +If you're not using SDKs or generating auth, using interceptors is a common approach to configuring auth for each request. + +```js +import { client } from 'client/client.gen'; + +client.interceptors.request.use((request, options) => { + request.headers.set('Authorization', 'Bearer '); // [!code ++] + return request; +}); +``` + +or with ofetch hooks: + +```js +import { client } from 'client/client.gen'; + +client.setConfig({ + onRequest: ({ options }) => { + options.headers.set('Authorization', 'Bearer '); // [!code ++] + }, +}); +``` + +## Build URL + +If you need to access the compiled URL, you can use the `buildUrl()` method. It's loosely typed by default to accept almost any value; in practice, you will want to pass a type hint. + +```ts +type FooData = { + path: { + fooId: number; + }; + query?: { + bar?: string; + }; + url: '/foo/{fooId}'; +}; + +const url = client.buildUrl({ + path: { + fooId: 1, + }, + query: { + bar: 'baz', + }, + url: '/foo/{fooId}', +}); +console.log(url); // prints '/foo/1?bar=baz' +``` + +## Custom `ofetch` + +You can provide a custom ofetch instance. This is useful if you need to extend ofetch with extra functionality (hooks, retry behavior, etc.) or replace it altogether. + +```js +import { ofetch } from 'ofetch'; +import { client } from 'client/client.gen'; + +const $ofetch = ofetch.create({ + onRequest: ({ options }) => { + // customize request + }, + onResponse: ({ response }) => { + // customize response + }, +}); + +client.setConfig({ + ofetch: $ofetch, +}); +``` + +You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom instance to be. + +## API + +You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/types.d.ts) interface. + + + From 9c5ef555c250ce5022be0fcbd51008ecaad6d4b2 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 17:07:26 +0700 Subject: [PATCH 07/26] test(client-ofetch): add unit runtime tests --- .../client-ofetch/__tests__/client.test.ts | 408 ++++++++++++++++++ .../client-ofetch/__tests__/utils.test.ts | 202 +++++++++ 2 files changed, 610 insertions(+) create mode 100644 packages/openapi-ts/src/plugins/@hey-api/client-ofetch/__tests__/client.test.ts create mode 100644 packages/openapi-ts/src/plugins/@hey-api/client-ofetch/__tests__/utils.test.ts diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/__tests__/client.test.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/__tests__/client.test.ts new file mode 100644 index 000000000..c31912576 --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/__tests__/client.test.ts @@ -0,0 +1,408 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { createClient } from '../bundle/client'; +import type { ResolvedRequestOptions } from '../bundle/types'; + +type MockOfetch = ((...args: any[]) => any) & { + raw?: any; +}; + +const makeMockOfetch = (response: Response): MockOfetch => { + const fn: any = vi.fn(); + fn.raw = vi.fn().mockResolvedValue(response); + return fn as MockOfetch; +}; + +describe('buildUrl', () => { + const client = createClient(); + + const scenarios: { + options: Parameters[0]; + url: string; + }[] = [ + { + options: { + url: '', + }, + url: '/', + }, + { + options: { + url: '/foo', + }, + url: '/foo', + }, + { + options: { + path: { + fooId: 1, + }, + url: '/foo/{fooId}', + }, + url: '/foo/1', + }, + { + options: { + path: { + fooId: 1, + }, + query: { + bar: 'baz', + }, + url: '/foo/{fooId}', + }, + url: '/foo/1?bar=baz', + }, + { + options: { + query: { + bar: [], + foo: [], + }, + url: '/', + }, + url: '/', + }, + { + options: { + query: { + bar: [], + foo: ['abc', 'def'], + }, + url: '/', + }, + url: '/?foo=abc&foo=def', + }, + ]; + + it.each(scenarios)('returns $url', ({ options, url }) => { + expect(client.buildUrl(options)).toBe(url); + }); +}); + +describe('zero-length body handling', () => { + const client = createClient({ baseUrl: 'https://example.com' }); + + it('returns empty Blob for zero-length application/octet-stream response', async () => { + const mockResponse = new Response(null, { + headers: { + 'Content-Length': '0', + 'Content-Type': 'application/octet-stream', + }, + status: 200, + }); + + const mockOfetch = makeMockOfetch(mockResponse); + + const result = await client.request({ + method: 'GET', + ofetch: mockOfetch as any, + url: '/test', + }); + + expect(result.data).toBeInstanceOf(Blob); + expect((result.data as Blob).size).toBe(0); + }); + + it('returns empty ArrayBuffer for zero-length response with arrayBuffer parseAs', async () => { + const mockResponse = new Response(null, { + headers: { + 'Content-Length': '0', + }, + status: 200, + }); + + const mockOfetch = makeMockOfetch(mockResponse); + + const result = await client.request({ + method: 'GET', + ofetch: mockOfetch as any, + parseAs: 'arrayBuffer', + url: '/test', + }); + + expect(result.data).toBeInstanceOf(ArrayBuffer); + expect((result.data as ArrayBuffer).byteLength).toBe(0); + }); + + it('returns empty string for zero-length text response', async () => { + const mockResponse = new Response(null, { + headers: { + 'Content-Length': '0', + 'Content-Type': 'text/plain', + }, + status: 200, + }); + + const mockOfetch = makeMockOfetch(mockResponse); + + const result = await client.request({ + method: 'GET', + ofetch: mockOfetch as any, + url: '/test', + }); + + expect(result.data).toBe(''); + }); + + it('returns empty object for zero-length JSON response', async () => { + const mockResponse = new Response(null, { + headers: { + 'Content-Length': '0', + 'Content-Type': 'application/json', + }, + status: 200, + }); + + const mockOfetch = makeMockOfetch(mockResponse); + + const result = await client.request({ + method: 'GET', + ofetch: mockOfetch as any, + url: '/test', + }); + + expect(result.data).toEqual({}); + }); + + it('returns empty FormData for zero-length multipart/form-data response', async () => { + const mockResponse = new Response(null, { + headers: { + 'Content-Length': '0', + 'Content-Type': 'multipart/form-data', + }, + status: 200, + }); + + const mockOfetch = makeMockOfetch(mockResponse); + + const result = await client.request({ + method: 'GET', + ofetch: mockOfetch as any, + url: '/test', + }); + + expect(result.data).toBeInstanceOf(FormData); + expect([...(result.data as FormData).entries()]).toHaveLength(0); + }); + + it('returns stream body for zero-length stream response', async () => { + const mockBody = new ReadableStream(); + const mockResponse = new Response(mockBody, { + headers: { + 'Content-Length': '0', + }, + status: 200, + }); + + const mockOfetch = makeMockOfetch(mockResponse); + + const result = await client.request({ + method: 'GET', + ofetch: mockOfetch as any, + parseAs: 'stream', + url: '/test', + }); + + expect(result.data).toBe(mockBody); + }); + + it('handles non-zero content correctly for comparison', async () => { + const blobContent = new Blob(['test data']); + const mockResponse = new Response(blobContent, { + headers: { + 'Content-Type': 'application/octet-stream', + }, + status: 200, + }); + + const mockOfetch = makeMockOfetch(mockResponse); + + const result = await client.request({ + method: 'GET', + ofetch: mockOfetch as any, + url: '/test', + }); + + expect(result.data).toBeInstanceOf(Blob); + expect((result.data as Blob).size).toBeGreaterThan(0); + }); +}); + +describe('unserialized request body handling', () => { + const client = createClient({ baseUrl: 'https://example.com' }); + + const scenarios = [ + { body: 0, textValue: '0' }, + { body: false, textValue: 'false' }, + { body: 'test string', textValue: 'test string' }, + { body: '', textValue: '' }, + ]; + + it.each(scenarios)( + 'handles plain text body with $body value', + async ({ body, textValue }) => { + const mockResponse = new Response(JSON.stringify({ success: true }), { + headers: { + 'Content-Type': 'application/json', + }, + status: 200, + }); + + const mockOfetch = makeMockOfetch(mockResponse); + + const result = await client.post({ + body, + bodySerializer: null, + headers: { + 'Content-Type': 'text/plain', + }, + ofetch: mockOfetch as any, + url: '/test', + }); + + await expect(result.request.text()).resolves.toEqual(textValue); + expect(result.request.headers.get('Content-Type')).toEqual('text/plain'); + }, + ); +}); + +describe('serialized request body handling', () => { + const client = createClient({ baseUrl: 'https://example.com' }); + + const scenarios = [ + { + body: '', + expectBodyValue: null, + expectContentHeader: false, + serializedBody: '', + textValue: '', + }, + { + body: 0, + expectBodyValue: 0, + expectContentHeader: true, + serializedBody: 0, + textValue: '0', + }, + { + body: false, + expectBodyValue: false, + expectContentHeader: true, + serializedBody: false, + textValue: 'false', + }, + { + body: {}, + expectBodyValue: '{"key":"value"}', + expectContentHeader: true, + serializedBody: '{"key":"value"}', + textValue: '{"key":"value"}', + }, + ]; + + it.each(scenarios)( + 'handles $serializedBody serializedBody value', + async ({ + body, + expectBodyValue, + expectContentHeader, + serializedBody, + textValue, + }) => { + const mockResponse = new Response(JSON.stringify({ success: true }), { + headers: { + 'Content-Type': 'application/json', + }, + status: 200, + }); + + const mockOfetch = makeMockOfetch(mockResponse); + + const result = await client.post({ + body, + bodySerializer: () => serializedBody, + headers: { + 'Content-Type': 'application/json', + }, + ofetch: mockOfetch as any, + url: '/test', + }); + + // Ensure request captures serialized text value + await expect(result.request.text()).resolves.toEqual(textValue); + expect(result.request.headers.get('Content-Type')).toEqual( + expectContentHeader ? 'application/json' : null, + ); + + // Ensure ofetch.raw received the expected body + const call = (mockOfetch.raw as any).mock.calls[0]; + const opts = call[1]; + expect(opts.body).toEqual(expectBodyValue); + }, + ); +}); + +describe('request interceptor', () => { + const client = createClient({ baseUrl: 'https://example.com' }); + + const scenarios = [ + { + body: 'test string', + bodySerializer: null, + contentType: 'text/plain', + expectedSerializedValue: undefined, + }, + { + body: { key: 'value' }, + bodySerializer: (body: object) => JSON.stringify(body), + contentType: 'application/json', + expectedSerializedValue: '{"key":"value"}', + }, + ]; + + it.each(scenarios)( + 'exposes $contentType serialized and raw body values', + async ({ body, bodySerializer, contentType, expectedSerializedValue }) => { + const mockResponse = new Response(JSON.stringify({ success: true }), { + headers: { + 'Content-Type': 'application/json', + }, + status: 200, + }); + + const mockOfetch = makeMockOfetch(mockResponse); + + const mockRequestInterceptor = vi + .fn() + .mockImplementation( + (request: Request, options: ResolvedRequestOptions) => { + expect(options.serializedBody).toBe(expectedSerializedValue); + expect(options.body).toBe(body); + return request; + }, + ); + + const interceptorId = client.interceptors.request.use( + mockRequestInterceptor, + ); + + await client.post({ + body, + bodySerializer, + headers: { + 'Content-Type': contentType, + }, + ofetch: mockOfetch as any, + url: '/test', + }); + + expect(mockRequestInterceptor).toHaveBeenCalledOnce(); + + client.interceptors.request.eject(interceptorId); + }, + ); +}); + +// Note: дополнительные проверки поведения ofetch (responseType/responseStyle/retry) +// не дублируем, чтобы набор тестов оставался сопоставим с другими клиентами. diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/__tests__/utils.test.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/__tests__/utils.test.ts new file mode 100644 index 000000000..19e1b6336 --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/__tests__/utils.test.ts @@ -0,0 +1,202 @@ +import { describe, expect, it, vi } from 'vitest'; + +import type { Auth } from '../../client-core/bundle/auth'; +import { mergeHeaders, setAuthParams } from '../bundle/utils'; + +describe('mergeHeaders', () => { + it('merges plain objects into Headers', () => { + const headers = mergeHeaders( + { + baz: 'qux', + foo: 'bar', + }, + { + baz: 'override', + }, + ); + + expect(headers).toBeInstanceOf(Headers); + expect(headers.get('foo')).toBe('bar'); + expect(headers.get('baz')).toBe('override'); + }); +}); + +describe('setAuthParams', () => { + it('sets bearer token in headers', async () => { + const auth = vi.fn().mockReturnValue('foo'); + const headers = new Headers(); + const query: Record = {}; + await setAuthParams({ + auth, + headers, + query, + security: [ + { + name: 'baz', + scheme: 'bearer', + type: 'http', + }, + ], + }); + expect(auth).toHaveBeenCalled(); + expect(headers.get('baz')).toBe('Bearer foo'); + expect(Object.keys(query).length).toBe(0); + }); + + it('sets access token in query', async () => { + const auth = vi.fn().mockReturnValue('foo'); + const headers = new Headers(); + const query: Record = {}; + await setAuthParams({ + auth, + headers, + query, + security: [ + { + in: 'query', + name: 'baz', + scheme: 'bearer', + type: 'http', + }, + ], + }); + expect(auth).toHaveBeenCalled(); + expect(headers.get('baz')).toBeNull(); + expect(query.baz).toBe('Bearer foo'); + }); + + it('sets Authorization header when `in` and `name` are undefined', async () => { + const auth = vi.fn().mockReturnValue('foo'); + const headers = new Headers(); + const query: Record = {}; + await setAuthParams({ + auth, + headers, + query, + security: [ + { + type: 'http', + }, + ], + }); + expect(auth).toHaveBeenCalled(); + expect(headers.get('Authorization')).toBe('foo'); + expect(Object.keys(query).length).toBe(0); + }); + + it('sets an API key in a cookie', async () => { + const auth = vi.fn().mockReturnValue('foo'); + const headers = new Headers(); + const query: Record = {}; + await setAuthParams({ + auth, + headers, + query, + security: [ + { + in: 'cookie', + name: 'baz', + type: 'apiKey', + }, + ], + }); + expect(auth).toHaveBeenCalled(); + expect(headers.get('Cookie')).toContain('baz=foo'); + expect(Object.keys(query).length).toBe(0); + }); + + it('sets first scheme only', async () => { + const auth = vi.fn().mockReturnValue('foo'); + const headers = new Headers(); + const query: Record = {}; + await setAuthParams({ + auth, + headers, + query, + security: [ + { + name: 'baz', + scheme: 'bearer', + type: 'http', + }, + { + in: 'query', + name: 'baz', + scheme: 'bearer', + type: 'http', + }, + ], + }); + expect(auth).toHaveBeenCalled(); + expect(headers.get('baz')).toBe('Bearer foo'); + expect(query.baz).toBeUndefined(); + }); + + it('sets first scheme with token', async () => { + const auth = vi.fn().mockImplementation((auth: Auth) => { + if (auth.type === 'apiKey') { + return; + } + return 'foo'; + }); + const headers = new Headers(); + const query: Record = {}; + await setAuthParams({ + auth, + headers, + query, + security: [ + { + name: 'baz', + type: 'apiKey', + }, + { + in: 'query', + name: 'baz', + scheme: 'bearer', + type: 'http', + }, + ], + }); + expect(auth).toHaveBeenCalled(); + expect(headers.get('baz')).toBeNull(); + expect(query.baz).toBe('Bearer foo'); + }); + + it('sets only one specific header', async () => { + const auth = vi.fn(({ name }: Auth) => { + if (name === 'baz') { + return 'foo'; + } + return 'buz'; + }); + const headers = new Headers(); + const query: Record = {}; + await setAuthParams({ + auth, + headers, + query, + security: [ + { + name: 'baz', + scheme: 'bearer', + type: 'http', + }, + { + name: 'fiz', + type: 'http', + }, + { + in: 'query', + name: 'baz', + scheme: 'bearer', + type: 'http', + }, + ], + }); + expect(auth).toHaveBeenCalled(); + expect(headers.get('baz')).toBe('Bearer foo'); + expect(headers.get('fiz')).toBe('buz'); + expect(Object.keys(query).length).toBe(0); + }); +}); From ae42ac9ad1262aaa617b8a1f0beb6e66c8a83649 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 17:08:17 +0700 Subject: [PATCH 08/26] chore: bump `pnpm-lock.yaml` --- pnpm-lock.yaml | 873 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 699 insertions(+), 174 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7250f012..66f06a37e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -170,7 +170,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: 19.2.0 - version: 19.2.0(696c3532ef15b073c21785e1bc79a040) + version: 19.2.0(0febb1e4c56a1b5e1c5d3c7b402114ed) '@angular/cli': specifier: 19.2.0 version: 19.2.0(@types/node@22.10.5)(chokidar@4.0.3) @@ -264,7 +264,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: 19.2.0 - version: 19.2.0(b57b04a4dfd0d7238fcb437f41884422) + version: 19.2.0(7fd0f8177046d1fae009bc667d774099) '@angular/cli': specifier: 19.2.0 version: 19.2.0(@types/node@22.10.5)(chokidar@4.0.3) @@ -529,7 +529,7 @@ importers: version: link:../../packages/nuxt nuxt: specifier: 3.14.1592 - version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) vue: specifier: 3.5.13 version: 3.5.13(typescript@5.9.2) @@ -544,6 +544,91 @@ importers: specifier: 7.1.2 version: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + examples/openapi-ts-ofetch: + dependencies: + ofetch: + specifier: 1.4.1 + version: 1.4.1 + vue: + specifier: 3.5.21 + version: 3.5.21(typescript@5.8.3) + devDependencies: + '@config/vite-base': + specifier: workspace:* + version: link:../../packages/config-vite-base + '@hey-api/openapi-ts': + specifier: workspace:* + version: link:../../packages/openapi-ts + '@rushstack/eslint-patch': + specifier: 1.10.5 + version: 1.10.5 + '@tsconfig/node20': + specifier: 20.1.4 + version: 20.1.4 + '@types/jsdom': + specifier: 21.1.7 + version: 21.1.7 + '@types/node': + specifier: 22.10.5 + version: 22.10.5 + '@vitejs/plugin-vue': + specifier: 5.2.1 + version: 5.2.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) + '@vitejs/plugin-vue-jsx': + specifier: 4.1.1 + version: 4.1.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) + '@vue/eslint-config-prettier': + specifier: 10.1.0 + version: 10.1.0(@types/eslint@9.6.0)(eslint@9.17.0(jiti@2.5.1))(prettier@3.4.2) + '@vue/eslint-config-typescript': + specifier: 14.2.0 + version: 14.2.0(eslint-plugin-vue@9.32.0(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3) + '@vue/test-utils': + specifier: 2.4.6 + version: 2.4.6 + '@vue/tsconfig': + specifier: 0.7.0 + version: 0.7.0(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3)) + autoprefixer: + specifier: 10.4.20 + version: 10.4.20(postcss@8.4.41) + eslint: + specifier: 9.17.0 + version: 9.17.0(jiti@2.5.1) + eslint-plugin-vue: + specifier: 9.32.0 + version: 9.32.0(eslint@9.17.0(jiti@2.5.1)) + jsdom: + specifier: 23.0.0 + version: 23.0.0 + npm-run-all2: + specifier: 6.2.0 + version: 6.2.0 + postcss: + specifier: 8.4.41 + version: 8.4.41 + prettier: + specifier: 3.4.2 + version: 3.4.2 + tailwindcss: + specifier: 3.4.9 + version: 3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) + typescript: + specifier: 5.8.3 + version: 5.8.3 + vite: + specifier: 7.1.2 + version: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + vite-plugin-vue-devtools: + specifier: 7.7.0 + version: 7.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) + vitest: + specifier: 3.1.1 + version: 3.1.1(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + vue-tsc: + specifier: 2.2.0 + version: 2.2.0(typescript@5.8.3) + examples/openapi-ts-openai: dependencies: '@radix-ui/react-form': @@ -825,7 +910,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: ^19.2.15 - version: 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@angular/platform-server@19.2.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(fc183c600d5538ac11e1814ee07b5dfc))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))(yaml@2.8.0) + version: 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@angular/platform-server@19.2.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(fc183c600d5538ac11e1814ee07b5dfc))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))(yaml@2.8.0) '@angular/cli': specifier: ^19.2.15 version: 19.2.15(@types/node@22.10.5)(chokidar@4.0.3) @@ -1147,10 +1232,10 @@ importers: version: 1.7.4 nuxt: specifier: '>=3.0.0' - version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) vue: specifier: '>=3.5.13' - version: 3.5.13(typescript@5.8.3) + version: 3.5.13(typescript@5.9.2) devDependencies: '@config/vite-base': specifier: workspace:* @@ -1160,13 +1245,13 @@ importers: version: link:../openapi-ts '@nuxt/module-builder': specifier: 0.8.4 - version: 0.8.4(@nuxt/kit@3.15.4(magicast@0.3.5))(nuxi@3.28.0)(sass@1.85.0)(typescript@5.8.3) + version: 0.8.4(@nuxt/kit@3.15.4(magicast@0.3.5))(nuxi@3.28.0)(sass@1.85.0)(typescript@5.9.2) '@nuxt/schema': specifier: 3.16.2 version: 3.16.2 '@nuxt/test-utils': specifier: 3.17.2 - version: 3.17.2(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0) + version: 3.17.2(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0) vite: specifier: 7.1.2 version: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) @@ -1257,7 +1342,10 @@ importers: version: 3.3.2 nuxt: specifier: 3.14.1592 - version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + ofetch: + specifier: ^1.4.0 + version: 1.4.1 prettier: specifier: 3.4.2 version: 3.4.2 @@ -1287,7 +1375,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: 19.2.0 - version: 19.2.0(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))))(@angular/ssr@19.2.15(dd1e44f07212b3264aa2eee962615cd1))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0) + version: 19.2.0(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))))(@angular/ssr@19.2.15(dd1e44f07212b3264aa2eee962615cd1))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0) '@angular/animations': specifier: 19.2.15 version: 19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)) @@ -1371,7 +1459,7 @@ importers: version: 3.3.2 nuxt: specifier: 3.14.1592 - version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) prettier: specifier: 3.4.2 version: 3.4.2 @@ -3675,6 +3763,12 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -3683,10 +3777,22 @@ packages: resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.1': + resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.13.0': resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.9.1': resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3703,6 +3809,10 @@ packages: resolution: {integrity: sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.35.0': + resolution: {integrity: sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3711,6 +3821,10 @@ packages: resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/ajv-compiler@4.0.2': resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==} @@ -6490,24 +6604,36 @@ packages: '@vue/compiler-core@3.5.20': resolution: {integrity: sha512-8TWXUyiqFd3GmP4JTX9hbiTFRwYHgVL/vr3cqhr4YQ258+9FADwvj7golk2sWNGHR67QgmCZ8gz80nQcMokhwg==} + '@vue/compiler-core@3.5.21': + resolution: {integrity: sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==} + '@vue/compiler-dom@3.5.13': resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} '@vue/compiler-dom@3.5.20': resolution: {integrity: sha512-whB44M59XKjqUEYOMPYU0ijUV0G+4fdrHVKDe32abNdX/kJe1NUEMqsi4cwzXa9kyM9w5S8WqFsrfo1ogtBZGQ==} + '@vue/compiler-dom@3.5.21': + resolution: {integrity: sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==} + '@vue/compiler-sfc@3.5.13': resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} '@vue/compiler-sfc@3.5.20': resolution: {integrity: sha512-SFcxapQc0/feWiSBfkGsa1v4DOrnMAQSYuvDMpEaxbpH5dKbnEM5KobSNSgU+1MbHCl+9ftm7oQWxvwDB6iBfw==} + '@vue/compiler-sfc@3.5.21': + resolution: {integrity: sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==} + '@vue/compiler-ssr@3.5.13': resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} '@vue/compiler-ssr@3.5.20': resolution: {integrity: sha512-RSl5XAMc5YFUXpDQi+UQDdVjH9FnEpLDHIALg5J0ITHxkEzJ8uQLlo7CIbjPYqmZtt6w0TsIPbo1izYXwDG7JA==} + '@vue/compiler-ssr@3.5.21': + resolution: {integrity: sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==} + '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} @@ -6573,18 +6699,27 @@ packages: '@vue/reactivity@3.5.20': resolution: {integrity: sha512-hS8l8x4cl1fmZpSQX/NXlqWKARqEsNmfkwOIYqtR2F616NGfsLUm0G6FQBK6uDKUCVyi1YOL8Xmt/RkZcd/jYQ==} + '@vue/reactivity@3.5.21': + resolution: {integrity: sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==} + '@vue/runtime-core@3.5.13': resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} '@vue/runtime-core@3.5.20': resolution: {integrity: sha512-vyQRiH5uSZlOa+4I/t4Qw/SsD/gbth0SW2J7oMeVlMFMAmsG1rwDD6ok0VMmjXY3eI0iHNSSOBilEDW98PLRKw==} + '@vue/runtime-core@3.5.21': + resolution: {integrity: sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==} + '@vue/runtime-dom@3.5.13': resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} '@vue/runtime-dom@3.5.20': resolution: {integrity: sha512-KBHzPld/Djw3im0CQ7tGCpgRedryIn4CcAl047EhFTCCPT2xFf4e8j6WeKLgEEoqPSl9TYqShc3Q6tpWpz/Xgw==} + '@vue/runtime-dom@3.5.21': + resolution: {integrity: sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==} + '@vue/server-renderer@3.5.13': resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} peerDependencies: @@ -6595,12 +6730,20 @@ packages: peerDependencies: vue: 3.5.20 + '@vue/server-renderer@3.5.21': + resolution: {integrity: sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==} + peerDependencies: + vue: 3.5.21 + '@vue/shared@3.5.13': resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} '@vue/shared@3.5.20': resolution: {integrity: sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==} + '@vue/shared@3.5.21': + resolution: {integrity: sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==} + '@vue/test-utils@2.4.6': resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} @@ -8349,6 +8492,16 @@ packages: jiti: optional: true + eslint@9.35.0: + resolution: {integrity: sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + esm-env@1.2.2: resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} @@ -12217,6 +12370,11 @@ packages: tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + tailwindcss@3.4.14: + resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} + engines: {node: '>=14.0.0'} + hasBin: true + tailwindcss@3.4.9: resolution: {integrity: sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==} engines: {node: '>=14.0.0'} @@ -12316,6 +12474,10 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -13173,6 +13335,46 @@ packages: yaml: optional: true + vite@7.1.5: + resolution: {integrity: sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitefu@1.1.1: resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} peerDependencies: @@ -13344,6 +13546,14 @@ packages: typescript: optional: true + vue@3.5.21: + resolution: {integrity: sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -13715,13 +13925,13 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@19.2.0(696c3532ef15b073c21785e1bc79a040)': + '@angular-devkit/build-angular@19.2.0(0febb1e4c56a1b5e1c5d3c7b402114ed)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.0)))(webpack@5.98.0(esbuild@0.25.0)) '@angular-devkit/core': 19.2.0(chokidar@4.0.3) - '@angular/build': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/platform-server@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(5c03da8199d2fcdf9ff93b70f9349edd))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) + '@angular/build': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/platform-server@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(5c03da8199d2fcdf9ff93b70f9349edd))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3) '@babel/core': 7.26.9 '@babel/generator': 7.26.9 @@ -13734,7 +13944,7 @@ snapshots: '@babel/runtime': 7.26.9 '@discoveryjs/json-ext': 0.6.3 '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) @@ -13779,7 +13989,7 @@ snapshots: '@angular/ssr': 19.2.15(5c03da8199d2fcdf9ff93b70f9349edd) esbuild: 0.25.0 karma: 6.4.4 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) + tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) transitivePeerDependencies: - '@angular/compiler' - '@rspack/core' @@ -13803,14 +14013,14 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-angular@19.2.0(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))))(@angular/ssr@19.2.15(dd1e44f07212b3264aa2eee962615cd1))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0)': + '@angular-devkit/build-angular@19.2.0(7fd0f8177046d1fae009bc667d774099)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.0)))(webpack@5.98.0(esbuild@0.25.0)) '@angular-devkit/core': 19.2.0(chokidar@4.0.3) - '@angular/build': 19.2.0(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))))(@angular/ssr@19.2.15(dd1e44f07212b3264aa2eee962615cd1))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) - '@angular/compiler-cli': 19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3) + '@angular/build': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/platform-server@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(5c03da8199d2fcdf9ff93b70f9349edd))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) + '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3) '@babel/core': 7.26.9 '@babel/generator': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 @@ -13821,8 +14031,8 @@ snapshots: '@babel/preset-env': 7.26.9(@babel/core@7.26.9) '@babel/runtime': 7.26.9 '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) @@ -13863,11 +14073,11 @@ snapshots: webpack-merge: 6.0.1 webpack-subresource-integrity: 5.1.0(webpack@5.98.0(esbuild@0.25.9)) optionalDependencies: - '@angular/platform-server': 19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))) - '@angular/ssr': 19.2.15(dd1e44f07212b3264aa2eee962615cd1) + '@angular/platform-server': 19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))) + '@angular/ssr': 19.2.15(5c03da8199d2fcdf9ff93b70f9349edd) esbuild: 0.25.0 karma: 6.4.4 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) + tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) transitivePeerDependencies: - '@angular/compiler' - '@rspack/core' @@ -13891,14 +14101,14 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-angular@19.2.0(b57b04a4dfd0d7238fcb437f41884422)': + '@angular-devkit/build-angular@19.2.0(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))))(@angular/ssr@19.2.15(dd1e44f07212b3264aa2eee962615cd1))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.0)))(webpack@5.98.0(esbuild@0.25.0)) '@angular-devkit/core': 19.2.0(chokidar@4.0.3) - '@angular/build': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/platform-server@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(5c03da8199d2fcdf9ff93b70f9349edd))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) - '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3) + '@angular/build': 19.2.0(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))))(@angular/ssr@19.2.15(dd1e44f07212b3264aa2eee962615cd1))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) + '@angular/compiler-cli': 19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3) '@babel/core': 7.26.9 '@babel/generator': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 @@ -13909,8 +14119,8 @@ snapshots: '@babel/preset-env': 7.26.9(@babel/core@7.26.9) '@babel/runtime': 7.26.9 '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) @@ -13951,11 +14161,11 @@ snapshots: webpack-merge: 6.0.1 webpack-subresource-integrity: 5.1.0(webpack@5.98.0(esbuild@0.25.9)) optionalDependencies: - '@angular/platform-server': 19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))) - '@angular/ssr': 19.2.15(5c03da8199d2fcdf9ff93b70f9349edd) + '@angular/platform-server': 19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))) + '@angular/ssr': 19.2.15(dd1e44f07212b3264aa2eee962615cd1) esbuild: 0.25.0 karma: 6.4.4 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) + tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) transitivePeerDependencies: - '@angular/compiler' - '@rspack/core' @@ -13979,13 +14189,13 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-angular@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@angular/platform-server@19.2.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(fc183c600d5538ac11e1814ee07b5dfc))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))(yaml@2.8.0)': + '@angular-devkit/build-angular@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@angular/platform-server@19.2.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(fc183c600d5538ac11e1814ee07b5dfc))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))(yaml@2.8.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) '@angular-devkit/build-webpack': 0.1902.15(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.98.0(esbuild@0.25.9)))(webpack@5.98.0(esbuild@0.25.9)) '@angular-devkit/core': 19.2.15(chokidar@4.0.3) - '@angular/build': 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@angular/platform-server@19.2.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(fc183c600d5538ac11e1814ee07b5dfc))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) + '@angular/build': 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@angular/platform-server@19.2.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(fc183c600d5538ac11e1814ee07b5dfc))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) '@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -13998,7 +14208,7 @@ snapshots: '@babel/runtime': 7.26.10 '@discoveryjs/json-ext': 0.6.3 '@ngtools/webpack': 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.9)) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.98.0(esbuild@0.25.9)) @@ -14043,7 +14253,7 @@ snapshots: '@angular/ssr': 19.2.15(fc183c600d5538ac11e1814ee07b5dfc) esbuild: 0.25.4 karma: 6.4.4 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) + tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) transitivePeerDependencies: - '@angular/compiler' - '@rspack/core' @@ -14145,7 +14355,7 @@ snapshots: '@angular/core': 19.2.15(rxjs@7.8.1)(zone.js@0.15.1) tslib: 2.8.1 - '@angular/build@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/platform-server@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(5c03da8199d2fcdf9ff93b70f9349edd))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0)': + '@angular/build@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/platform-server@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(5c03da8199d2fcdf9ff93b70f9349edd))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) @@ -14183,7 +14393,7 @@ snapshots: less: 4.2.2 lmdb: 3.2.6 postcss: 8.5.2 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) + tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) transitivePeerDependencies: - '@types/node' - chokidar @@ -14197,7 +14407,7 @@ snapshots: - tsx - yaml - '@angular/build@19.2.0(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))))(@angular/ssr@19.2.15(dd1e44f07212b3264aa2eee962615cd1))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0)': + '@angular/build@19.2.0(@angular/compiler-cli@19.2.15(@angular/compiler@19.2.15)(typescript@5.8.3))(@angular/compiler@19.2.15)(@angular/platform-server@19.2.0(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/compiler@19.2.15)(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(@angular/platform-browser@19.2.15(@angular/animations@19.2.15(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1)))(@angular/common@19.2.15(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@19.2.15(rxjs@7.8.1)(zone.js@0.15.1))))(@angular/ssr@19.2.15(dd1e44f07212b3264aa2eee962615cd1))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) @@ -14235,7 +14445,7 @@ snapshots: less: 4.2.2 lmdb: 3.2.6 postcss: 8.5.2 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) + tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) transitivePeerDependencies: - '@types/node' - chokidar @@ -14249,7 +14459,7 @@ snapshots: - tsx - yaml - '@angular/build@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@angular/platform-server@19.2.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(fc183c600d5538ac11e1814ee07b5dfc))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0)': + '@angular/build@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@angular/platform-server@19.2.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.14)(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(fc183c600d5538ac11e1814ee07b5dfc))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) @@ -14287,7 +14497,7 @@ snapshots: less: 4.2.2 lmdb: 3.2.6 postcss: 8.5.2 - tailwindcss: 3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) + tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) transitivePeerDependencies: - '@types/node' - chokidar @@ -16724,6 +16934,17 @@ snapshots: eslint: 9.17.0(jiti@2.5.1) eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.0(eslint@9.17.0(jiti@2.5.1))': + dependencies: + eslint: 9.17.0(jiti@2.5.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.0(eslint@9.35.0(jiti@2.5.1))': + dependencies: + eslint: 9.35.0(jiti@2.5.1) + eslint-visitor-keys: 3.4.3 + optional: true + '@eslint-community/regexpp@4.12.1': {} '@eslint/config-array@0.19.2': @@ -16734,10 +16955,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + optional: true + + '@eslint/config-helpers@0.3.1': + optional: true + '@eslint/core@0.13.0': dependencies: '@types/json-schema': 7.0.15 + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + optional: true + '@eslint/core@0.9.1': dependencies: '@types/json-schema': 7.0.15 @@ -16760,6 +16998,9 @@ snapshots: '@eslint/js@9.32.0': {} + '@eslint/js@9.35.0': + optional: true + '@eslint/object-schema@2.1.6': {} '@eslint/plugin-kit@0.2.8': @@ -16767,6 +17008,12 @@ snapshots: '@eslint/core': 0.13.0 levn: 0.4.1 + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + optional: true + '@fastify/ajv-compiler@4.0.2': dependencies: ajv: 8.17.1 @@ -17514,6 +17761,16 @@ snapshots: - magicast - supports-color + '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': + dependencies: + '@nuxt/kit': 3.15.4(magicast@0.3.5) + '@nuxt/schema': 3.16.2 + execa: 7.2.0 + vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + transitivePeerDependencies: + - magicast + - supports-color + '@nuxt/devtools-wizard@1.7.0': dependencies: consola: 3.4.2 @@ -17527,13 +17784,13 @@ snapshots: rc9: 2.1.2 semver: 7.7.2 - '@nuxt/devtools@1.7.0(rollup@3.29.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': + '@nuxt/devtools@1.7.0(rollup@3.29.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2))': dependencies: '@antfu/utils': 0.7.10 '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) '@nuxt/devtools-wizard': 1.7.0 '@nuxt/kit': 3.15.4(magicast@0.3.5) - '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) + '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2)) '@vue/devtools-kit': 7.6.8 birpc: 0.2.19 consola: 3.4.2 @@ -17574,13 +17831,13 @@ snapshots: - utf-8-validate - vue - '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': + '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2))': dependencies: '@antfu/utils': 0.7.10 '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) '@nuxt/devtools-wizard': 1.7.0 '@nuxt/kit': 3.15.4(magicast@0.3.5) - '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) + '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2)) '@vue/devtools-kit': 7.6.8 birpc: 0.2.19 consola: 3.4.2 @@ -17621,13 +17878,13 @@ snapshots: - utf-8-validate - vue - '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2))': + '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))': dependencies: '@antfu/utils': 0.7.10 - '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) '@nuxt/devtools-wizard': 1.7.0 '@nuxt/kit': 3.15.4(magicast@0.3.5) - '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2)) + '@vue/devtools-core': 7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) '@vue/devtools-kit': 7.6.8 birpc: 0.2.19 consola: 3.4.2 @@ -17656,9 +17913,9 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.10 unimport: 3.14.6(rollup@4.50.0) - vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) - vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) - vite-plugin-vue-inspector: 5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + vite-plugin-vue-inspector: 5.3.2(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) which: 3.0.1 ws: 8.18.3 transitivePeerDependencies: @@ -17775,7 +18032,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/module-builder@0.8.4(@nuxt/kit@3.15.4(magicast@0.3.5))(nuxi@3.28.0)(sass@1.85.0)(typescript@5.8.3)': + '@nuxt/module-builder@0.8.4(@nuxt/kit@3.15.4(magicast@0.3.5))(nuxi@3.28.0)(sass@1.85.0)(typescript@5.9.2)': dependencies: '@nuxt/kit': 3.15.4(magicast@0.3.5) citty: 0.1.6 @@ -17786,8 +18043,8 @@ snapshots: nuxi: 3.28.0 pathe: 1.1.2 pkg-types: 1.3.1 - tsconfck: 3.1.6(typescript@5.8.3) - unbuild: 2.0.0(sass@1.85.0)(typescript@5.8.3) + tsconfck: 3.1.6(typescript@5.9.2) + unbuild: 2.0.0(sass@1.85.0)(typescript@5.9.2) transitivePeerDependencies: - sass - supports-color @@ -17859,7 +18116,7 @@ snapshots: - magicast - supports-color - '@nuxt/test-utils@3.17.2(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0)': + '@nuxt/test-utils@3.17.2(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0)': dependencies: '@nuxt/kit': 3.18.1(magicast@0.3.5) '@nuxt/schema': 3.16.2 @@ -17885,8 +18142,8 @@ snapshots: ufo: 1.6.1 unplugin: 2.3.10 vite: 6.3.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) - vitest-environment-nuxt: 1.0.1(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0) - vue: 3.5.13(typescript@5.8.3) + vitest-environment-nuxt: 1.0.1(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0) + vue: 3.5.21(typescript@5.9.2) optionalDependencies: '@vue/test-utils': 2.4.6 jsdom: 23.0.0 @@ -17906,12 +18163,12 @@ snapshots: - typescript - yaml - '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3))': + '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3))': dependencies: - '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@3.29.5) - '@rollup/plugin-replace': 6.0.2(rollup@3.29.5) - '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3)) - '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3)) + '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) + '@rollup/plugin-replace': 6.0.2(rollup@4.50.0) + '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.8.3)) + '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.8.3)) autoprefixer: 10.4.20(postcss@8.5.6) clear: 0.1.0 consola: 3.4.2 @@ -17932,7 +18189,7 @@ snapshots: perfect-debounce: 1.0.0 pkg-types: 1.3.1 postcss: 8.5.6 - rollup-plugin-visualizer: 5.14.0(rollup@3.29.5) + rollup-plugin-visualizer: 5.14.0(rollup@4.50.0) std-env: 3.9.0 strip-literal: 2.1.1 ufo: 1.6.1 @@ -17941,7 +18198,7 @@ snapshots: vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) vite-node: 2.1.9(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) vite-plugin-checker: 0.8.0(eslint@9.17.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.8.3)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) - vue: 3.5.13(typescript@5.8.3) + vue: 3.5.21(typescript@5.8.3) vue-bundle-renderer: 2.1.2 transitivePeerDependencies: - '@biomejs/biome' @@ -17966,12 +18223,12 @@ snapshots: - vti - vue-tsc - '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3))': + '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))': dependencies: - '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) - '@rollup/plugin-replace': 6.0.2(rollup@4.50.0) - '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3)) - '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3)) + '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@3.29.5) + '@rollup/plugin-replace': 6.0.2(rollup@3.29.5) + '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2)) + '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2)) autoprefixer: 10.4.20(postcss@8.5.6) clear: 0.1.0 consola: 3.4.2 @@ -17992,7 +18249,7 @@ snapshots: perfect-debounce: 1.0.0 pkg-types: 1.3.1 postcss: 8.5.6 - rollup-plugin-visualizer: 5.14.0(rollup@4.50.0) + rollup-plugin-visualizer: 5.14.0(rollup@3.29.5) std-env: 3.9.0 strip-literal: 2.1.1 ufo: 1.6.1 @@ -18000,8 +18257,8 @@ snapshots: unplugin: 1.16.1 vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) vite-node: 2.1.9(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) - vite-plugin-checker: 0.8.0(eslint@9.17.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.8.3)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) - vue: 3.5.13(typescript@5.8.3) + vite-plugin-checker: 0.8.0(eslint@9.35.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) + vue: 3.5.21(typescript@5.9.2) vue-bundle-renderer: 2.1.2 transitivePeerDependencies: - '@biomejs/biome' @@ -18026,12 +18283,12 @@ snapshots: - vti - vue-tsc - '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2))': + '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))': dependencies: '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) '@rollup/plugin-replace': 6.0.2(rollup@4.50.0) - '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.9.2)) - '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.9.2)) + '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2)) + '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2)) autoprefixer: 10.4.20(postcss@8.5.6) clear: 0.1.0 consola: 3.4.2 @@ -18060,8 +18317,8 @@ snapshots: unplugin: 1.16.1 vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) vite-node: 2.1.9(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) - vite-plugin-checker: 0.8.0(eslint@9.17.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) - vue: 3.5.13(typescript@5.9.2) + vite-plugin-checker: 0.8.0(eslint@9.35.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) + vue: 3.5.21(typescript@5.9.2) vue-bundle-renderer: 2.1.2 transitivePeerDependencies: - '@biomejs/biome' @@ -19818,21 +20075,21 @@ snapshots: '@unhead/schema': 1.11.20 '@unhead/shared': 1.11.20 - '@unhead/vue@1.11.20(vue@3.5.13(typescript@5.8.3))': + '@unhead/vue@1.11.20(vue@3.5.21(typescript@5.8.3))': dependencies: '@unhead/schema': 1.11.20 '@unhead/shared': 1.11.20 hookable: 5.5.3 unhead: 1.11.20 - vue: 3.5.13(typescript@5.8.3) + vue: 3.5.21(typescript@5.8.3) - '@unhead/vue@1.11.20(vue@3.5.13(typescript@5.9.2))': + '@unhead/vue@1.11.20(vue@3.5.21(typescript@5.9.2))': dependencies: '@unhead/schema': 1.11.20 '@unhead/shared': 1.11.20 hookable: 5.5.3 unhead: 1.11.20 - vue: 3.5.13(typescript@5.9.2) + vue: 3.5.21(typescript@5.9.2) '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -19920,13 +20177,13 @@ snapshots: dependencies: vite: 6.2.7(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) - '@vitejs/plugin-basic-ssl@1.2.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))': + '@vitejs/plugin-basic-ssl@1.2.0(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))': dependencies: - vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) + vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) - '@vitejs/plugin-basic-ssl@1.2.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': + '@vitejs/plugin-basic-ssl@1.2.0(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': dependencies: - vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) '@vitejs/plugin-react@4.4.0-beta.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': dependencies: @@ -19939,23 +20196,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3))': + '@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.8.3))': dependencies: '@babel/core': 7.28.3 '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) - vue: 3.5.13(typescript@5.8.3) + vue: 3.5.21(typescript@5.8.3) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.9.2))': + '@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2))': dependencies: '@babel/core': 7.28.3 '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) - vue: 3.5.13(typescript@5.9.2) + vue: 3.5.21(typescript@5.9.2) transitivePeerDependencies: - supports-color @@ -19969,21 +20226,36 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3))': + '@vitejs/plugin-vue-jsx@4.1.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))': + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) + vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + vue: 3.5.21(typescript@5.8.3) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.8.3))': dependencies: vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) - vue: 3.5.13(typescript@5.8.3) + vue: 3.5.21(typescript@5.8.3) - '@vitejs/plugin-vue@5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.9.2))': + '@vitejs/plugin-vue@5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2))': dependencies: vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) - vue: 3.5.13(typescript@5.9.2) + vue: 3.5.21(typescript@5.9.2) '@vitejs/plugin-vue@5.2.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': dependencies: vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) vue: 3.5.13(typescript@5.8.3) + '@vitejs/plugin-vue@5.2.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))': + dependencies: + vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + vue: 3.5.21(typescript@5.8.3) + '@vitejs/plugin-vue@6.0.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.20(typescript@5.9.2))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 @@ -20102,7 +20374,7 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@vue-macros/common@1.16.1(vue@3.5.13(typescript@5.8.3))': + '@vue-macros/common@1.16.1(vue@3.5.21(typescript@5.8.3))': dependencies: '@vue/compiler-sfc': 3.5.20 ast-kit: 1.4.3 @@ -20111,9 +20383,9 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.3 optionalDependencies: - vue: 3.5.13(typescript@5.8.3) + vue: 3.5.21(typescript@5.8.3) - '@vue-macros/common@1.16.1(vue@3.5.13(typescript@5.9.2))': + '@vue-macros/common@1.16.1(vue@3.5.21(typescript@5.9.2))': dependencies: '@vue/compiler-sfc': 3.5.20 ast-kit: 1.4.3 @@ -20122,7 +20394,7 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.3 optionalDependencies: - vue: 3.5.13(typescript@5.9.2) + vue: 3.5.21(typescript@5.9.2) '@vue/babel-helper-vue-transform-on@1.5.0': {} @@ -20136,7 +20408,7 @@ snapshots: '@babel/types': 7.28.2 '@vue/babel-helper-vue-transform-on': 1.5.0 '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.28.3) - '@vue/shared': 3.5.20 + '@vue/shared': 3.5.21 optionalDependencies: '@babel/core': 7.28.3 transitivePeerDependencies: @@ -20149,7 +20421,7 @@ snapshots: '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 '@babel/parser': 7.28.3 - '@vue/compiler-sfc': 3.5.20 + '@vue/compiler-sfc': 3.5.21 transitivePeerDependencies: - supports-color @@ -20169,6 +20441,14 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 + '@vue/compiler-core@3.5.21': + dependencies: + '@babel/parser': 7.28.3 + '@vue/shared': 3.5.21 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + '@vue/compiler-dom@3.5.13': dependencies: '@vue/compiler-core': 3.5.13 @@ -20179,6 +20459,11 @@ snapshots: '@vue/compiler-core': 3.5.20 '@vue/shared': 3.5.20 + '@vue/compiler-dom@3.5.21': + dependencies: + '@vue/compiler-core': 3.5.21 + '@vue/shared': 3.5.21 + '@vue/compiler-sfc@3.5.13': dependencies: '@babel/parser': 7.28.3 @@ -20203,6 +20488,18 @@ snapshots: postcss: 8.5.6 source-map-js: 1.2.1 + '@vue/compiler-sfc@3.5.21': + dependencies: + '@babel/parser': 7.28.3 + '@vue/compiler-core': 3.5.21 + '@vue/compiler-dom': 3.5.21 + '@vue/compiler-ssr': 3.5.21 + '@vue/shared': 3.5.21 + estree-walker: 2.0.2 + magic-string: 0.30.18 + postcss: 8.5.6 + source-map-js: 1.2.1 + '@vue/compiler-ssr@3.5.13': dependencies: '@vue/compiler-dom': 3.5.13 @@ -20213,6 +20510,11 @@ snapshots: '@vue/compiler-dom': 3.5.20 '@vue/shared': 3.5.20 + '@vue/compiler-ssr@3.5.21': + dependencies: + '@vue/compiler-dom': 3.5.21 + '@vue/shared': 3.5.21 + '@vue/compiler-vue2@2.7.16': dependencies: de-indent: 1.0.2 @@ -20224,7 +20526,7 @@ snapshots: dependencies: '@vue/devtools-kit': 8.0.1 - '@vue/devtools-core@7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': + '@vue/devtools-core@7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2))': dependencies: '@vue/devtools-kit': 7.6.8 '@vue/devtools-shared': 7.7.7 @@ -20232,19 +20534,19 @@ snapshots: nanoid: 5.1.5 pathe: 1.1.2 vite-hot-client: 0.2.4(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) - vue: 3.5.13(typescript@5.8.3) + vue: 3.5.21(typescript@5.9.2) transitivePeerDependencies: - vite - '@vue/devtools-core@7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2))': + '@vue/devtools-core@7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))': dependencies: '@vue/devtools-kit': 7.6.8 '@vue/devtools-shared': 7.7.7 mitt: 3.0.1 nanoid: 5.1.5 pathe: 1.1.2 - vite-hot-client: 0.2.4(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) - vue: 3.5.13(typescript@5.9.2) + vite-hot-client: 0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + vue: 3.5.21(typescript@5.8.3) transitivePeerDependencies: - vite @@ -20260,6 +20562,18 @@ snapshots: transitivePeerDependencies: - vite + '@vue/devtools-core@7.7.7(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))': + dependencies: + '@vue/devtools-kit': 7.7.7 + '@vue/devtools-shared': 7.7.7 + mitt: 3.0.1 + nanoid: 5.1.5 + pathe: 2.0.3 + vite-hot-client: 2.1.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + vue: 3.5.21(typescript@5.8.3) + transitivePeerDependencies: + - vite + '@vue/devtools-kit@7.6.8': dependencies: '@vue/devtools-shared': 7.7.7 @@ -20322,9 +20636,9 @@ snapshots: '@vue/language-core@2.2.0(typescript@5.8.3)': dependencies: '@volar/language-core': 2.4.23 - '@vue/compiler-dom': 3.5.20 + '@vue/compiler-dom': 3.5.21 '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.5.20 + '@vue/shared': 3.5.21 alien-signals: 0.4.14 minimatch: 9.0.5 muggle-string: 0.4.1 @@ -20340,6 +20654,10 @@ snapshots: dependencies: '@vue/shared': 3.5.20 + '@vue/reactivity@3.5.21': + dependencies: + '@vue/shared': 3.5.21 + '@vue/runtime-core@3.5.13': dependencies: '@vue/reactivity': 3.5.13 @@ -20350,6 +20668,11 @@ snapshots: '@vue/reactivity': 3.5.20 '@vue/shared': 3.5.20 + '@vue/runtime-core@3.5.21': + dependencies: + '@vue/reactivity': 3.5.21 + '@vue/shared': 3.5.21 + '@vue/runtime-dom@3.5.13': dependencies: '@vue/reactivity': 3.5.13 @@ -20364,6 +20687,13 @@ snapshots: '@vue/shared': 3.5.20 csstype: 3.1.3 + '@vue/runtime-dom@3.5.21': + dependencies: + '@vue/reactivity': 3.5.21 + '@vue/runtime-core': 3.5.21 + '@vue/shared': 3.5.21 + csstype: 3.1.3 + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3))': dependencies: '@vue/compiler-ssr': 3.5.13 @@ -20382,10 +20712,24 @@ snapshots: '@vue/shared': 3.5.20 vue: 3.5.20(typescript@5.9.2) + '@vue/server-renderer@3.5.21(vue@3.5.21(typescript@5.8.3))': + dependencies: + '@vue/compiler-ssr': 3.5.21 + '@vue/shared': 3.5.21 + vue: 3.5.21(typescript@5.8.3) + + '@vue/server-renderer@3.5.21(vue@3.5.21(typescript@5.9.2))': + dependencies: + '@vue/compiler-ssr': 3.5.21 + '@vue/shared': 3.5.21 + vue: 3.5.21(typescript@5.9.2) + '@vue/shared@3.5.13': {} '@vue/shared@3.5.20': {} + '@vue/shared@3.5.21': {} + '@vue/test-utils@2.4.6': dependencies: js-beautify: 1.15.4 @@ -20396,6 +20740,11 @@ snapshots: typescript: 5.8.3 vue: 3.5.13(typescript@5.8.3) + '@vue/tsconfig@0.7.0(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3))': + optionalDependencies: + typescript: 5.8.3 + vue: 3.5.21(typescript@5.8.3) + '@vueuse/core@13.9.0(vue@3.5.20(typescript@5.9.2))': dependencies: '@types/web-bluetooth': 0.0.21 @@ -20926,7 +21275,7 @@ snapshots: domhandler: 5.0.3 htmlparser2: 9.1.0 picocolors: 1.1.1 - postcss: 8.5.2 + postcss: 8.5.6 postcss-media-query-parser: 0.2.3 beasties@0.3.2: @@ -20937,7 +21286,7 @@ snapshots: domhandler: 5.0.3 htmlparser2: 10.0.0 picocolors: 1.1.1 - postcss: 8.5.2 + postcss: 8.5.6 postcss-media-query-parser: 0.2.3 better-path-resolve@1.0.0: @@ -21741,7 +22090,7 @@ snapshots: detective-vue2@2.2.0(typescript@5.9.2): dependencies: '@dependents/detective-less': 5.0.1 - '@vue/compiler-sfc': 3.5.20 + '@vue/compiler-sfc': 3.5.21 detective-es6: 5.0.1 detective-sass: 6.0.1 detective-scss: 5.0.1 @@ -22250,7 +22599,7 @@ snapshots: eslint: 9.17.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.17.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.17.0(jiti@2.5.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.17.0(jiti@2.5.1)) @@ -22284,7 +22633,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -22299,7 +22648,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -22437,7 +22786,7 @@ snapshots: eslint-plugin-vue@9.32.0(eslint@9.17.0(jiti@2.5.1)): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.17.0(jiti@2.5.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.17.0(jiti@2.5.1)) eslint: 9.17.0(jiti@2.5.1) globals: 13.24.0 natural-compare: 1.4.0 @@ -22472,7 +22821,7 @@ snapshots: eslint@9.17.0(jiti@2.5.1): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.17.0(jiti@2.5.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.17.0(jiti@2.5.1)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.19.2 '@eslint/core': 0.9.1 @@ -22511,6 +22860,49 @@ snapshots: transitivePeerDependencies: - supports-color + eslint@9.35.0(jiti@2.5.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.35.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.5.1 + transitivePeerDependencies: + - supports-color + optional: true + esm-env@1.2.2: {} espree@10.4.0: @@ -24670,7 +25062,7 @@ snapshots: mkdirp@3.0.1: {} - mkdist@1.6.0(sass@1.85.0)(typescript@5.8.3): + mkdist@1.6.0(sass@1.85.0)(typescript@5.9.2): dependencies: autoprefixer: 10.4.20(postcss@8.5.6) citty: 0.1.6 @@ -24687,7 +25079,7 @@ snapshots: tinyglobby: 0.2.14 optionalDependencies: sass: 1.85.0 - typescript: 5.8.3 + typescript: 5.9.2 mlly@1.7.4: dependencies: @@ -25061,18 +25453,18 @@ snapshots: nuxi@3.28.0: {} - nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): + nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 1.7.0(rollup@3.29.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) - '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@3.29.5) - '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@3.29.5) + '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) + '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) + '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) '@nuxt/telemetry': 2.6.6(magicast@0.3.5) - '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)) + '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3)) '@unhead/dom': 1.11.20 '@unhead/shared': 1.11.20 '@unhead/ssr': 1.11.20 - '@unhead/vue': 1.11.20(vue@3.5.13(typescript@5.8.3)) + '@unhead/vue': 1.11.20(vue@3.5.21(typescript@5.8.3)) '@vue/shared': 3.5.20 acorn: 8.14.0 c12: 2.0.1(magicast@0.3.5) @@ -25091,7 +25483,7 @@ snapshots: h3: 1.15.4 hookable: 5.5.3 ignore: 6.0.2 - impound: 0.2.2(rollup@3.29.5) + impound: 0.2.2(rollup@4.50.0) jiti: 2.5.1 klona: 2.0.6 knitwork: 1.2.0 @@ -25118,15 +25510,15 @@ snapshots: unctx: 2.4.1 unenv: 1.10.0 unhead: 1.11.20 - unimport: 3.14.6(rollup@3.29.5) + unimport: 3.14.6(rollup@4.50.0) unplugin: 1.16.1 - unplugin-vue-router: 0.10.9(rollup@3.29.5)(vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)) + unplugin-vue-router: 0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3)) unstorage: 1.17.0(@netlify/blobs@9.1.2)(db0@0.3.2)(ioredis@5.7.0) untyped: 1.5.2 - vue: 3.5.13(typescript@5.8.3) + vue: 3.5.21(typescript@5.8.3) vue-bundle-renderer: 2.1.2 vue-devtools-stub: 0.1.0 - vue-router: 4.5.0(vue@3.5.13(typescript@5.8.3)) + vue-router: 4.5.0(vue@3.5.21(typescript@5.8.3)) optionalDependencies: '@parcel/watcher': 2.5.1 '@types/node': 22.10.5 @@ -25182,18 +25574,18 @@ snapshots: - vue-tsc - xml2js - nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): + nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) - '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) - '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) + '@nuxt/devtools': 1.7.0(rollup@3.29.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2)) + '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@3.29.5) + '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@3.29.5) '@nuxt/telemetry': 2.6.6(magicast@0.3.5) - '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)) + '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2)) '@unhead/dom': 1.11.20 '@unhead/shared': 1.11.20 '@unhead/ssr': 1.11.20 - '@unhead/vue': 1.11.20(vue@3.5.13(typescript@5.8.3)) + '@unhead/vue': 1.11.20(vue@3.5.21(typescript@5.9.2)) '@vue/shared': 3.5.20 acorn: 8.14.0 c12: 2.0.1(magicast@0.3.5) @@ -25212,7 +25604,7 @@ snapshots: h3: 1.15.4 hookable: 5.5.3 ignore: 6.0.2 - impound: 0.2.2(rollup@4.50.0) + impound: 0.2.2(rollup@3.29.5) jiti: 2.5.1 klona: 2.0.6 knitwork: 1.2.0 @@ -25239,15 +25631,15 @@ snapshots: unctx: 2.4.1 unenv: 1.10.0 unhead: 1.11.20 - unimport: 3.14.6(rollup@4.50.0) + unimport: 3.14.6(rollup@3.29.5) unplugin: 1.16.1 - unplugin-vue-router: 0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)) + unplugin-vue-router: 0.10.9(rollup@3.29.5)(vue-router@4.5.0(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)) unstorage: 1.17.0(@netlify/blobs@9.1.2)(db0@0.3.2)(ioredis@5.7.0) untyped: 1.5.2 - vue: 3.5.13(typescript@5.8.3) + vue: 3.5.21(typescript@5.9.2) vue-bundle-renderer: 2.1.2 vue-devtools-stub: 0.1.0 - vue-router: 4.5.0(vue@3.5.13(typescript@5.8.3)) + vue-router: 4.5.0(vue@3.5.21(typescript@5.9.2)) optionalDependencies: '@parcel/watcher': 2.5.1 '@types/node': 22.10.5 @@ -25303,18 +25695,18 @@ snapshots: - vue-tsc - xml2js - nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): + nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2)) + '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2)) '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) '@nuxt/telemetry': 2.6.6(magicast@0.3.5) - '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)) + '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2)) '@unhead/dom': 1.11.20 '@unhead/shared': 1.11.20 '@unhead/ssr': 1.11.20 - '@unhead/vue': 1.11.20(vue@3.5.13(typescript@5.9.2)) + '@unhead/vue': 1.11.20(vue@3.5.21(typescript@5.9.2)) '@vue/shared': 3.5.20 acorn: 8.14.0 c12: 2.0.1(magicast@0.3.5) @@ -25362,13 +25754,13 @@ snapshots: unhead: 1.11.20 unimport: 3.14.6(rollup@4.50.0) unplugin: 1.16.1 - unplugin-vue-router: 0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2)) + unplugin-vue-router: 0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)) unstorage: 1.17.0(@netlify/blobs@9.1.2)(db0@0.3.2)(ioredis@5.7.0) untyped: 1.5.2 - vue: 3.5.13(typescript@5.9.2) + vue: 3.5.21(typescript@5.9.2) vue-bundle-renderer: 2.1.2 vue-devtools-stub: 0.1.0 - vue-router: 4.5.0(vue@3.5.13(typescript@5.9.2)) + vue-router: 4.5.0(vue@3.5.21(typescript@5.9.2)) optionalDependencies: '@parcel/watcher': 2.5.1 '@types/node': 22.10.5 @@ -26547,11 +26939,11 @@ snapshots: dependencies: glob: 7.2.3 - rollup-plugin-dts@6.1.1(rollup@3.29.5)(typescript@5.8.3): + rollup-plugin-dts@6.1.1(rollup@3.29.5)(typescript@5.9.2): dependencies: magic-string: 0.30.18 rollup: 3.29.5 - typescript: 5.8.3 + typescript: 5.9.2 optionalDependencies: '@babel/code-frame': 7.27.1 @@ -27416,6 +27808,34 @@ snapshots: tabbable@6.2.0: {} + tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 2.1.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.4.41 + postcss-import: 15.1.0(postcss@8.4.41) + postcss-js: 4.0.1(postcss@8.4.41) + postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)) + postcss-nested: 6.2.0(postcss@8.4.41) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + optional: true + tailwindcss@3.4.9(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)): dependencies: '@alloc/quick-lru': 5.2.0 @@ -27555,6 +27975,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tinypool@1.1.1: {} tinyrainbow@2.0.0: {} @@ -27644,9 +28069,9 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - tsconfck@3.1.6(typescript@5.8.3): + tsconfck@3.1.6(typescript@5.9.2): optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 tsconfig-paths@3.15.0: dependencies: @@ -27812,7 +28237,7 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - unbuild@2.0.0(sass@1.85.0)(typescript@5.8.3): + unbuild@2.0.0(sass@1.85.0)(typescript@5.9.2): dependencies: '@rollup/plugin-alias': 5.1.1(rollup@3.29.5) '@rollup/plugin-commonjs': 25.0.8(rollup@3.29.5) @@ -27829,17 +28254,17 @@ snapshots: hookable: 5.5.3 jiti: 1.21.7 magic-string: 0.30.18 - mkdist: 1.6.0(sass@1.85.0)(typescript@5.8.3) + mkdist: 1.6.0(sass@1.85.0)(typescript@5.9.2) mlly: 1.7.4 pathe: 1.1.2 pkg-types: 1.3.1 pretty-bytes: 6.1.1 rollup: 3.29.5 - rollup-plugin-dts: 6.1.1(rollup@3.29.5)(typescript@5.8.3) + rollup-plugin-dts: 6.1.1(rollup@3.29.5)(typescript@5.9.2) scule: 1.3.0 untyped: 1.5.2 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.2 transitivePeerDependencies: - sass - supports-color @@ -28032,11 +28457,11 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.3 - unplugin-vue-router@0.10.9(rollup@3.29.5)(vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)): + unplugin-vue-router@0.10.9(rollup@3.29.5)(vue-router@4.5.0(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)): dependencies: '@babel/types': 7.28.2 '@rollup/pluginutils': 5.2.0(rollup@3.29.5) - '@vue-macros/common': 1.16.1(vue@3.5.13(typescript@5.8.3)) + '@vue-macros/common': 1.16.1(vue@3.5.21(typescript@5.9.2)) ast-walker-scope: 0.6.2 chokidar: 3.6.0 fast-glob: 3.3.3 @@ -28049,16 +28474,16 @@ snapshots: unplugin: 2.0.0-beta.1 yaml: 2.8.0 optionalDependencies: - vue-router: 4.5.0(vue@3.5.13(typescript@5.8.3)) + vue-router: 4.5.0(vue@3.5.21(typescript@5.9.2)) transitivePeerDependencies: - rollup - vue - unplugin-vue-router@0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)): + unplugin-vue-router@0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)): dependencies: '@babel/types': 7.28.2 '@rollup/pluginutils': 5.2.0(rollup@4.50.0) - '@vue-macros/common': 1.16.1(vue@3.5.13(typescript@5.8.3)) + '@vue-macros/common': 1.16.1(vue@3.5.21(typescript@5.9.2)) ast-walker-scope: 0.6.2 chokidar: 3.6.0 fast-glob: 3.3.3 @@ -28071,16 +28496,16 @@ snapshots: unplugin: 2.0.0-beta.1 yaml: 2.8.0 optionalDependencies: - vue-router: 4.5.0(vue@3.5.13(typescript@5.8.3)) + vue-router: 4.5.0(vue@3.5.13(typescript@5.9.2)) transitivePeerDependencies: - rollup - vue - unplugin-vue-router@0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2)): + unplugin-vue-router@0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3)): dependencies: '@babel/types': 7.28.2 '@rollup/pluginutils': 5.2.0(rollup@4.50.0) - '@vue-macros/common': 1.16.1(vue@3.5.13(typescript@5.9.2)) + '@vue-macros/common': 1.16.1(vue@3.5.21(typescript@5.8.3)) ast-walker-scope: 0.6.2 chokidar: 3.6.0 fast-glob: 3.3.3 @@ -28093,7 +28518,7 @@ snapshots: unplugin: 2.0.0-beta.1 yaml: 2.8.0 optionalDependencies: - vue-router: 4.5.0(vue@3.5.13(typescript@5.9.2)) + vue-router: 4.5.0(vue@3.5.21(typescript@5.8.3)) transitivePeerDependencies: - rollup - vue @@ -28269,6 +28694,10 @@ snapshots: dependencies: vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + vite-hot-client@0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): + dependencies: + vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + vite-hot-client@2.1.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): dependencies: vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) @@ -28355,7 +28784,7 @@ snapshots: optionator: 0.9.4 typescript: 5.8.3 - vite-plugin-checker@0.8.0(eslint@9.17.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)): + vite-plugin-checker@0.8.0(eslint@9.35.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)): dependencies: '@babel/code-frame': 7.27.1 ansi-escapes: 4.3.2 @@ -28373,7 +28802,7 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 optionalDependencies: - eslint: 9.17.0(jiti@2.5.1) + eslint: 9.35.0(jiti@2.5.1) optionator: 0.9.4 typescript: 5.9.2 @@ -28413,6 +28842,24 @@ snapshots: - rollup - supports-color + vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): + dependencies: + '@antfu/utils': 0.7.10 + '@rollup/pluginutils': 5.2.0(rollup@4.50.0) + debug: 4.4.1 + error-stack-parser-es: 0.1.5 + fs-extra: 11.3.1 + open: 10.1.2 + perfect-debounce: 1.0.0 + picocolors: 1.1.1 + sirv: 3.0.1 + vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + optionalDependencies: + '@nuxt/kit': 3.15.4(magicast@0.3.5) + transitivePeerDependencies: + - rollup + - supports-color + vite-plugin-vue-devtools@7.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)): dependencies: '@vue/devtools-core': 7.7.7(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) @@ -28429,6 +28876,22 @@ snapshots: - supports-color - vue + vite-plugin-vue-devtools@7.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)): + dependencies: + '@vue/devtools-core': 7.7.7(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) + '@vue/devtools-kit': 7.7.7 + '@vue/devtools-shared': 7.7.7 + execa: 9.6.0 + sirv: 3.0.1 + vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + vite-plugin-vue-inspector: 5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + transitivePeerDependencies: + - '@nuxt/kit' + - rollup + - supports-color + - vue + vite-plugin-vue-inspector@5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): dependencies: '@babel/core': 7.28.3 @@ -28444,6 +28907,21 @@ snapshots: transitivePeerDependencies: - supports-color + vite-plugin-vue-inspector@5.3.2(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) + '@vue/compiler-dom': 3.5.20 + kolorist: 1.8.0 + magic-string: 0.30.18 + vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) + transitivePeerDependencies: + - supports-color + vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1): dependencies: esbuild: 0.21.5 @@ -28459,7 +28937,7 @@ snapshots: vite@6.1.0(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0): dependencies: esbuild: 0.24.2 - postcss: 8.5.2 + postcss: 8.5.6 rollup: 4.31.0 optionalDependencies: '@types/node': 22.10.5 @@ -28472,7 +28950,7 @@ snapshots: vite@6.2.7(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0): dependencies: - esbuild: 0.25.4 + esbuild: 0.25.9 postcss: 8.5.6 rollup: 4.31.0 optionalDependencies: @@ -28491,7 +28969,7 @@ snapshots: picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.50.0 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 22.10.5 fsevents: 2.3.3 @@ -28501,14 +28979,31 @@ snapshots: terser: 5.43.1 yaml: 2.8.0 - vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0): + vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0): dependencies: esbuild: 0.25.9 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.50.0 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.10.5 + fsevents: 2.3.3 + jiti: 2.5.1 + less: 4.2.2 + sass: 1.85.0 + terser: 5.43.1 + yaml: 2.8.0 + + vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0): + dependencies: + esbuild: 0.25.9 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.50.0 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 22.10.5 fsevents: 2.3.3 @@ -28518,14 +29013,14 @@ snapshots: terser: 5.39.0 yaml: 2.8.0 - vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0): + vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0): dependencies: esbuild: 0.25.9 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.50.0 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 22.10.5 fsevents: 2.3.3 @@ -28605,9 +29100,9 @@ snapshots: - universal-cookie - yaml - vitest-environment-nuxt@1.0.1(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0): + vitest-environment-nuxt@1.0.1(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0): dependencies: - '@nuxt/test-utils': 3.17.2(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0) + '@nuxt/test-utils': 3.17.2(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0) transitivePeerDependencies: - '@cucumber/cucumber' - '@jest/globals' @@ -28692,7 +29187,7 @@ snapshots: std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) @@ -28776,6 +29271,16 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.13(typescript@5.9.2) + vue-router@4.5.0(vue@3.5.21(typescript@5.8.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.21(typescript@5.8.3) + + vue-router@4.5.0(vue@3.5.21(typescript@5.9.2)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.21(typescript@5.9.2) + vue-tsc@2.2.0(typescript@5.8.3): dependencies: '@volar/typescript': 2.4.23 @@ -28818,6 +29323,26 @@ snapshots: optionalDependencies: typescript: 5.9.2 + vue@3.5.21(typescript@5.8.3): + dependencies: + '@vue/compiler-dom': 3.5.21 + '@vue/compiler-sfc': 3.5.21 + '@vue/runtime-dom': 3.5.21 + '@vue/server-renderer': 3.5.21(vue@3.5.21(typescript@5.8.3)) + '@vue/shared': 3.5.21 + optionalDependencies: + typescript: 5.8.3 + + vue@3.5.21(typescript@5.9.2): + dependencies: + '@vue/compiler-dom': 3.5.21 + '@vue/compiler-sfc': 3.5.21 + '@vue/runtime-dom': 3.5.21 + '@vue/server-renderer': 3.5.21(vue@3.5.21(typescript@5.9.2)) + '@vue/shared': 3.5.21 + optionalDependencies: + typescript: 5.9.2 + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 From 1aa9c2b126837d6ec9695ebfd2b099fbd7a437dd Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 17:10:08 +0700 Subject: [PATCH 09/26] test(openapi-ts-tests): add `client-ofetch` to clients matrix & snapshots --- .../main/scripts/gen-ofetch.mjs | 133 ++ .../openapi-ts-tests/main/test/3.1.x.test.ts | 15 + .../base-url-false/client.gen.ts | 16 + .../base-url-false/client/client.gen.ts | 239 ++ .../base-url-false/client/index.ts | 25 + .../base-url-false/client/types.gen.ts | 300 +++ .../base-url-false/client/utils.gen.ts | 527 +++++ .../base-url-false/core/auth.gen.ts | 42 + .../base-url-false/core/bodySerializer.gen.ts | 92 + .../base-url-false/core/params.gen.ts | 153 ++ .../base-url-false/core/pathSerializer.gen.ts | 181 ++ .../core/serverSentEvents.gen.ts | 264 +++ .../base-url-false/core/types.gen.ts | 118 + .../base-url-false/core/utils.gen.ts | 143 ++ .../client-ofetch/base-url-false/index.ts | 3 + .../client-ofetch/base-url-false/types.gen.ts | 2065 +++++++++++++++++ .../base-url-number/client.gen.ts | 18 + .../base-url-number/client/client.gen.ts | 239 ++ .../base-url-number/client/index.ts | 25 + .../base-url-number/client/types.gen.ts | 300 +++ .../base-url-number/client/utils.gen.ts | 527 +++++ .../base-url-number/core/auth.gen.ts | 42 + .../core/bodySerializer.gen.ts | 92 + .../base-url-number/core/params.gen.ts | 153 ++ .../core/pathSerializer.gen.ts | 181 ++ .../core/serverSentEvents.gen.ts | 264 +++ .../base-url-number/core/types.gen.ts | 118 + .../base-url-number/core/utils.gen.ts | 143 ++ .../client-ofetch/base-url-number/index.ts | 3 + .../base-url-number/types.gen.ts | 2065 +++++++++++++++++ .../base-url-strict/client.gen.ts | 18 + .../base-url-strict/client/client.gen.ts | 239 ++ .../base-url-strict/client/index.ts | 25 + .../base-url-strict/client/types.gen.ts | 300 +++ .../base-url-strict/client/utils.gen.ts | 527 +++++ .../base-url-strict/core/auth.gen.ts | 42 + .../core/bodySerializer.gen.ts | 92 + .../base-url-strict/core/params.gen.ts | 153 ++ .../core/pathSerializer.gen.ts | 181 ++ .../core/serverSentEvents.gen.ts | 264 +++ .../base-url-strict/core/types.gen.ts | 118 + .../base-url-strict/core/utils.gen.ts | 143 ++ .../client-ofetch/base-url-strict/index.ts | 3 + .../base-url-strict/types.gen.ts | 2065 +++++++++++++++++ .../base-url-string/client.gen.ts | 18 + .../base-url-string/client/client.gen.ts | 239 ++ .../base-url-string/client/index.ts | 25 + .../base-url-string/client/types.gen.ts | 300 +++ .../base-url-string/client/utils.gen.ts | 527 +++++ .../base-url-string/core/auth.gen.ts | 42 + .../core/bodySerializer.gen.ts | 92 + .../base-url-string/core/params.gen.ts | 153 ++ .../core/pathSerializer.gen.ts | 181 ++ .../core/serverSentEvents.gen.ts | 264 +++ .../base-url-string/core/types.gen.ts | 118 + .../base-url-string/core/utils.gen.ts | 143 ++ .../client-ofetch/base-url-string/index.ts | 3 + .../base-url-string/types.gen.ts | 2065 +++++++++++++++++ .../client-ofetch/clean-false/client.gen.ts | 18 + .../clean-false/client/client.gen.ts | 239 ++ .../client-ofetch/clean-false/client/index.ts | 25 + .../clean-false/client/types.gen.ts | 300 +++ .../clean-false/client/utils.gen.ts | 527 +++++ .../clean-false/core/auth.gen.ts | 42 + .../clean-false/core/bodySerializer.gen.ts | 92 + .../clean-false/core/params.gen.ts | 153 ++ .../clean-false/core/pathSerializer.gen.ts | 181 ++ .../clean-false/core/serverSentEvents.gen.ts | 264 +++ .../clean-false/core/types.gen.ts | 118 + .../clean-false/core/utils.gen.ts | 143 ++ .../client-ofetch/clean-false/index.ts | 4 + .../client-ofetch/clean-false/sdk.gen.ts | 409 ++++ .../client-ofetch/clean-false/types.gen.ts | 2065 +++++++++++++++++ .../client-ofetch/default/client.gen.ts | 18 + .../default/client/client.gen.ts | 239 ++ .../client-ofetch/default/client/index.ts | 25 + .../client-ofetch/default/client/types.gen.ts | 300 +++ .../client-ofetch/default/client/utils.gen.ts | 527 +++++ .../client-ofetch/default/core/auth.gen.ts | 42 + .../default/core/bodySerializer.gen.ts | 92 + .../client-ofetch/default/core/params.gen.ts | 153 ++ .../default/core/pathSerializer.gen.ts | 181 ++ .../default/core/serverSentEvents.gen.ts | 264 +++ .../client-ofetch/default/core/types.gen.ts | 118 + .../client-ofetch/default/core/utils.gen.ts | 143 ++ .../@hey-api/client-ofetch/default/index.ts | 4 + .../@hey-api/client-ofetch/default/sdk.gen.ts | 409 ++++ .../client-ofetch/default/types.gen.ts | 2065 +++++++++++++++++ .../sdk-client-optional/client.gen.ts | 18 + .../sdk-client-optional/client/client.gen.ts | 239 ++ .../sdk-client-optional/client/index.ts | 25 + .../sdk-client-optional/client/types.gen.ts | 300 +++ .../sdk-client-optional/client/utils.gen.ts | 527 +++++ .../sdk-client-optional/core/auth.gen.ts | 42 + .../core/bodySerializer.gen.ts | 92 + .../sdk-client-optional/core/params.gen.ts | 153 ++ .../core/pathSerializer.gen.ts | 181 ++ .../core/serverSentEvents.gen.ts | 264 +++ .../sdk-client-optional/core/types.gen.ts | 118 + .../sdk-client-optional/core/utils.gen.ts | 143 ++ .../sdk-client-optional/index.ts | 4 + .../sdk-client-optional/sdk.gen.ts | 409 ++++ .../sdk-client-optional/types.gen.ts | 2065 +++++++++++++++++ .../sdk-client-required/client.gen.ts | 18 + .../sdk-client-required/client/client.gen.ts | 239 ++ .../sdk-client-required/client/index.ts | 25 + .../sdk-client-required/client/types.gen.ts | 300 +++ .../sdk-client-required/client/utils.gen.ts | 527 +++++ .../sdk-client-required/core/auth.gen.ts | 42 + .../core/bodySerializer.gen.ts | 92 + .../sdk-client-required/core/params.gen.ts | 153 ++ .../core/pathSerializer.gen.ts | 181 ++ .../core/serverSentEvents.gen.ts | 264 +++ .../sdk-client-required/core/types.gen.ts | 118 + .../sdk-client-required/core/utils.gen.ts | 143 ++ .../sdk-client-required/index.ts | 4 + .../sdk-client-required/sdk.gen.ts | 408 ++++ .../sdk-client-required/types.gen.ts | 2065 +++++++++++++++++ .../tsconfig-nodenext-sdk/client.gen.ts | 18 + .../client/client.gen.ts | 239 ++ .../tsconfig-nodenext-sdk/client/index.ts | 25 + .../tsconfig-nodenext-sdk/client/types.gen.ts | 300 +++ .../tsconfig-nodenext-sdk/client/utils.gen.ts | 527 +++++ .../tsconfig-nodenext-sdk/core/auth.gen.ts | 42 + .../core/bodySerializer.gen.ts | 92 + .../tsconfig-nodenext-sdk/core/params.gen.ts | 153 ++ .../core/pathSerializer.gen.ts | 181 ++ .../core/serverSentEvents.gen.ts | 264 +++ .../tsconfig-nodenext-sdk/core/types.gen.ts | 118 + .../tsconfig-nodenext-sdk/core/utils.gen.ts | 143 ++ .../tsconfig-nodenext-sdk/index.ts | 4 + .../tsconfig-nodenext-sdk/sdk.gen.ts | 409 ++++ .../tsconfig-nodenext-sdk/types.gen.ts | 2065 +++++++++++++++++ .../3.1.x/sse-ofetch/client.gen.ts | 16 + .../3.1.x/sse-ofetch/client/client.gen.ts | 239 ++ .../3.1.x/sse-ofetch/client/index.ts | 25 + .../3.1.x/sse-ofetch/client/types.gen.ts | 300 +++ .../3.1.x/sse-ofetch/client/utils.gen.ts | 527 +++++ .../3.1.x/sse-ofetch/core/auth.gen.ts | 42 + .../sse-ofetch/core/bodySerializer.gen.ts | 92 + .../3.1.x/sse-ofetch/core/params.gen.ts | 153 ++ .../sse-ofetch/core/pathSerializer.gen.ts | 181 ++ .../sse-ofetch/core/serverSentEvents.gen.ts | 264 +++ .../3.1.x/sse-ofetch/core/types.gen.ts | 118 + .../3.1.x/sse-ofetch/core/utils.gen.ts | 143 ++ .../__snapshots__/3.1.x/sse-ofetch/index.ts | 4 + .../__snapshots__/3.1.x/sse-ofetch/sdk.gen.ts | 29 + .../3.1.x/sse-ofetch/types.gen.ts | 527 +++++ .../main/test/clients.test.ts | 1 + 149 files changed, 42386 insertions(+) create mode 100644 packages/openapi-ts-tests/main/scripts/gen-ofetch.mjs create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/auth.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/bodySerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/params.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/pathSerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/serverSentEvents.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/auth.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/bodySerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/params.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/pathSerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/serverSentEvents.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/auth.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/bodySerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/params.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/pathSerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/serverSentEvents.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/auth.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/bodySerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/params.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/pathSerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/serverSentEvents.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/auth.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/bodySerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/params.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/pathSerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/serverSentEvents.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/sdk.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/auth.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/bodySerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/params.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/pathSerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/serverSentEvents.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/sdk.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/auth.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/bodySerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/params.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/pathSerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/serverSentEvents.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/sdk.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/auth.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/bodySerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/params.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/pathSerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/serverSentEvents.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/sdk.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/auth.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/bodySerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/params.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/pathSerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/serverSentEvents.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/sdk.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/auth.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/bodySerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/params.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/pathSerializer.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/serverSentEvents.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/types.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/utils.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/index.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/sdk.gen.ts create mode 100644 packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/types.gen.ts diff --git a/packages/openapi-ts-tests/main/scripts/gen-ofetch.mjs b/packages/openapi-ts-tests/main/scripts/gen-ofetch.mjs new file mode 100644 index 000000000..a7963e1ef --- /dev/null +++ b/packages/openapi-ts-tests/main/scripts/gen-ofetch.mjs @@ -0,0 +1,133 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { createClient } from '@hey-api/openapi-ts'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const root = path.resolve(__dirname, '..'); +const getSpecsPath = () => path.resolve(root, '..', 'specs'); + +const version = '3.1.x'; +const namespace = 'clients'; +const clientName = '@hey-api/client-ofetch'; +const outputDir = path.join( + root, + 'test', + 'generated', + version, + namespace, + clientName, +); + +const createConfig = (userConfig) => ({ + ...userConfig, + input: path.join(getSpecsPath(), version, 'full.yaml'), + logs: { level: 'silent' }, + output: + typeof userConfig.output === 'string' + ? path.join(outputDir, userConfig.output) + : { + ...userConfig.output, + path: path.join(outputDir, userConfig.output.path), + }, +}); + +const scenarios = [ + { + config: createConfig({ output: 'default', plugins: [clientName] }), + description: 'default output', + }, + { + config: createConfig({ + output: 'sdk-client-optional', + plugins: [clientName, { client: true, name: '@hey-api/sdk' }], + }), + description: 'SDK with optional client option', + }, + { + config: createConfig({ + output: 'sdk-client-required', + plugins: [clientName, { client: false, name: '@hey-api/sdk' }], + }), + description: 'SDK with required client option', + }, + { + config: createConfig({ + output: 'base-url-false', + plugins: [{ baseUrl: false, name: clientName }, '@hey-api/typescript'], + }), + description: 'client without base URL', + }, + { + config: createConfig({ + output: 'base-url-number', + plugins: [{ baseUrl: 0, name: clientName }, '@hey-api/typescript'], + }), + description: 'client with numeric base URL', + }, + { + config: createConfig({ + output: 'base-url-string', + plugins: [ + { baseUrl: 'https://foo.com', name: clientName }, + '@hey-api/typescript', + ], + }), + description: 'client with custom string base URL', + }, + { + config: createConfig({ + output: 'base-url-strict', + plugins: [{ baseUrl: true, name: clientName }, '@hey-api/typescript'], + }), + description: 'client with strict base URL', + }, + { + config: createConfig({ + output: { + path: 'tsconfig-nodenext-sdk', + tsConfigPath: path.join( + root, + 'test', + 'tsconfig', + 'tsconfig.nodenext.json', + ), + }, + plugins: [clientName, '@hey-api/sdk'], + }), + description: 'SDK with NodeNext tsconfig', + }, + { + config: createConfig({ + output: { clean: false, path: 'clean-false' }, + plugins: [clientName, '@hey-api/sdk'], + }), + description: 'avoid appending extension multiple times | twice', + }, +]; + +async function main() { + for (const { config, description } of scenarios) { + console.log('Generating:', description); + await createClient(config); + if (description.endsWith('twice')) { + await createClient(config); + } + } + // Generate SSE for ofetch as well (mirrors 3.1.x.test.ts scenario) + const sseOut = path.join(root, 'test', 'generated', version, 'sse-ofetch'); + await createClient({ + input: path.join(getSpecsPath(), version, 'opencode.yaml'), + logs: { level: 'silent' }, + output: sseOut, + parser: { filters: { operations: { include: ['GET /event'] } } }, + plugins: [clientName, '@hey-api/sdk'], + }); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/packages/openapi-ts-tests/main/test/3.1.x.test.ts b/packages/openapi-ts-tests/main/test/3.1.x.test.ts index 8907d7ffe..ab78014bd 100644 --- a/packages/openapi-ts-tests/main/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/main/test/3.1.x.test.ts @@ -903,6 +903,21 @@ describe(`OpenAPI ${version}`, () => { }), description: 'client with SSE (Fetch)', }, + { + config: createConfig({ + input: 'opencode.yaml', + output: 'sse-ofetch', + parser: { + filters: { + operations: { + include: ['GET /event'], + }, + }, + }, + plugins: ['@hey-api/client-ofetch', '@hey-api/sdk'], + }), + description: 'client with SSE (ofetch)', + }, { config: createConfig({ input: 'opencode.yaml', diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client.gen.ts new file mode 100644 index 000000000..fe57f118d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig()); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts new file mode 100644 index 000000000..cdc57e116 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts @@ -0,0 +1,239 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( + options as any, + ); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/index.ts new file mode 100644 index 000000000..318a84b6a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts new file mode 100644 index 000000000..e4925b81b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts new file mode 100644 index 000000000..e54e85704 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions); + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/auth.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/bodySerializer.gen.ts new file mode 100644 index 000000000..49cd8925e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/params.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..f8fd78e28 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/types.gen.ts new file mode 100644 index 000000000..643c070c9 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/utils.gen.ts new file mode 100644 index 000000000..0b5389d08 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/index.ts new file mode 100644 index 000000000..0339b6e31 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/types.gen.ts new file mode 100644 index 000000000..4c755476d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/types.gen.ts @@ -0,0 +1,2065 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * External ref to shared model (A) + */ +export type ExternalRefA = ExternalSharedExternalSharedModel; + +/** + * External ref to shared model (B) + */ +export type ExternalRefB = ExternalSharedExternalSharedModel; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CamelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆøÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = Blob | File; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | "'Single Quote'" | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = "'Single Quote'" | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: CamelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<{ + foo?: string; +} | { + bar?: string; +}>; + +export type AnyOfAnyAndNull = { + data?: unknown | null; +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<{ + foo?: string; + } | { + bar?: string; + }>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: string; +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: string | number | boolean | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: string; + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type ModelFromZendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: string | null; + /** + * This is a simple string property + */ + nullableProp2?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: string | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: true; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + /** + * Foo-Bar-Baz-Qux + */ + 'foo-bar-baz-qux'?: '3.0'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: 'Success' | 'Warning' | 'Error'; + }; + dictionaryWithEnumFromDescription?: { + [key: string]: number; + }; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: string; + }; +}; + +/** + * This is a deprecated model with a deprecated property + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: string; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: string; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ({ + kind: 'circle'; +} & ModelCircle) | ({ + kind: 'square'; +} & ModelSquare); + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: Array | Array; +}; + +export type _3eNum1Период = 'Bird' | 'Dog'; + +export type ConstValue = 'ConstValue'; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + /** + * Scopes + */ + propA?: Array<_3eNum1Период | ConstValue> | null; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: boolean | { + [key: string]: number; + }; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean; + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + age: number; + firstName: string; + lastname: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type File = { + /** + * Id + */ + readonly id?: string; + /** + * Updated at + */ + readonly updated_at?: string; + /** + * Created at + */ + readonly created_at?: string; + /** + * Mime + */ + mime: string; + /** + * File + */ + readonly file?: string; +}; + +export type Default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: 'String'; + number?: 0; + null?: null; + withType?: 'Some string'; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown | string | undefined; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: SimpleParameter; +} | { + bar: NonAsciiStringæøåÆøÅöôêÊ字符串; +}) & { + baz: number | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + string, + string + ]; + foo: 'Corge'; +}; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + number | string, + number | string, + number | string +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + number | string, + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + number | null | string, + number | null | string, + number | null | string +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + number | Import, + number | Import +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + number & string, + number & string +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (SimpleParameter | NonAsciiStringæøåÆøÅöôêÊ字符串) & { + baz: number | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type Import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<{ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: IoK8sApimachineryPkgApisMetaV1Preconditions; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: number; +}; + +export type OneOfAllOfIssue = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBoolean = { + item?: boolean; + error?: string | null; + readonly hasError?: boolean; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemString = { + item?: string | null; + error?: string | null; + readonly hasError?: boolean; +}; + +export type ExternalSharedExternalSharedModel = { + id: string; + name?: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithPropertiesWritable = { + required: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPatternWritable = { + key: string; + name: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type FileWritable = { + /** + * Mime + */ + mime: string; +}; + +export type ModelWithReadOnlyAndWriteOnlyWritable = { + foo: string; + baz: string; +}; + +export type AdditionalPropertiesUnknownIssueWritable = { + [key: string]: string | number; +}; + +export type GenericSchemaDuplicateIssue1SystemBooleanWritable = { + item?: boolean; + error?: string | null; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemStringWritable = { + item?: string | null; + error?: string | null; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type XFooBar = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleRequestBody = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleFormData = ModelWithString; + +export type ExportData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ImportData = { + body: ModelWithReadOnlyAndWriteOnlyWritable | ModelWithArrayReadOnlyAndWriteOnly; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type ImportResponses = { + /** + * Success + */ + 200: ModelFromZendesk; + /** + * Default success response + */ + default: ModelWithReadOnlyAndWriteOnly; +}; + +export type ImportResponse = ImportResponses[keyof ImportResponses]; + +export type FooWowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type FooWowResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ApiVVersionODataControllerCountData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple/$count'; +}; + +export type ApiVVersionODataControllerCountResponses = { + /** + * Success + */ + 200: ModelFromZendesk; +}; + +export type ApiVVersionODataControllerCountResponse = ApiVVersionODataControllerCountResponses[keyof ApiVVersionODataControllerCountResponses]; + +export type GetApiVbyApiVersionSimpleOperationData = { + body?: never; + path: { + /** + * foo in method + */ + foo_param: string; + }; + query?: never; + url: '/api/v{api-version}/simple:operation'; +}; + +export type GetApiVbyApiVersionSimpleOperationErrors = { + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type GetApiVbyApiVersionSimpleOperationError = GetApiVbyApiVersionSimpleOperationErrors[keyof GetApiVbyApiVersionSimpleOperationErrors]; + +export type GetApiVbyApiVersionSimpleOperationResponses = { + /** + * Response is a simple number + */ + 200: number; +}; + +export type GetApiVbyApiVersionSimpleOperationResponse = GetApiVbyApiVersionSimpleOperationResponses[keyof GetApiVbyApiVersionSimpleOperationResponses]; + +export type DeleteCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type GetCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type HeadCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type OptionsCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PatchCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PostCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PutCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type DeleteFooData3 = { + body?: never; + headers: { + /** + * Parameter with illegal characters + */ + 'x-Foo-Bar': ModelWithString; + }; + path: { + /** + * foo in method + */ + foo_param: string; + /** + * bar in method + */ + BarParam: string; + }; + query?: never; + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}'; +}; + +export type CallWithDescriptionsData = { + body?: never; + path?: never; + query?: { + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: string; + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: string; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: string; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: string; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: string; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: string; + }; + url: '/api/v{api-version}/descriptions'; +}; + +export type DeprecatedCallData = { + body?: never; + headers: { + /** + * This parameter is deprecated + * @deprecated + */ + parameter: DeprecatedModel | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/parameters/deprecated'; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the body + */ + body: { + [key: string]: unknown; + } | null; + headers: { + /** + * This is the parameter that goes into the header + */ + parameterHeader: string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + parameterPath: string | null; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + foo_ref_enum?: ModelWithNestedArrayEnumsDataFoo; + foo_all_of_enum: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the query params + */ + cursor: string | null; + }; + url: '/api/v{api-version}/parameters/{parameterPath}'; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter that goes into the body + */ + body: ModelWithString | null; + headers: { + /** + * This is the parameter that goes into the request header + */ + 'parameter.header': string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + 'parameter.path.1'?: string; + /** + * This is the parameter that goes into the path + */ + 'parameter-path-2'?: string; + /** + * This is the parameter that goes into the path + */ + 'PARAMETER-PATH-3'?: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + /** + * This is the parameter with a reserved keyword + */ + default?: string; + /** + * This is the parameter that goes into the request query params + */ + 'parameter-query': string | null; + }; + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}'; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is a required parameter + */ + body: ModelWithOneOfEnum; + path?: never; + query?: { + /** + * This is an optional parameter + */ + page?: number; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + body?: { + offset?: number | null; + }; + path?: never; + query: { + /** + * This is a required parameter + */ + parameter: Pageable; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type PostCallWithOptionalParamResponse = PostCallWithOptionalParamResponses[keyof PostCallWithOptionalParamResponses]; + +export type PostApiVbyApiVersionRequestBodyData = { + /** + * A reusable request body + */ + body?: SimpleRequestBody; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/requestBody'; +}; + +export type PostApiVbyApiVersionFormDataData = { + /** + * A reusable request body + */ + body?: SimpleFormData; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/formData'; +}; + +export type CallWithDefaultParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string with default value + */ + parameterString?: string | null; + /** + * This is a simple number with default value + */ + parameterNumber?: number | null; + /** + * This is a simple boolean with default value + */ + parameterBoolean?: boolean | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: ModelWithString | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallWithDefaultOptionalParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallToTestOrderOfParamsData = { + body?: never; + path?: never; + query: { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: string | null; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: string | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type DuplicateNameData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName2Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName3Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName4Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type CallWithNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no-content'; +}; + +export type CallWithNoContentResponseResponses = { + /** + * Success + */ + 204: void; +}; + +export type CallWithNoContentResponseResponse = CallWithNoContentResponseResponses[keyof CallWithNoContentResponseResponses]; + +export type CallWithResponseAndNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/response-and-no-content'; +}; + +export type CallWithResponseAndNoContentResponseResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type CallWithResponseAndNoContentResponseResponse = CallWithResponseAndNoContentResponseResponses[keyof CallWithResponseAndNoContentResponseResponses]; + +export type DummyAData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/a'; +}; + +export type DummyAResponses = { + 200: _400; +}; + +export type DummyAResponse = DummyAResponses[keyof DummyAResponses]; + +export type DummyBData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/b'; +}; + +export type DummyBResponses = { + /** + * Success + */ + 204: void; +}; + +export type DummyBResponse = DummyBResponses[keyof DummyBResponses]; + +export type CallWithResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponseResponses = { + default: Import; +}; + +export type CallWithResponseResponse = CallWithResponseResponses[keyof CallWithResponseResponses]; + +export type CallWithDuplicateResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithDuplicateResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for 4XX errors + */ + '4XX': DictionaryWithArray; + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type CallWithDuplicateResponsesError = CallWithDuplicateResponsesErrors[keyof CallWithDuplicateResponsesErrors]; + +export type CallWithDuplicateResponsesResponses = { + /** + * Message for 200 response + */ + 200: ModelWithBoolean & ModelWithInteger; + /** + * Message for 201 response + */ + 201: ModelWithString; + /** + * Message for 202 response + */ + 202: ModelWithString; +}; + +export type CallWithDuplicateResponsesResponse = CallWithDuplicateResponsesResponses[keyof CallWithDuplicateResponsesResponses]; + +export type CallWithResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for default response + */ + default: ModelWithStringError; +}; + +export type CallWithResponsesError = CallWithResponsesErrors[keyof CallWithResponsesErrors]; + +export type CallWithResponsesResponses = { + /** + * Message for 200 response + */ + 200: { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + }; + /** + * Message for 201 response + */ + 201: ModelThatExtends; + /** + * Message for 202 response + */ + 202: ModelThatExtendsExtends; +}; + +export type CallWithResponsesResponse = CallWithResponsesResponses[keyof CallWithResponsesResponses]; + +export type CollectionFormatData = { + body?: never; + path?: never; + query: { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCSV: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySSV: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTSV: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + }; + url: '/api/v{api-version}/collectionFormat'; +}; + +export type TypesData = { + body?: never; + path?: { + /** + * This is a number parameter + */ + id?: number; + }; + query: { + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is a string parameter + */ + parameterString: string | null; + /** + * This is a boolean parameter + */ + parameterBoolean: boolean | null; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + }; + url: '/api/v{api-version}/types'; +}; + +export type TypesResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Response is a simple string + */ + 201: string; + /** + * Response is a simple boolean + */ + 202: boolean; + /** + * Response is a simple object + */ + 203: { + [key: string]: unknown; + }; +}; + +export type TypesResponse = TypesResponses[keyof TypesResponses]; + +export type UploadFileData = { + body: Blob | File; + path: { + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query?: never; + url: '/api/v{api-version}/upload'; +}; + +export type UploadFileResponses = { + 200: boolean; +}; + +export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; + +export type FileResponseData = { + body?: never; + path: { + id: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/file/{id}'; +}; + +export type FileResponseResponses = { + /** + * Success + */ + 200: Blob | File; +}; + +export type FileResponseResponse = FileResponseResponses[keyof FileResponseResponses]; + +export type ComplexTypesData = { + body?: never; + path?: never; + query: { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; + }; + url: '/api/v{api-version}/complex'; +}; + +export type ComplexTypesErrors = { + /** + * 400 `server` error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type ComplexTypesResponses = { + /** + * Successful response + */ + 200: Array; +}; + +export type ComplexTypesResponse = ComplexTypesResponses[keyof ComplexTypesResponses]; + +export type MultipartResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type MultipartResponseResponses = { + /** + * OK + */ + 200: { + file?: Blob | File; + metadata?: { + foo?: string; + bar?: string; + }; + }; +}; + +export type MultipartResponseResponse = MultipartResponseResponses[keyof MultipartResponseResponses]; + +export type MultipartRequestData = { + body?: { + content?: Blob | File; + data?: ModelWithString | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type ComplexParamsData = { + body?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; + path: { + id: number; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/complex/{id}'; +}; + +export type ComplexParamsResponses = { + /** + * Success + */ + 200: ModelWithString; +}; + +export type ComplexParamsResponse = ComplexParamsResponses[keyof ComplexParamsResponses]; + +export type CallWithResultFromHeaderData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/header'; +}; + +export type CallWithResultFromHeaderErrors = { + /** + * 400 server error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type CallWithResultFromHeaderResponses = { + /** + * Successful response + */ + 200: unknown; +}; + +export type TestErrorCodeData = { + body?: never; + path?: never; + query: { + /** + * Status code to return + */ + status: number; + }; + url: '/api/v{api-version}/error'; +}; + +export type TestErrorCodeErrors = { + /** + * Custom message: Internal Server Error + */ + 500: unknown; + /** + * Custom message: Not Implemented + */ + 501: unknown; + /** + * Custom message: Bad Gateway + */ + 502: unknown; + /** + * Custom message: Service Unavailable + */ + 503: unknown; +}; + +export type TestErrorCodeResponses = { + /** + * Custom message: Successful response + */ + 200: unknown; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + body?: never; + path?: never; + query: { + /** + * Dummy input param + */ + nonAsciiParamæøåÆØÅöôêÊ: number; + }; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Responses = { + /** + * Successful response + */ + 200: Array; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = NonAsciiæøåÆøÅöôêÊ字符串Responses[keyof NonAsciiæøåÆøÅöôêÊ字符串Responses]; + +export type PutWithFormUrlEncodedData = { + body: ArrayWithStrings; + path?: never; + query?: never; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client.gen.ts new file mode 100644 index 000000000..950198e01 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'http://localhost:3000/base' +})); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts new file mode 100644 index 000000000..cdc57e116 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts @@ -0,0 +1,239 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( + options as any, + ); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/index.ts new file mode 100644 index 000000000..318a84b6a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts new file mode 100644 index 000000000..e4925b81b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts new file mode 100644 index 000000000..e54e85704 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions); + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/auth.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/bodySerializer.gen.ts new file mode 100644 index 000000000..49cd8925e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/params.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..f8fd78e28 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/types.gen.ts new file mode 100644 index 000000000..643c070c9 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/utils.gen.ts new file mode 100644 index 000000000..0b5389d08 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/index.ts new file mode 100644 index 000000000..0339b6e31 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/types.gen.ts new file mode 100644 index 000000000..4c755476d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/types.gen.ts @@ -0,0 +1,2065 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * External ref to shared model (A) + */ +export type ExternalRefA = ExternalSharedExternalSharedModel; + +/** + * External ref to shared model (B) + */ +export type ExternalRefB = ExternalSharedExternalSharedModel; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CamelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆøÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = Blob | File; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | "'Single Quote'" | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = "'Single Quote'" | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: CamelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<{ + foo?: string; +} | { + bar?: string; +}>; + +export type AnyOfAnyAndNull = { + data?: unknown | null; +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<{ + foo?: string; + } | { + bar?: string; + }>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: string; +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: string | number | boolean | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: string; + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type ModelFromZendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: string | null; + /** + * This is a simple string property + */ + nullableProp2?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: string | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: true; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + /** + * Foo-Bar-Baz-Qux + */ + 'foo-bar-baz-qux'?: '3.0'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: 'Success' | 'Warning' | 'Error'; + }; + dictionaryWithEnumFromDescription?: { + [key: string]: number; + }; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: string; + }; +}; + +/** + * This is a deprecated model with a deprecated property + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: string; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: string; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ({ + kind: 'circle'; +} & ModelCircle) | ({ + kind: 'square'; +} & ModelSquare); + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: Array | Array; +}; + +export type _3eNum1Период = 'Bird' | 'Dog'; + +export type ConstValue = 'ConstValue'; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + /** + * Scopes + */ + propA?: Array<_3eNum1Период | ConstValue> | null; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: boolean | { + [key: string]: number; + }; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean; + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + age: number; + firstName: string; + lastname: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type File = { + /** + * Id + */ + readonly id?: string; + /** + * Updated at + */ + readonly updated_at?: string; + /** + * Created at + */ + readonly created_at?: string; + /** + * Mime + */ + mime: string; + /** + * File + */ + readonly file?: string; +}; + +export type Default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: 'String'; + number?: 0; + null?: null; + withType?: 'Some string'; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown | string | undefined; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: SimpleParameter; +} | { + bar: NonAsciiStringæøåÆøÅöôêÊ字符串; +}) & { + baz: number | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + string, + string + ]; + foo: 'Corge'; +}; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + number | string, + number | string, + number | string +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + number | string, + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + number | null | string, + number | null | string, + number | null | string +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + number | Import, + number | Import +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + number & string, + number & string +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (SimpleParameter | NonAsciiStringæøåÆøÅöôêÊ字符串) & { + baz: number | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type Import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<{ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: IoK8sApimachineryPkgApisMetaV1Preconditions; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: number; +}; + +export type OneOfAllOfIssue = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBoolean = { + item?: boolean; + error?: string | null; + readonly hasError?: boolean; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemString = { + item?: string | null; + error?: string | null; + readonly hasError?: boolean; +}; + +export type ExternalSharedExternalSharedModel = { + id: string; + name?: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithPropertiesWritable = { + required: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPatternWritable = { + key: string; + name: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type FileWritable = { + /** + * Mime + */ + mime: string; +}; + +export type ModelWithReadOnlyAndWriteOnlyWritable = { + foo: string; + baz: string; +}; + +export type AdditionalPropertiesUnknownIssueWritable = { + [key: string]: string | number; +}; + +export type GenericSchemaDuplicateIssue1SystemBooleanWritable = { + item?: boolean; + error?: string | null; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemStringWritable = { + item?: string | null; + error?: string | null; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type XFooBar = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleRequestBody = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleFormData = ModelWithString; + +export type ExportData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ImportData = { + body: ModelWithReadOnlyAndWriteOnlyWritable | ModelWithArrayReadOnlyAndWriteOnly; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type ImportResponses = { + /** + * Success + */ + 200: ModelFromZendesk; + /** + * Default success response + */ + default: ModelWithReadOnlyAndWriteOnly; +}; + +export type ImportResponse = ImportResponses[keyof ImportResponses]; + +export type FooWowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type FooWowResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ApiVVersionODataControllerCountData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple/$count'; +}; + +export type ApiVVersionODataControllerCountResponses = { + /** + * Success + */ + 200: ModelFromZendesk; +}; + +export type ApiVVersionODataControllerCountResponse = ApiVVersionODataControllerCountResponses[keyof ApiVVersionODataControllerCountResponses]; + +export type GetApiVbyApiVersionSimpleOperationData = { + body?: never; + path: { + /** + * foo in method + */ + foo_param: string; + }; + query?: never; + url: '/api/v{api-version}/simple:operation'; +}; + +export type GetApiVbyApiVersionSimpleOperationErrors = { + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type GetApiVbyApiVersionSimpleOperationError = GetApiVbyApiVersionSimpleOperationErrors[keyof GetApiVbyApiVersionSimpleOperationErrors]; + +export type GetApiVbyApiVersionSimpleOperationResponses = { + /** + * Response is a simple number + */ + 200: number; +}; + +export type GetApiVbyApiVersionSimpleOperationResponse = GetApiVbyApiVersionSimpleOperationResponses[keyof GetApiVbyApiVersionSimpleOperationResponses]; + +export type DeleteCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type GetCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type HeadCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type OptionsCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PatchCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PostCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PutCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type DeleteFooData3 = { + body?: never; + headers: { + /** + * Parameter with illegal characters + */ + 'x-Foo-Bar': ModelWithString; + }; + path: { + /** + * foo in method + */ + foo_param: string; + /** + * bar in method + */ + BarParam: string; + }; + query?: never; + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}'; +}; + +export type CallWithDescriptionsData = { + body?: never; + path?: never; + query?: { + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: string; + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: string; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: string; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: string; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: string; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: string; + }; + url: '/api/v{api-version}/descriptions'; +}; + +export type DeprecatedCallData = { + body?: never; + headers: { + /** + * This parameter is deprecated + * @deprecated + */ + parameter: DeprecatedModel | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/parameters/deprecated'; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the body + */ + body: { + [key: string]: unknown; + } | null; + headers: { + /** + * This is the parameter that goes into the header + */ + parameterHeader: string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + parameterPath: string | null; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + foo_ref_enum?: ModelWithNestedArrayEnumsDataFoo; + foo_all_of_enum: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the query params + */ + cursor: string | null; + }; + url: '/api/v{api-version}/parameters/{parameterPath}'; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter that goes into the body + */ + body: ModelWithString | null; + headers: { + /** + * This is the parameter that goes into the request header + */ + 'parameter.header': string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + 'parameter.path.1'?: string; + /** + * This is the parameter that goes into the path + */ + 'parameter-path-2'?: string; + /** + * This is the parameter that goes into the path + */ + 'PARAMETER-PATH-3'?: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + /** + * This is the parameter with a reserved keyword + */ + default?: string; + /** + * This is the parameter that goes into the request query params + */ + 'parameter-query': string | null; + }; + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}'; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is a required parameter + */ + body: ModelWithOneOfEnum; + path?: never; + query?: { + /** + * This is an optional parameter + */ + page?: number; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + body?: { + offset?: number | null; + }; + path?: never; + query: { + /** + * This is a required parameter + */ + parameter: Pageable; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type PostCallWithOptionalParamResponse = PostCallWithOptionalParamResponses[keyof PostCallWithOptionalParamResponses]; + +export type PostApiVbyApiVersionRequestBodyData = { + /** + * A reusable request body + */ + body?: SimpleRequestBody; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/requestBody'; +}; + +export type PostApiVbyApiVersionFormDataData = { + /** + * A reusable request body + */ + body?: SimpleFormData; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/formData'; +}; + +export type CallWithDefaultParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string with default value + */ + parameterString?: string | null; + /** + * This is a simple number with default value + */ + parameterNumber?: number | null; + /** + * This is a simple boolean with default value + */ + parameterBoolean?: boolean | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: ModelWithString | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallWithDefaultOptionalParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallToTestOrderOfParamsData = { + body?: never; + path?: never; + query: { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: string | null; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: string | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type DuplicateNameData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName2Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName3Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName4Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type CallWithNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no-content'; +}; + +export type CallWithNoContentResponseResponses = { + /** + * Success + */ + 204: void; +}; + +export type CallWithNoContentResponseResponse = CallWithNoContentResponseResponses[keyof CallWithNoContentResponseResponses]; + +export type CallWithResponseAndNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/response-and-no-content'; +}; + +export type CallWithResponseAndNoContentResponseResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type CallWithResponseAndNoContentResponseResponse = CallWithResponseAndNoContentResponseResponses[keyof CallWithResponseAndNoContentResponseResponses]; + +export type DummyAData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/a'; +}; + +export type DummyAResponses = { + 200: _400; +}; + +export type DummyAResponse = DummyAResponses[keyof DummyAResponses]; + +export type DummyBData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/b'; +}; + +export type DummyBResponses = { + /** + * Success + */ + 204: void; +}; + +export type DummyBResponse = DummyBResponses[keyof DummyBResponses]; + +export type CallWithResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponseResponses = { + default: Import; +}; + +export type CallWithResponseResponse = CallWithResponseResponses[keyof CallWithResponseResponses]; + +export type CallWithDuplicateResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithDuplicateResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for 4XX errors + */ + '4XX': DictionaryWithArray; + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type CallWithDuplicateResponsesError = CallWithDuplicateResponsesErrors[keyof CallWithDuplicateResponsesErrors]; + +export type CallWithDuplicateResponsesResponses = { + /** + * Message for 200 response + */ + 200: ModelWithBoolean & ModelWithInteger; + /** + * Message for 201 response + */ + 201: ModelWithString; + /** + * Message for 202 response + */ + 202: ModelWithString; +}; + +export type CallWithDuplicateResponsesResponse = CallWithDuplicateResponsesResponses[keyof CallWithDuplicateResponsesResponses]; + +export type CallWithResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for default response + */ + default: ModelWithStringError; +}; + +export type CallWithResponsesError = CallWithResponsesErrors[keyof CallWithResponsesErrors]; + +export type CallWithResponsesResponses = { + /** + * Message for 200 response + */ + 200: { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + }; + /** + * Message for 201 response + */ + 201: ModelThatExtends; + /** + * Message for 202 response + */ + 202: ModelThatExtendsExtends; +}; + +export type CallWithResponsesResponse = CallWithResponsesResponses[keyof CallWithResponsesResponses]; + +export type CollectionFormatData = { + body?: never; + path?: never; + query: { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCSV: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySSV: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTSV: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + }; + url: '/api/v{api-version}/collectionFormat'; +}; + +export type TypesData = { + body?: never; + path?: { + /** + * This is a number parameter + */ + id?: number; + }; + query: { + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is a string parameter + */ + parameterString: string | null; + /** + * This is a boolean parameter + */ + parameterBoolean: boolean | null; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + }; + url: '/api/v{api-version}/types'; +}; + +export type TypesResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Response is a simple string + */ + 201: string; + /** + * Response is a simple boolean + */ + 202: boolean; + /** + * Response is a simple object + */ + 203: { + [key: string]: unknown; + }; +}; + +export type TypesResponse = TypesResponses[keyof TypesResponses]; + +export type UploadFileData = { + body: Blob | File; + path: { + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query?: never; + url: '/api/v{api-version}/upload'; +}; + +export type UploadFileResponses = { + 200: boolean; +}; + +export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; + +export type FileResponseData = { + body?: never; + path: { + id: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/file/{id}'; +}; + +export type FileResponseResponses = { + /** + * Success + */ + 200: Blob | File; +}; + +export type FileResponseResponse = FileResponseResponses[keyof FileResponseResponses]; + +export type ComplexTypesData = { + body?: never; + path?: never; + query: { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; + }; + url: '/api/v{api-version}/complex'; +}; + +export type ComplexTypesErrors = { + /** + * 400 `server` error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type ComplexTypesResponses = { + /** + * Successful response + */ + 200: Array; +}; + +export type ComplexTypesResponse = ComplexTypesResponses[keyof ComplexTypesResponses]; + +export type MultipartResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type MultipartResponseResponses = { + /** + * OK + */ + 200: { + file?: Blob | File; + metadata?: { + foo?: string; + bar?: string; + }; + }; +}; + +export type MultipartResponseResponse = MultipartResponseResponses[keyof MultipartResponseResponses]; + +export type MultipartRequestData = { + body?: { + content?: Blob | File; + data?: ModelWithString | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type ComplexParamsData = { + body?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; + path: { + id: number; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/complex/{id}'; +}; + +export type ComplexParamsResponses = { + /** + * Success + */ + 200: ModelWithString; +}; + +export type ComplexParamsResponse = ComplexParamsResponses[keyof ComplexParamsResponses]; + +export type CallWithResultFromHeaderData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/header'; +}; + +export type CallWithResultFromHeaderErrors = { + /** + * 400 server error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type CallWithResultFromHeaderResponses = { + /** + * Successful response + */ + 200: unknown; +}; + +export type TestErrorCodeData = { + body?: never; + path?: never; + query: { + /** + * Status code to return + */ + status: number; + }; + url: '/api/v{api-version}/error'; +}; + +export type TestErrorCodeErrors = { + /** + * Custom message: Internal Server Error + */ + 500: unknown; + /** + * Custom message: Not Implemented + */ + 501: unknown; + /** + * Custom message: Bad Gateway + */ + 502: unknown; + /** + * Custom message: Service Unavailable + */ + 503: unknown; +}; + +export type TestErrorCodeResponses = { + /** + * Custom message: Successful response + */ + 200: unknown; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + body?: never; + path?: never; + query: { + /** + * Dummy input param + */ + nonAsciiParamæøåÆØÅöôêÊ: number; + }; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Responses = { + /** + * Successful response + */ + 200: Array; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = NonAsciiæøåÆøÅöôêÊ字符串Responses[keyof NonAsciiæøåÆøÅöôêÊ字符串Responses]; + +export type PutWithFormUrlEncodedData = { + body: ArrayWithStrings; + path?: never; + query?: never; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client.gen.ts new file mode 100644 index 000000000..950198e01 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'http://localhost:3000/base' +})); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts new file mode 100644 index 000000000..cdc57e116 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts @@ -0,0 +1,239 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( + options as any, + ); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/index.ts new file mode 100644 index 000000000..318a84b6a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts new file mode 100644 index 000000000..e4925b81b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts new file mode 100644 index 000000000..e54e85704 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions); + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/auth.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/bodySerializer.gen.ts new file mode 100644 index 000000000..49cd8925e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/params.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..f8fd78e28 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/types.gen.ts new file mode 100644 index 000000000..643c070c9 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/utils.gen.ts new file mode 100644 index 000000000..0b5389d08 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/index.ts new file mode 100644 index 000000000..0339b6e31 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/types.gen.ts new file mode 100644 index 000000000..e671e5b13 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/types.gen.ts @@ -0,0 +1,2065 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * External ref to shared model (A) + */ +export type ExternalRefA = ExternalSharedExternalSharedModel; + +/** + * External ref to shared model (B) + */ +export type ExternalRefB = ExternalSharedExternalSharedModel; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CamelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆøÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = Blob | File; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | "'Single Quote'" | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = "'Single Quote'" | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: CamelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<{ + foo?: string; +} | { + bar?: string; +}>; + +export type AnyOfAnyAndNull = { + data?: unknown | null; +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<{ + foo?: string; + } | { + bar?: string; + }>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: string; +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: string | number | boolean | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: string; + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type ModelFromZendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: string | null; + /** + * This is a simple string property + */ + nullableProp2?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: string | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: true; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + /** + * Foo-Bar-Baz-Qux + */ + 'foo-bar-baz-qux'?: '3.0'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: 'Success' | 'Warning' | 'Error'; + }; + dictionaryWithEnumFromDescription?: { + [key: string]: number; + }; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: string; + }; +}; + +/** + * This is a deprecated model with a deprecated property + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: string; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: string; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ({ + kind: 'circle'; +} & ModelCircle) | ({ + kind: 'square'; +} & ModelSquare); + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: Array | Array; +}; + +export type _3eNum1Период = 'Bird' | 'Dog'; + +export type ConstValue = 'ConstValue'; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + /** + * Scopes + */ + propA?: Array<_3eNum1Период | ConstValue> | null; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: boolean | { + [key: string]: number; + }; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean; + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + age: number; + firstName: string; + lastname: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type File = { + /** + * Id + */ + readonly id?: string; + /** + * Updated at + */ + readonly updated_at?: string; + /** + * Created at + */ + readonly created_at?: string; + /** + * Mime + */ + mime: string; + /** + * File + */ + readonly file?: string; +}; + +export type Default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: 'String'; + number?: 0; + null?: null; + withType?: 'Some string'; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown | string | undefined; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: SimpleParameter; +} | { + bar: NonAsciiStringæøåÆøÅöôêÊ字符串; +}) & { + baz: number | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + string, + string + ]; + foo: 'Corge'; +}; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + number | string, + number | string, + number | string +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + number | string, + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + number | null | string, + number | null | string, + number | null | string +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + number | Import, + number | Import +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + number & string, + number & string +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (SimpleParameter | NonAsciiStringæøåÆøÅöôêÊ字符串) & { + baz: number | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type Import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<{ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: IoK8sApimachineryPkgApisMetaV1Preconditions; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: number; +}; + +export type OneOfAllOfIssue = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBoolean = { + item?: boolean; + error?: string | null; + readonly hasError?: boolean; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemString = { + item?: string | null; + error?: string | null; + readonly hasError?: boolean; +}; + +export type ExternalSharedExternalSharedModel = { + id: string; + name?: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithPropertiesWritable = { + required: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPatternWritable = { + key: string; + name: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type FileWritable = { + /** + * Mime + */ + mime: string; +}; + +export type ModelWithReadOnlyAndWriteOnlyWritable = { + foo: string; + baz: string; +}; + +export type AdditionalPropertiesUnknownIssueWritable = { + [key: string]: string | number; +}; + +export type GenericSchemaDuplicateIssue1SystemBooleanWritable = { + item?: boolean; + error?: string | null; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemStringWritable = { + item?: string | null; + error?: string | null; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type XFooBar = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleRequestBody = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleFormData = ModelWithString; + +export type ExportData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ImportData = { + body: ModelWithReadOnlyAndWriteOnlyWritable | ModelWithArrayReadOnlyAndWriteOnly; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type ImportResponses = { + /** + * Success + */ + 200: ModelFromZendesk; + /** + * Default success response + */ + default: ModelWithReadOnlyAndWriteOnly; +}; + +export type ImportResponse = ImportResponses[keyof ImportResponses]; + +export type FooWowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type FooWowResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ApiVVersionODataControllerCountData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple/$count'; +}; + +export type ApiVVersionODataControllerCountResponses = { + /** + * Success + */ + 200: ModelFromZendesk; +}; + +export type ApiVVersionODataControllerCountResponse = ApiVVersionODataControllerCountResponses[keyof ApiVVersionODataControllerCountResponses]; + +export type GetApiVbyApiVersionSimpleOperationData = { + body?: never; + path: { + /** + * foo in method + */ + foo_param: string; + }; + query?: never; + url: '/api/v{api-version}/simple:operation'; +}; + +export type GetApiVbyApiVersionSimpleOperationErrors = { + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type GetApiVbyApiVersionSimpleOperationError = GetApiVbyApiVersionSimpleOperationErrors[keyof GetApiVbyApiVersionSimpleOperationErrors]; + +export type GetApiVbyApiVersionSimpleOperationResponses = { + /** + * Response is a simple number + */ + 200: number; +}; + +export type GetApiVbyApiVersionSimpleOperationResponse = GetApiVbyApiVersionSimpleOperationResponses[keyof GetApiVbyApiVersionSimpleOperationResponses]; + +export type DeleteCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type GetCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type HeadCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type OptionsCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PatchCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PostCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PutCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type DeleteFooData3 = { + body?: never; + headers: { + /** + * Parameter with illegal characters + */ + 'x-Foo-Bar': ModelWithString; + }; + path: { + /** + * foo in method + */ + foo_param: string; + /** + * bar in method + */ + BarParam: string; + }; + query?: never; + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}'; +}; + +export type CallWithDescriptionsData = { + body?: never; + path?: never; + query?: { + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: string; + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: string; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: string; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: string; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: string; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: string; + }; + url: '/api/v{api-version}/descriptions'; +}; + +export type DeprecatedCallData = { + body?: never; + headers: { + /** + * This parameter is deprecated + * @deprecated + */ + parameter: DeprecatedModel | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/parameters/deprecated'; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the body + */ + body: { + [key: string]: unknown; + } | null; + headers: { + /** + * This is the parameter that goes into the header + */ + parameterHeader: string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + parameterPath: string | null; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + foo_ref_enum?: ModelWithNestedArrayEnumsDataFoo; + foo_all_of_enum: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the query params + */ + cursor: string | null; + }; + url: '/api/v{api-version}/parameters/{parameterPath}'; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter that goes into the body + */ + body: ModelWithString | null; + headers: { + /** + * This is the parameter that goes into the request header + */ + 'parameter.header': string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + 'parameter.path.1'?: string; + /** + * This is the parameter that goes into the path + */ + 'parameter-path-2'?: string; + /** + * This is the parameter that goes into the path + */ + 'PARAMETER-PATH-3'?: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + /** + * This is the parameter with a reserved keyword + */ + default?: string; + /** + * This is the parameter that goes into the request query params + */ + 'parameter-query': string | null; + }; + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}'; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is a required parameter + */ + body: ModelWithOneOfEnum; + path?: never; + query?: { + /** + * This is an optional parameter + */ + page?: number; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + body?: { + offset?: number | null; + }; + path?: never; + query: { + /** + * This is a required parameter + */ + parameter: Pageable; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type PostCallWithOptionalParamResponse = PostCallWithOptionalParamResponses[keyof PostCallWithOptionalParamResponses]; + +export type PostApiVbyApiVersionRequestBodyData = { + /** + * A reusable request body + */ + body?: SimpleRequestBody; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/requestBody'; +}; + +export type PostApiVbyApiVersionFormDataData = { + /** + * A reusable request body + */ + body?: SimpleFormData; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/formData'; +}; + +export type CallWithDefaultParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string with default value + */ + parameterString?: string | null; + /** + * This is a simple number with default value + */ + parameterNumber?: number | null; + /** + * This is a simple boolean with default value + */ + parameterBoolean?: boolean | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: ModelWithString | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallWithDefaultOptionalParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallToTestOrderOfParamsData = { + body?: never; + path?: never; + query: { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: string | null; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: string | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type DuplicateNameData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName2Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName3Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName4Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type CallWithNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no-content'; +}; + +export type CallWithNoContentResponseResponses = { + /** + * Success + */ + 204: void; +}; + +export type CallWithNoContentResponseResponse = CallWithNoContentResponseResponses[keyof CallWithNoContentResponseResponses]; + +export type CallWithResponseAndNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/response-and-no-content'; +}; + +export type CallWithResponseAndNoContentResponseResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type CallWithResponseAndNoContentResponseResponse = CallWithResponseAndNoContentResponseResponses[keyof CallWithResponseAndNoContentResponseResponses]; + +export type DummyAData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/a'; +}; + +export type DummyAResponses = { + 200: _400; +}; + +export type DummyAResponse = DummyAResponses[keyof DummyAResponses]; + +export type DummyBData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/b'; +}; + +export type DummyBResponses = { + /** + * Success + */ + 204: void; +}; + +export type DummyBResponse = DummyBResponses[keyof DummyBResponses]; + +export type CallWithResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponseResponses = { + default: Import; +}; + +export type CallWithResponseResponse = CallWithResponseResponses[keyof CallWithResponseResponses]; + +export type CallWithDuplicateResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithDuplicateResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for 4XX errors + */ + '4XX': DictionaryWithArray; + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type CallWithDuplicateResponsesError = CallWithDuplicateResponsesErrors[keyof CallWithDuplicateResponsesErrors]; + +export type CallWithDuplicateResponsesResponses = { + /** + * Message for 200 response + */ + 200: ModelWithBoolean & ModelWithInteger; + /** + * Message for 201 response + */ + 201: ModelWithString; + /** + * Message for 202 response + */ + 202: ModelWithString; +}; + +export type CallWithDuplicateResponsesResponse = CallWithDuplicateResponsesResponses[keyof CallWithDuplicateResponsesResponses]; + +export type CallWithResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for default response + */ + default: ModelWithStringError; +}; + +export type CallWithResponsesError = CallWithResponsesErrors[keyof CallWithResponsesErrors]; + +export type CallWithResponsesResponses = { + /** + * Message for 200 response + */ + 200: { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + }; + /** + * Message for 201 response + */ + 201: ModelThatExtends; + /** + * Message for 202 response + */ + 202: ModelThatExtendsExtends; +}; + +export type CallWithResponsesResponse = CallWithResponsesResponses[keyof CallWithResponsesResponses]; + +export type CollectionFormatData = { + body?: never; + path?: never; + query: { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCSV: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySSV: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTSV: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + }; + url: '/api/v{api-version}/collectionFormat'; +}; + +export type TypesData = { + body?: never; + path?: { + /** + * This is a number parameter + */ + id?: number; + }; + query: { + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is a string parameter + */ + parameterString: string | null; + /** + * This is a boolean parameter + */ + parameterBoolean: boolean | null; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + }; + url: '/api/v{api-version}/types'; +}; + +export type TypesResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Response is a simple string + */ + 201: string; + /** + * Response is a simple boolean + */ + 202: boolean; + /** + * Response is a simple object + */ + 203: { + [key: string]: unknown; + }; +}; + +export type TypesResponse = TypesResponses[keyof TypesResponses]; + +export type UploadFileData = { + body: Blob | File; + path: { + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query?: never; + url: '/api/v{api-version}/upload'; +}; + +export type UploadFileResponses = { + 200: boolean; +}; + +export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; + +export type FileResponseData = { + body?: never; + path: { + id: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/file/{id}'; +}; + +export type FileResponseResponses = { + /** + * Success + */ + 200: Blob | File; +}; + +export type FileResponseResponse = FileResponseResponses[keyof FileResponseResponses]; + +export type ComplexTypesData = { + body?: never; + path?: never; + query: { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; + }; + url: '/api/v{api-version}/complex'; +}; + +export type ComplexTypesErrors = { + /** + * 400 `server` error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type ComplexTypesResponses = { + /** + * Successful response + */ + 200: Array; +}; + +export type ComplexTypesResponse = ComplexTypesResponses[keyof ComplexTypesResponses]; + +export type MultipartResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type MultipartResponseResponses = { + /** + * OK + */ + 200: { + file?: Blob | File; + metadata?: { + foo?: string; + bar?: string; + }; + }; +}; + +export type MultipartResponseResponse = MultipartResponseResponses[keyof MultipartResponseResponses]; + +export type MultipartRequestData = { + body?: { + content?: Blob | File; + data?: ModelWithString | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type ComplexParamsData = { + body?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; + path: { + id: number; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/complex/{id}'; +}; + +export type ComplexParamsResponses = { + /** + * Success + */ + 200: ModelWithString; +}; + +export type ComplexParamsResponse = ComplexParamsResponses[keyof ComplexParamsResponses]; + +export type CallWithResultFromHeaderData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/header'; +}; + +export type CallWithResultFromHeaderErrors = { + /** + * 400 server error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type CallWithResultFromHeaderResponses = { + /** + * Successful response + */ + 200: unknown; +}; + +export type TestErrorCodeData = { + body?: never; + path?: never; + query: { + /** + * Status code to return + */ + status: number; + }; + url: '/api/v{api-version}/error'; +}; + +export type TestErrorCodeErrors = { + /** + * Custom message: Internal Server Error + */ + 500: unknown; + /** + * Custom message: Not Implemented + */ + 501: unknown; + /** + * Custom message: Bad Gateway + */ + 502: unknown; + /** + * Custom message: Service Unavailable + */ + 503: unknown; +}; + +export type TestErrorCodeResponses = { + /** + * Custom message: Successful response + */ + 200: unknown; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + body?: never; + path?: never; + query: { + /** + * Dummy input param + */ + nonAsciiParamæøåÆØÅöôêÊ: number; + }; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Responses = { + /** + * Successful response + */ + 200: Array; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = NonAsciiæøåÆøÅöôêÊ字符串Responses[keyof NonAsciiæøåÆøÅöôêÊ字符串Responses]; + +export type PutWithFormUrlEncodedData = { + body: ArrayWithStrings; + path?: never; + query?: never; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base'; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client.gen.ts new file mode 100644 index 000000000..ccd37e81c --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'https://foo.com' +})); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts new file mode 100644 index 000000000..cdc57e116 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts @@ -0,0 +1,239 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( + options as any, + ); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/index.ts new file mode 100644 index 000000000..318a84b6a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts new file mode 100644 index 000000000..e4925b81b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts new file mode 100644 index 000000000..e54e85704 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions); + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/auth.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/bodySerializer.gen.ts new file mode 100644 index 000000000..49cd8925e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/params.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..f8fd78e28 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/types.gen.ts new file mode 100644 index 000000000..643c070c9 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/utils.gen.ts new file mode 100644 index 000000000..0b5389d08 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/index.ts new file mode 100644 index 000000000..0339b6e31 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/types.gen.ts new file mode 100644 index 000000000..4c755476d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/types.gen.ts @@ -0,0 +1,2065 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * External ref to shared model (A) + */ +export type ExternalRefA = ExternalSharedExternalSharedModel; + +/** + * External ref to shared model (B) + */ +export type ExternalRefB = ExternalSharedExternalSharedModel; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CamelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆøÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = Blob | File; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | "'Single Quote'" | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = "'Single Quote'" | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: CamelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<{ + foo?: string; +} | { + bar?: string; +}>; + +export type AnyOfAnyAndNull = { + data?: unknown | null; +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<{ + foo?: string; + } | { + bar?: string; + }>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: string; +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: string | number | boolean | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: string; + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type ModelFromZendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: string | null; + /** + * This is a simple string property + */ + nullableProp2?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: string | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: true; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + /** + * Foo-Bar-Baz-Qux + */ + 'foo-bar-baz-qux'?: '3.0'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: 'Success' | 'Warning' | 'Error'; + }; + dictionaryWithEnumFromDescription?: { + [key: string]: number; + }; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: string; + }; +}; + +/** + * This is a deprecated model with a deprecated property + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: string; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: string; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ({ + kind: 'circle'; +} & ModelCircle) | ({ + kind: 'square'; +} & ModelSquare); + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: Array | Array; +}; + +export type _3eNum1Период = 'Bird' | 'Dog'; + +export type ConstValue = 'ConstValue'; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + /** + * Scopes + */ + propA?: Array<_3eNum1Период | ConstValue> | null; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: boolean | { + [key: string]: number; + }; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean; + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + age: number; + firstName: string; + lastname: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type File = { + /** + * Id + */ + readonly id?: string; + /** + * Updated at + */ + readonly updated_at?: string; + /** + * Created at + */ + readonly created_at?: string; + /** + * Mime + */ + mime: string; + /** + * File + */ + readonly file?: string; +}; + +export type Default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: 'String'; + number?: 0; + null?: null; + withType?: 'Some string'; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown | string | undefined; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: SimpleParameter; +} | { + bar: NonAsciiStringæøåÆøÅöôêÊ字符串; +}) & { + baz: number | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + string, + string + ]; + foo: 'Corge'; +}; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + number | string, + number | string, + number | string +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + number | string, + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + number | null | string, + number | null | string, + number | null | string +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + number | Import, + number | Import +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + number & string, + number & string +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (SimpleParameter | NonAsciiStringæøåÆøÅöôêÊ字符串) & { + baz: number | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type Import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<{ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: IoK8sApimachineryPkgApisMetaV1Preconditions; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: number; +}; + +export type OneOfAllOfIssue = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBoolean = { + item?: boolean; + error?: string | null; + readonly hasError?: boolean; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemString = { + item?: string | null; + error?: string | null; + readonly hasError?: boolean; +}; + +export type ExternalSharedExternalSharedModel = { + id: string; + name?: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithPropertiesWritable = { + required: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPatternWritable = { + key: string; + name: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type FileWritable = { + /** + * Mime + */ + mime: string; +}; + +export type ModelWithReadOnlyAndWriteOnlyWritable = { + foo: string; + baz: string; +}; + +export type AdditionalPropertiesUnknownIssueWritable = { + [key: string]: string | number; +}; + +export type GenericSchemaDuplicateIssue1SystemBooleanWritable = { + item?: boolean; + error?: string | null; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemStringWritable = { + item?: string | null; + error?: string | null; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type XFooBar = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleRequestBody = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleFormData = ModelWithString; + +export type ExportData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ImportData = { + body: ModelWithReadOnlyAndWriteOnlyWritable | ModelWithArrayReadOnlyAndWriteOnly; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type ImportResponses = { + /** + * Success + */ + 200: ModelFromZendesk; + /** + * Default success response + */ + default: ModelWithReadOnlyAndWriteOnly; +}; + +export type ImportResponse = ImportResponses[keyof ImportResponses]; + +export type FooWowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type FooWowResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ApiVVersionODataControllerCountData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple/$count'; +}; + +export type ApiVVersionODataControllerCountResponses = { + /** + * Success + */ + 200: ModelFromZendesk; +}; + +export type ApiVVersionODataControllerCountResponse = ApiVVersionODataControllerCountResponses[keyof ApiVVersionODataControllerCountResponses]; + +export type GetApiVbyApiVersionSimpleOperationData = { + body?: never; + path: { + /** + * foo in method + */ + foo_param: string; + }; + query?: never; + url: '/api/v{api-version}/simple:operation'; +}; + +export type GetApiVbyApiVersionSimpleOperationErrors = { + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type GetApiVbyApiVersionSimpleOperationError = GetApiVbyApiVersionSimpleOperationErrors[keyof GetApiVbyApiVersionSimpleOperationErrors]; + +export type GetApiVbyApiVersionSimpleOperationResponses = { + /** + * Response is a simple number + */ + 200: number; +}; + +export type GetApiVbyApiVersionSimpleOperationResponse = GetApiVbyApiVersionSimpleOperationResponses[keyof GetApiVbyApiVersionSimpleOperationResponses]; + +export type DeleteCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type GetCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type HeadCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type OptionsCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PatchCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PostCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PutCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type DeleteFooData3 = { + body?: never; + headers: { + /** + * Parameter with illegal characters + */ + 'x-Foo-Bar': ModelWithString; + }; + path: { + /** + * foo in method + */ + foo_param: string; + /** + * bar in method + */ + BarParam: string; + }; + query?: never; + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}'; +}; + +export type CallWithDescriptionsData = { + body?: never; + path?: never; + query?: { + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: string; + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: string; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: string; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: string; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: string; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: string; + }; + url: '/api/v{api-version}/descriptions'; +}; + +export type DeprecatedCallData = { + body?: never; + headers: { + /** + * This parameter is deprecated + * @deprecated + */ + parameter: DeprecatedModel | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/parameters/deprecated'; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the body + */ + body: { + [key: string]: unknown; + } | null; + headers: { + /** + * This is the parameter that goes into the header + */ + parameterHeader: string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + parameterPath: string | null; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + foo_ref_enum?: ModelWithNestedArrayEnumsDataFoo; + foo_all_of_enum: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the query params + */ + cursor: string | null; + }; + url: '/api/v{api-version}/parameters/{parameterPath}'; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter that goes into the body + */ + body: ModelWithString | null; + headers: { + /** + * This is the parameter that goes into the request header + */ + 'parameter.header': string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + 'parameter.path.1'?: string; + /** + * This is the parameter that goes into the path + */ + 'parameter-path-2'?: string; + /** + * This is the parameter that goes into the path + */ + 'PARAMETER-PATH-3'?: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + /** + * This is the parameter with a reserved keyword + */ + default?: string; + /** + * This is the parameter that goes into the request query params + */ + 'parameter-query': string | null; + }; + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}'; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is a required parameter + */ + body: ModelWithOneOfEnum; + path?: never; + query?: { + /** + * This is an optional parameter + */ + page?: number; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + body?: { + offset?: number | null; + }; + path?: never; + query: { + /** + * This is a required parameter + */ + parameter: Pageable; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type PostCallWithOptionalParamResponse = PostCallWithOptionalParamResponses[keyof PostCallWithOptionalParamResponses]; + +export type PostApiVbyApiVersionRequestBodyData = { + /** + * A reusable request body + */ + body?: SimpleRequestBody; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/requestBody'; +}; + +export type PostApiVbyApiVersionFormDataData = { + /** + * A reusable request body + */ + body?: SimpleFormData; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/formData'; +}; + +export type CallWithDefaultParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string with default value + */ + parameterString?: string | null; + /** + * This is a simple number with default value + */ + parameterNumber?: number | null; + /** + * This is a simple boolean with default value + */ + parameterBoolean?: boolean | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: ModelWithString | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallWithDefaultOptionalParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallToTestOrderOfParamsData = { + body?: never; + path?: never; + query: { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: string | null; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: string | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type DuplicateNameData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName2Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName3Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName4Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type CallWithNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no-content'; +}; + +export type CallWithNoContentResponseResponses = { + /** + * Success + */ + 204: void; +}; + +export type CallWithNoContentResponseResponse = CallWithNoContentResponseResponses[keyof CallWithNoContentResponseResponses]; + +export type CallWithResponseAndNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/response-and-no-content'; +}; + +export type CallWithResponseAndNoContentResponseResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type CallWithResponseAndNoContentResponseResponse = CallWithResponseAndNoContentResponseResponses[keyof CallWithResponseAndNoContentResponseResponses]; + +export type DummyAData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/a'; +}; + +export type DummyAResponses = { + 200: _400; +}; + +export type DummyAResponse = DummyAResponses[keyof DummyAResponses]; + +export type DummyBData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/b'; +}; + +export type DummyBResponses = { + /** + * Success + */ + 204: void; +}; + +export type DummyBResponse = DummyBResponses[keyof DummyBResponses]; + +export type CallWithResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponseResponses = { + default: Import; +}; + +export type CallWithResponseResponse = CallWithResponseResponses[keyof CallWithResponseResponses]; + +export type CallWithDuplicateResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithDuplicateResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for 4XX errors + */ + '4XX': DictionaryWithArray; + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type CallWithDuplicateResponsesError = CallWithDuplicateResponsesErrors[keyof CallWithDuplicateResponsesErrors]; + +export type CallWithDuplicateResponsesResponses = { + /** + * Message for 200 response + */ + 200: ModelWithBoolean & ModelWithInteger; + /** + * Message for 201 response + */ + 201: ModelWithString; + /** + * Message for 202 response + */ + 202: ModelWithString; +}; + +export type CallWithDuplicateResponsesResponse = CallWithDuplicateResponsesResponses[keyof CallWithDuplicateResponsesResponses]; + +export type CallWithResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for default response + */ + default: ModelWithStringError; +}; + +export type CallWithResponsesError = CallWithResponsesErrors[keyof CallWithResponsesErrors]; + +export type CallWithResponsesResponses = { + /** + * Message for 200 response + */ + 200: { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + }; + /** + * Message for 201 response + */ + 201: ModelThatExtends; + /** + * Message for 202 response + */ + 202: ModelThatExtendsExtends; +}; + +export type CallWithResponsesResponse = CallWithResponsesResponses[keyof CallWithResponsesResponses]; + +export type CollectionFormatData = { + body?: never; + path?: never; + query: { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCSV: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySSV: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTSV: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + }; + url: '/api/v{api-version}/collectionFormat'; +}; + +export type TypesData = { + body?: never; + path?: { + /** + * This is a number parameter + */ + id?: number; + }; + query: { + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is a string parameter + */ + parameterString: string | null; + /** + * This is a boolean parameter + */ + parameterBoolean: boolean | null; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + }; + url: '/api/v{api-version}/types'; +}; + +export type TypesResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Response is a simple string + */ + 201: string; + /** + * Response is a simple boolean + */ + 202: boolean; + /** + * Response is a simple object + */ + 203: { + [key: string]: unknown; + }; +}; + +export type TypesResponse = TypesResponses[keyof TypesResponses]; + +export type UploadFileData = { + body: Blob | File; + path: { + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query?: never; + url: '/api/v{api-version}/upload'; +}; + +export type UploadFileResponses = { + 200: boolean; +}; + +export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; + +export type FileResponseData = { + body?: never; + path: { + id: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/file/{id}'; +}; + +export type FileResponseResponses = { + /** + * Success + */ + 200: Blob | File; +}; + +export type FileResponseResponse = FileResponseResponses[keyof FileResponseResponses]; + +export type ComplexTypesData = { + body?: never; + path?: never; + query: { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; + }; + url: '/api/v{api-version}/complex'; +}; + +export type ComplexTypesErrors = { + /** + * 400 `server` error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type ComplexTypesResponses = { + /** + * Successful response + */ + 200: Array; +}; + +export type ComplexTypesResponse = ComplexTypesResponses[keyof ComplexTypesResponses]; + +export type MultipartResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type MultipartResponseResponses = { + /** + * OK + */ + 200: { + file?: Blob | File; + metadata?: { + foo?: string; + bar?: string; + }; + }; +}; + +export type MultipartResponseResponse = MultipartResponseResponses[keyof MultipartResponseResponses]; + +export type MultipartRequestData = { + body?: { + content?: Blob | File; + data?: ModelWithString | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type ComplexParamsData = { + body?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; + path: { + id: number; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/complex/{id}'; +}; + +export type ComplexParamsResponses = { + /** + * Success + */ + 200: ModelWithString; +}; + +export type ComplexParamsResponse = ComplexParamsResponses[keyof ComplexParamsResponses]; + +export type CallWithResultFromHeaderData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/header'; +}; + +export type CallWithResultFromHeaderErrors = { + /** + * 400 server error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type CallWithResultFromHeaderResponses = { + /** + * Successful response + */ + 200: unknown; +}; + +export type TestErrorCodeData = { + body?: never; + path?: never; + query: { + /** + * Status code to return + */ + status: number; + }; + url: '/api/v{api-version}/error'; +}; + +export type TestErrorCodeErrors = { + /** + * Custom message: Internal Server Error + */ + 500: unknown; + /** + * Custom message: Not Implemented + */ + 501: unknown; + /** + * Custom message: Bad Gateway + */ + 502: unknown; + /** + * Custom message: Service Unavailable + */ + 503: unknown; +}; + +export type TestErrorCodeResponses = { + /** + * Custom message: Successful response + */ + 200: unknown; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + body?: never; + path?: never; + query: { + /** + * Dummy input param + */ + nonAsciiParamæøåÆØÅöôêÊ: number; + }; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Responses = { + /** + * Successful response + */ + 200: Array; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = NonAsciiæøåÆøÅöôêÊ字符串Responses[keyof NonAsciiæøåÆøÅöôêÊ字符串Responses]; + +export type PutWithFormUrlEncodedData = { + body: ArrayWithStrings; + path?: never; + query?: never; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client.gen.ts new file mode 100644 index 000000000..950198e01 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'http://localhost:3000/base' +})); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts new file mode 100644 index 000000000..cdc57e116 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts @@ -0,0 +1,239 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( + options as any, + ); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/index.ts new file mode 100644 index 000000000..318a84b6a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts new file mode 100644 index 000000000..e4925b81b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts new file mode 100644 index 000000000..e54e85704 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions); + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/auth.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/bodySerializer.gen.ts new file mode 100644 index 000000000..49cd8925e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/params.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..f8fd78e28 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/types.gen.ts new file mode 100644 index 000000000..643c070c9 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/utils.gen.ts new file mode 100644 index 000000000..0b5389d08 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/index.ts new file mode 100644 index 000000000..cc646f13a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen'; +export * from './sdk.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/sdk.gen.ts new file mode 100644 index 000000000..17622c66b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/sdk.gen.ts @@ -0,0 +1,409 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type Options as ClientOptions, type Client, type TDataShape, formDataBodySerializer, urlSearchParamsBodySerializer } from './client'; +import type { ExportData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, ImportData, ImportResponses, FooWowData, FooWowResponses, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationResponses, GetApiVbyApiVersionSimpleOperationErrors, DeleteCallWithoutParametersAndResponseData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, OptionsCallWithoutParametersAndResponseData, PatchCallWithoutParametersAndResponseData, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, DeleteFooData3 as DeleteFooData, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostApiVbyApiVersionRequestBodyData, PostApiVbyApiVersionFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, DuplicateNameData, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, DummyAData, DummyAResponses, DummyBData, DummyBResponses, CallWithResponseData, CallWithResponseResponses, CallWithDuplicateResponsesData, CallWithDuplicateResponsesResponses, CallWithDuplicateResponsesErrors, CallWithResponsesData, CallWithResponsesResponses, CallWithResponsesErrors, CollectionFormatData, TypesData, TypesResponses, UploadFileData, UploadFileResponses, FileResponseData, FileResponseResponses, ComplexTypesData, ComplexTypesResponses, ComplexTypesErrors, MultipartResponseData, MultipartResponseResponses, MultipartRequestData, ComplexParamsData, ComplexParamsResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderResponses, CallWithResultFromHeaderErrors, TestErrorCodeData, TestErrorCodeResponses, TestErrorCodeErrors, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, PutWithFormUrlEncodedData } from './types.gen'; +import { client } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +export const export_ = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const patchApiVbyApiVersionNoTag = (options?: Options) => { + return (options?.client ?? client).patch({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const import_ = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/no+tag', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const fooWow = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const apiVVersionODataControllerCount = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/simple/$count', + ...options + }); +}; + +export const getApiVbyApiVersionSimpleOperation = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/simple:operation', + ...options + }); +}; + +export const deleteCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).delete({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const getCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const headCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).head({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const optionsCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).options({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const patchCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).patch({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const postCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const putCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const deleteFoo = (options: Options) => { + return (options.client ?? client).delete({ + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', + ...options + }); +}; + +export const callWithDescriptions = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/descriptions', + ...options + }); +}; + +/** + * @deprecated + */ +export const deprecatedCall = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/deprecated', + ...options + }); +}; + +export const callWithParameters = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/{parameterPath}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const callWithWeirdParameterNames = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const getCallWithOptionalParam = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/parameters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postCallWithOptionalParam = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postApiVbyApiVersionRequestBody = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/requestBody', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +export const postApiVbyApiVersionFormData = (options?: Options) => { + return (options?.client ?? client).post({ + ...formDataBodySerializer, + url: '/api/v{api-version}/formData', + ...options, + headers: { + 'Content-Type': null, + ...options?.headers + } + }); +}; + +export const callWithDefaultParameters = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const callWithDefaultOptionalParameters = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const callToTestOrderOfParams = (options: Options) => { + return (options.client ?? client).put({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const duplicateName = (options?: Options) => { + return (options?.client ?? client).delete({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName2 = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName3 = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName4 = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const callWithNoContentResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/no-content', + ...options + }); +}; + +export const callWithResponseAndNoContentResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + ...options + }); +}; + +export const dummyA = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/a', + ...options + }); +}; + +export const dummyB = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/b', + ...options + }); +}; + +export const callWithResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const callWithDuplicateResponses = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const callWithResponses = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const collectionFormat = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/collectionFormat', + ...options + }); +}; + +export const types = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/types', + ...options + }); +}; + +export const uploadFile = (options: Options) => { + return (options.client ?? client).post({ + ...urlSearchParamsBodySerializer, + url: '/api/v{api-version}/upload', + ...options, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...options.headers + } + }); +}; + +export const fileResponse = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/file/{id}', + ...options + }); +}; + +export const complexTypes = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/complex', + ...options + }); +}; + +export const multipartResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multipart', + ...options + }); +}; + +export const multipartRequest = (options?: Options) => { + return (options?.client ?? client).post({ + ...formDataBodySerializer, + url: '/api/v{api-version}/multipart', + ...options, + headers: { + 'Content-Type': null, + ...options?.headers + } + }); +}; + +export const complexParams = (options: Options) => { + return (options.client ?? client).put({ + url: '/api/v{api-version}/complex/{id}', + ...options, + headers: { + 'Content-Type': 'application/json-patch+json', + ...options.headers + } + }); +}; + +export const callWithResultFromHeader = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/header', + ...options + }); +}; + +export const testErrorCode = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/error', + ...options + }); +}; + +export const nonAsciiæøåÆøÅöôêÊ字符串 = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + ...options + }); +}; + +/** + * Login User + */ +export const putWithFormUrlEncoded = (options: Options) => { + return (options.client ?? client).put({ + ...urlSearchParamsBodySerializer, + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + ...options, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...options.headers + } + }); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/types.gen.ts new file mode 100644 index 000000000..4c755476d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/types.gen.ts @@ -0,0 +1,2065 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * External ref to shared model (A) + */ +export type ExternalRefA = ExternalSharedExternalSharedModel; + +/** + * External ref to shared model (B) + */ +export type ExternalRefB = ExternalSharedExternalSharedModel; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CamelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆøÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = Blob | File; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | "'Single Quote'" | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = "'Single Quote'" | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: CamelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<{ + foo?: string; +} | { + bar?: string; +}>; + +export type AnyOfAnyAndNull = { + data?: unknown | null; +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<{ + foo?: string; + } | { + bar?: string; + }>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: string; +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: string | number | boolean | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: string; + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type ModelFromZendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: string | null; + /** + * This is a simple string property + */ + nullableProp2?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: string | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: true; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + /** + * Foo-Bar-Baz-Qux + */ + 'foo-bar-baz-qux'?: '3.0'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: 'Success' | 'Warning' | 'Error'; + }; + dictionaryWithEnumFromDescription?: { + [key: string]: number; + }; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: string; + }; +}; + +/** + * This is a deprecated model with a deprecated property + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: string; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: string; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ({ + kind: 'circle'; +} & ModelCircle) | ({ + kind: 'square'; +} & ModelSquare); + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: Array | Array; +}; + +export type _3eNum1Период = 'Bird' | 'Dog'; + +export type ConstValue = 'ConstValue'; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + /** + * Scopes + */ + propA?: Array<_3eNum1Период | ConstValue> | null; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: boolean | { + [key: string]: number; + }; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean; + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + age: number; + firstName: string; + lastname: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type File = { + /** + * Id + */ + readonly id?: string; + /** + * Updated at + */ + readonly updated_at?: string; + /** + * Created at + */ + readonly created_at?: string; + /** + * Mime + */ + mime: string; + /** + * File + */ + readonly file?: string; +}; + +export type Default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: 'String'; + number?: 0; + null?: null; + withType?: 'Some string'; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown | string | undefined; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: SimpleParameter; +} | { + bar: NonAsciiStringæøåÆøÅöôêÊ字符串; +}) & { + baz: number | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + string, + string + ]; + foo: 'Corge'; +}; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + number | string, + number | string, + number | string +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + number | string, + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + number | null | string, + number | null | string, + number | null | string +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + number | Import, + number | Import +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + number & string, + number & string +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (SimpleParameter | NonAsciiStringæøåÆøÅöôêÊ字符串) & { + baz: number | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type Import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<{ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: IoK8sApimachineryPkgApisMetaV1Preconditions; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: number; +}; + +export type OneOfAllOfIssue = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBoolean = { + item?: boolean; + error?: string | null; + readonly hasError?: boolean; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemString = { + item?: string | null; + error?: string | null; + readonly hasError?: boolean; +}; + +export type ExternalSharedExternalSharedModel = { + id: string; + name?: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithPropertiesWritable = { + required: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPatternWritable = { + key: string; + name: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type FileWritable = { + /** + * Mime + */ + mime: string; +}; + +export type ModelWithReadOnlyAndWriteOnlyWritable = { + foo: string; + baz: string; +}; + +export type AdditionalPropertiesUnknownIssueWritable = { + [key: string]: string | number; +}; + +export type GenericSchemaDuplicateIssue1SystemBooleanWritable = { + item?: boolean; + error?: string | null; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemStringWritable = { + item?: string | null; + error?: string | null; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type XFooBar = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleRequestBody = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleFormData = ModelWithString; + +export type ExportData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ImportData = { + body: ModelWithReadOnlyAndWriteOnlyWritable | ModelWithArrayReadOnlyAndWriteOnly; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type ImportResponses = { + /** + * Success + */ + 200: ModelFromZendesk; + /** + * Default success response + */ + default: ModelWithReadOnlyAndWriteOnly; +}; + +export type ImportResponse = ImportResponses[keyof ImportResponses]; + +export type FooWowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type FooWowResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ApiVVersionODataControllerCountData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple/$count'; +}; + +export type ApiVVersionODataControllerCountResponses = { + /** + * Success + */ + 200: ModelFromZendesk; +}; + +export type ApiVVersionODataControllerCountResponse = ApiVVersionODataControllerCountResponses[keyof ApiVVersionODataControllerCountResponses]; + +export type GetApiVbyApiVersionSimpleOperationData = { + body?: never; + path: { + /** + * foo in method + */ + foo_param: string; + }; + query?: never; + url: '/api/v{api-version}/simple:operation'; +}; + +export type GetApiVbyApiVersionSimpleOperationErrors = { + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type GetApiVbyApiVersionSimpleOperationError = GetApiVbyApiVersionSimpleOperationErrors[keyof GetApiVbyApiVersionSimpleOperationErrors]; + +export type GetApiVbyApiVersionSimpleOperationResponses = { + /** + * Response is a simple number + */ + 200: number; +}; + +export type GetApiVbyApiVersionSimpleOperationResponse = GetApiVbyApiVersionSimpleOperationResponses[keyof GetApiVbyApiVersionSimpleOperationResponses]; + +export type DeleteCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type GetCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type HeadCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type OptionsCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PatchCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PostCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PutCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type DeleteFooData3 = { + body?: never; + headers: { + /** + * Parameter with illegal characters + */ + 'x-Foo-Bar': ModelWithString; + }; + path: { + /** + * foo in method + */ + foo_param: string; + /** + * bar in method + */ + BarParam: string; + }; + query?: never; + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}'; +}; + +export type CallWithDescriptionsData = { + body?: never; + path?: never; + query?: { + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: string; + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: string; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: string; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: string; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: string; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: string; + }; + url: '/api/v{api-version}/descriptions'; +}; + +export type DeprecatedCallData = { + body?: never; + headers: { + /** + * This parameter is deprecated + * @deprecated + */ + parameter: DeprecatedModel | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/parameters/deprecated'; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the body + */ + body: { + [key: string]: unknown; + } | null; + headers: { + /** + * This is the parameter that goes into the header + */ + parameterHeader: string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + parameterPath: string | null; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + foo_ref_enum?: ModelWithNestedArrayEnumsDataFoo; + foo_all_of_enum: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the query params + */ + cursor: string | null; + }; + url: '/api/v{api-version}/parameters/{parameterPath}'; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter that goes into the body + */ + body: ModelWithString | null; + headers: { + /** + * This is the parameter that goes into the request header + */ + 'parameter.header': string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + 'parameter.path.1'?: string; + /** + * This is the parameter that goes into the path + */ + 'parameter-path-2'?: string; + /** + * This is the parameter that goes into the path + */ + 'PARAMETER-PATH-3'?: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + /** + * This is the parameter with a reserved keyword + */ + default?: string; + /** + * This is the parameter that goes into the request query params + */ + 'parameter-query': string | null; + }; + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}'; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is a required parameter + */ + body: ModelWithOneOfEnum; + path?: never; + query?: { + /** + * This is an optional parameter + */ + page?: number; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + body?: { + offset?: number | null; + }; + path?: never; + query: { + /** + * This is a required parameter + */ + parameter: Pageable; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type PostCallWithOptionalParamResponse = PostCallWithOptionalParamResponses[keyof PostCallWithOptionalParamResponses]; + +export type PostApiVbyApiVersionRequestBodyData = { + /** + * A reusable request body + */ + body?: SimpleRequestBody; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/requestBody'; +}; + +export type PostApiVbyApiVersionFormDataData = { + /** + * A reusable request body + */ + body?: SimpleFormData; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/formData'; +}; + +export type CallWithDefaultParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string with default value + */ + parameterString?: string | null; + /** + * This is a simple number with default value + */ + parameterNumber?: number | null; + /** + * This is a simple boolean with default value + */ + parameterBoolean?: boolean | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: ModelWithString | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallWithDefaultOptionalParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallToTestOrderOfParamsData = { + body?: never; + path?: never; + query: { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: string | null; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: string | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type DuplicateNameData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName2Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName3Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName4Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type CallWithNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no-content'; +}; + +export type CallWithNoContentResponseResponses = { + /** + * Success + */ + 204: void; +}; + +export type CallWithNoContentResponseResponse = CallWithNoContentResponseResponses[keyof CallWithNoContentResponseResponses]; + +export type CallWithResponseAndNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/response-and-no-content'; +}; + +export type CallWithResponseAndNoContentResponseResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type CallWithResponseAndNoContentResponseResponse = CallWithResponseAndNoContentResponseResponses[keyof CallWithResponseAndNoContentResponseResponses]; + +export type DummyAData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/a'; +}; + +export type DummyAResponses = { + 200: _400; +}; + +export type DummyAResponse = DummyAResponses[keyof DummyAResponses]; + +export type DummyBData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/b'; +}; + +export type DummyBResponses = { + /** + * Success + */ + 204: void; +}; + +export type DummyBResponse = DummyBResponses[keyof DummyBResponses]; + +export type CallWithResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponseResponses = { + default: Import; +}; + +export type CallWithResponseResponse = CallWithResponseResponses[keyof CallWithResponseResponses]; + +export type CallWithDuplicateResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithDuplicateResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for 4XX errors + */ + '4XX': DictionaryWithArray; + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type CallWithDuplicateResponsesError = CallWithDuplicateResponsesErrors[keyof CallWithDuplicateResponsesErrors]; + +export type CallWithDuplicateResponsesResponses = { + /** + * Message for 200 response + */ + 200: ModelWithBoolean & ModelWithInteger; + /** + * Message for 201 response + */ + 201: ModelWithString; + /** + * Message for 202 response + */ + 202: ModelWithString; +}; + +export type CallWithDuplicateResponsesResponse = CallWithDuplicateResponsesResponses[keyof CallWithDuplicateResponsesResponses]; + +export type CallWithResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for default response + */ + default: ModelWithStringError; +}; + +export type CallWithResponsesError = CallWithResponsesErrors[keyof CallWithResponsesErrors]; + +export type CallWithResponsesResponses = { + /** + * Message for 200 response + */ + 200: { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + }; + /** + * Message for 201 response + */ + 201: ModelThatExtends; + /** + * Message for 202 response + */ + 202: ModelThatExtendsExtends; +}; + +export type CallWithResponsesResponse = CallWithResponsesResponses[keyof CallWithResponsesResponses]; + +export type CollectionFormatData = { + body?: never; + path?: never; + query: { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCSV: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySSV: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTSV: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + }; + url: '/api/v{api-version}/collectionFormat'; +}; + +export type TypesData = { + body?: never; + path?: { + /** + * This is a number parameter + */ + id?: number; + }; + query: { + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is a string parameter + */ + parameterString: string | null; + /** + * This is a boolean parameter + */ + parameterBoolean: boolean | null; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + }; + url: '/api/v{api-version}/types'; +}; + +export type TypesResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Response is a simple string + */ + 201: string; + /** + * Response is a simple boolean + */ + 202: boolean; + /** + * Response is a simple object + */ + 203: { + [key: string]: unknown; + }; +}; + +export type TypesResponse = TypesResponses[keyof TypesResponses]; + +export type UploadFileData = { + body: Blob | File; + path: { + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query?: never; + url: '/api/v{api-version}/upload'; +}; + +export type UploadFileResponses = { + 200: boolean; +}; + +export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; + +export type FileResponseData = { + body?: never; + path: { + id: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/file/{id}'; +}; + +export type FileResponseResponses = { + /** + * Success + */ + 200: Blob | File; +}; + +export type FileResponseResponse = FileResponseResponses[keyof FileResponseResponses]; + +export type ComplexTypesData = { + body?: never; + path?: never; + query: { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; + }; + url: '/api/v{api-version}/complex'; +}; + +export type ComplexTypesErrors = { + /** + * 400 `server` error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type ComplexTypesResponses = { + /** + * Successful response + */ + 200: Array; +}; + +export type ComplexTypesResponse = ComplexTypesResponses[keyof ComplexTypesResponses]; + +export type MultipartResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type MultipartResponseResponses = { + /** + * OK + */ + 200: { + file?: Blob | File; + metadata?: { + foo?: string; + bar?: string; + }; + }; +}; + +export type MultipartResponseResponse = MultipartResponseResponses[keyof MultipartResponseResponses]; + +export type MultipartRequestData = { + body?: { + content?: Blob | File; + data?: ModelWithString | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type ComplexParamsData = { + body?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; + path: { + id: number; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/complex/{id}'; +}; + +export type ComplexParamsResponses = { + /** + * Success + */ + 200: ModelWithString; +}; + +export type ComplexParamsResponse = ComplexParamsResponses[keyof ComplexParamsResponses]; + +export type CallWithResultFromHeaderData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/header'; +}; + +export type CallWithResultFromHeaderErrors = { + /** + * 400 server error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type CallWithResultFromHeaderResponses = { + /** + * Successful response + */ + 200: unknown; +}; + +export type TestErrorCodeData = { + body?: never; + path?: never; + query: { + /** + * Status code to return + */ + status: number; + }; + url: '/api/v{api-version}/error'; +}; + +export type TestErrorCodeErrors = { + /** + * Custom message: Internal Server Error + */ + 500: unknown; + /** + * Custom message: Not Implemented + */ + 501: unknown; + /** + * Custom message: Bad Gateway + */ + 502: unknown; + /** + * Custom message: Service Unavailable + */ + 503: unknown; +}; + +export type TestErrorCodeResponses = { + /** + * Custom message: Successful response + */ + 200: unknown; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + body?: never; + path?: never; + query: { + /** + * Dummy input param + */ + nonAsciiParamæøåÆØÅöôêÊ: number; + }; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Responses = { + /** + * Successful response + */ + 200: Array; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = NonAsciiæøåÆøÅöôêÊ字符串Responses[keyof NonAsciiæøåÆøÅöôêÊ字符串Responses]; + +export type PutWithFormUrlEncodedData = { + body: ArrayWithStrings; + path?: never; + query?: never; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client.gen.ts new file mode 100644 index 000000000..950198e01 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'http://localhost:3000/base' +})); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts new file mode 100644 index 000000000..cdc57e116 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts @@ -0,0 +1,239 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( + options as any, + ); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/index.ts new file mode 100644 index 000000000..318a84b6a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts new file mode 100644 index 000000000..e4925b81b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts new file mode 100644 index 000000000..e54e85704 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions); + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/auth.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/bodySerializer.gen.ts new file mode 100644 index 000000000..49cd8925e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/params.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..f8fd78e28 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/types.gen.ts new file mode 100644 index 000000000..643c070c9 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/utils.gen.ts new file mode 100644 index 000000000..0b5389d08 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/index.ts new file mode 100644 index 000000000..cc646f13a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen'; +export * from './sdk.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/sdk.gen.ts new file mode 100644 index 000000000..17622c66b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/sdk.gen.ts @@ -0,0 +1,409 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type Options as ClientOptions, type Client, type TDataShape, formDataBodySerializer, urlSearchParamsBodySerializer } from './client'; +import type { ExportData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, ImportData, ImportResponses, FooWowData, FooWowResponses, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationResponses, GetApiVbyApiVersionSimpleOperationErrors, DeleteCallWithoutParametersAndResponseData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, OptionsCallWithoutParametersAndResponseData, PatchCallWithoutParametersAndResponseData, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, DeleteFooData3 as DeleteFooData, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostApiVbyApiVersionRequestBodyData, PostApiVbyApiVersionFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, DuplicateNameData, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, DummyAData, DummyAResponses, DummyBData, DummyBResponses, CallWithResponseData, CallWithResponseResponses, CallWithDuplicateResponsesData, CallWithDuplicateResponsesResponses, CallWithDuplicateResponsesErrors, CallWithResponsesData, CallWithResponsesResponses, CallWithResponsesErrors, CollectionFormatData, TypesData, TypesResponses, UploadFileData, UploadFileResponses, FileResponseData, FileResponseResponses, ComplexTypesData, ComplexTypesResponses, ComplexTypesErrors, MultipartResponseData, MultipartResponseResponses, MultipartRequestData, ComplexParamsData, ComplexParamsResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderResponses, CallWithResultFromHeaderErrors, TestErrorCodeData, TestErrorCodeResponses, TestErrorCodeErrors, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, PutWithFormUrlEncodedData } from './types.gen'; +import { client } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +export const export_ = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const patchApiVbyApiVersionNoTag = (options?: Options) => { + return (options?.client ?? client).patch({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const import_ = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/no+tag', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const fooWow = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const apiVVersionODataControllerCount = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/simple/$count', + ...options + }); +}; + +export const getApiVbyApiVersionSimpleOperation = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/simple:operation', + ...options + }); +}; + +export const deleteCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).delete({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const getCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const headCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).head({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const optionsCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).options({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const patchCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).patch({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const postCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const putCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const deleteFoo = (options: Options) => { + return (options.client ?? client).delete({ + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', + ...options + }); +}; + +export const callWithDescriptions = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/descriptions', + ...options + }); +}; + +/** + * @deprecated + */ +export const deprecatedCall = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/deprecated', + ...options + }); +}; + +export const callWithParameters = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/{parameterPath}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const callWithWeirdParameterNames = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const getCallWithOptionalParam = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/parameters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postCallWithOptionalParam = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postApiVbyApiVersionRequestBody = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/requestBody', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +export const postApiVbyApiVersionFormData = (options?: Options) => { + return (options?.client ?? client).post({ + ...formDataBodySerializer, + url: '/api/v{api-version}/formData', + ...options, + headers: { + 'Content-Type': null, + ...options?.headers + } + }); +}; + +export const callWithDefaultParameters = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const callWithDefaultOptionalParameters = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const callToTestOrderOfParams = (options: Options) => { + return (options.client ?? client).put({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const duplicateName = (options?: Options) => { + return (options?.client ?? client).delete({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName2 = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName3 = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName4 = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const callWithNoContentResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/no-content', + ...options + }); +}; + +export const callWithResponseAndNoContentResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + ...options + }); +}; + +export const dummyA = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/a', + ...options + }); +}; + +export const dummyB = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/b', + ...options + }); +}; + +export const callWithResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const callWithDuplicateResponses = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const callWithResponses = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const collectionFormat = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/collectionFormat', + ...options + }); +}; + +export const types = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/types', + ...options + }); +}; + +export const uploadFile = (options: Options) => { + return (options.client ?? client).post({ + ...urlSearchParamsBodySerializer, + url: '/api/v{api-version}/upload', + ...options, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...options.headers + } + }); +}; + +export const fileResponse = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/file/{id}', + ...options + }); +}; + +export const complexTypes = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/complex', + ...options + }); +}; + +export const multipartResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multipart', + ...options + }); +}; + +export const multipartRequest = (options?: Options) => { + return (options?.client ?? client).post({ + ...formDataBodySerializer, + url: '/api/v{api-version}/multipart', + ...options, + headers: { + 'Content-Type': null, + ...options?.headers + } + }); +}; + +export const complexParams = (options: Options) => { + return (options.client ?? client).put({ + url: '/api/v{api-version}/complex/{id}', + ...options, + headers: { + 'Content-Type': 'application/json-patch+json', + ...options.headers + } + }); +}; + +export const callWithResultFromHeader = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/header', + ...options + }); +}; + +export const testErrorCode = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/error', + ...options + }); +}; + +export const nonAsciiæøåÆøÅöôêÊ字符串 = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + ...options + }); +}; + +/** + * Login User + */ +export const putWithFormUrlEncoded = (options: Options) => { + return (options.client ?? client).put({ + ...urlSearchParamsBodySerializer, + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + ...options, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...options.headers + } + }); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/types.gen.ts new file mode 100644 index 000000000..4c755476d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/types.gen.ts @@ -0,0 +1,2065 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * External ref to shared model (A) + */ +export type ExternalRefA = ExternalSharedExternalSharedModel; + +/** + * External ref to shared model (B) + */ +export type ExternalRefB = ExternalSharedExternalSharedModel; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CamelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆøÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = Blob | File; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | "'Single Quote'" | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = "'Single Quote'" | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: CamelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<{ + foo?: string; +} | { + bar?: string; +}>; + +export type AnyOfAnyAndNull = { + data?: unknown | null; +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<{ + foo?: string; + } | { + bar?: string; + }>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: string; +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: string | number | boolean | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: string; + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type ModelFromZendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: string | null; + /** + * This is a simple string property + */ + nullableProp2?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: string | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: true; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + /** + * Foo-Bar-Baz-Qux + */ + 'foo-bar-baz-qux'?: '3.0'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: 'Success' | 'Warning' | 'Error'; + }; + dictionaryWithEnumFromDescription?: { + [key: string]: number; + }; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: string; + }; +}; + +/** + * This is a deprecated model with a deprecated property + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: string; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: string; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ({ + kind: 'circle'; +} & ModelCircle) | ({ + kind: 'square'; +} & ModelSquare); + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: Array | Array; +}; + +export type _3eNum1Период = 'Bird' | 'Dog'; + +export type ConstValue = 'ConstValue'; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + /** + * Scopes + */ + propA?: Array<_3eNum1Период | ConstValue> | null; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: boolean | { + [key: string]: number; + }; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean; + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + age: number; + firstName: string; + lastname: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type File = { + /** + * Id + */ + readonly id?: string; + /** + * Updated at + */ + readonly updated_at?: string; + /** + * Created at + */ + readonly created_at?: string; + /** + * Mime + */ + mime: string; + /** + * File + */ + readonly file?: string; +}; + +export type Default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: 'String'; + number?: 0; + null?: null; + withType?: 'Some string'; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown | string | undefined; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: SimpleParameter; +} | { + bar: NonAsciiStringæøåÆøÅöôêÊ字符串; +}) & { + baz: number | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + string, + string + ]; + foo: 'Corge'; +}; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + number | string, + number | string, + number | string +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + number | string, + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + number | null | string, + number | null | string, + number | null | string +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + number | Import, + number | Import +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + number & string, + number & string +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (SimpleParameter | NonAsciiStringæøåÆøÅöôêÊ字符串) & { + baz: number | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type Import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<{ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: IoK8sApimachineryPkgApisMetaV1Preconditions; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: number; +}; + +export type OneOfAllOfIssue = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBoolean = { + item?: boolean; + error?: string | null; + readonly hasError?: boolean; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemString = { + item?: string | null; + error?: string | null; + readonly hasError?: boolean; +}; + +export type ExternalSharedExternalSharedModel = { + id: string; + name?: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithPropertiesWritable = { + required: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPatternWritable = { + key: string; + name: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type FileWritable = { + /** + * Mime + */ + mime: string; +}; + +export type ModelWithReadOnlyAndWriteOnlyWritable = { + foo: string; + baz: string; +}; + +export type AdditionalPropertiesUnknownIssueWritable = { + [key: string]: string | number; +}; + +export type GenericSchemaDuplicateIssue1SystemBooleanWritable = { + item?: boolean; + error?: string | null; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemStringWritable = { + item?: string | null; + error?: string | null; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type XFooBar = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleRequestBody = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleFormData = ModelWithString; + +export type ExportData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ImportData = { + body: ModelWithReadOnlyAndWriteOnlyWritable | ModelWithArrayReadOnlyAndWriteOnly; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type ImportResponses = { + /** + * Success + */ + 200: ModelFromZendesk; + /** + * Default success response + */ + default: ModelWithReadOnlyAndWriteOnly; +}; + +export type ImportResponse = ImportResponses[keyof ImportResponses]; + +export type FooWowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type FooWowResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ApiVVersionODataControllerCountData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple/$count'; +}; + +export type ApiVVersionODataControllerCountResponses = { + /** + * Success + */ + 200: ModelFromZendesk; +}; + +export type ApiVVersionODataControllerCountResponse = ApiVVersionODataControllerCountResponses[keyof ApiVVersionODataControllerCountResponses]; + +export type GetApiVbyApiVersionSimpleOperationData = { + body?: never; + path: { + /** + * foo in method + */ + foo_param: string; + }; + query?: never; + url: '/api/v{api-version}/simple:operation'; +}; + +export type GetApiVbyApiVersionSimpleOperationErrors = { + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type GetApiVbyApiVersionSimpleOperationError = GetApiVbyApiVersionSimpleOperationErrors[keyof GetApiVbyApiVersionSimpleOperationErrors]; + +export type GetApiVbyApiVersionSimpleOperationResponses = { + /** + * Response is a simple number + */ + 200: number; +}; + +export type GetApiVbyApiVersionSimpleOperationResponse = GetApiVbyApiVersionSimpleOperationResponses[keyof GetApiVbyApiVersionSimpleOperationResponses]; + +export type DeleteCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type GetCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type HeadCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type OptionsCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PatchCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PostCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PutCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type DeleteFooData3 = { + body?: never; + headers: { + /** + * Parameter with illegal characters + */ + 'x-Foo-Bar': ModelWithString; + }; + path: { + /** + * foo in method + */ + foo_param: string; + /** + * bar in method + */ + BarParam: string; + }; + query?: never; + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}'; +}; + +export type CallWithDescriptionsData = { + body?: never; + path?: never; + query?: { + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: string; + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: string; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: string; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: string; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: string; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: string; + }; + url: '/api/v{api-version}/descriptions'; +}; + +export type DeprecatedCallData = { + body?: never; + headers: { + /** + * This parameter is deprecated + * @deprecated + */ + parameter: DeprecatedModel | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/parameters/deprecated'; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the body + */ + body: { + [key: string]: unknown; + } | null; + headers: { + /** + * This is the parameter that goes into the header + */ + parameterHeader: string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + parameterPath: string | null; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + foo_ref_enum?: ModelWithNestedArrayEnumsDataFoo; + foo_all_of_enum: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the query params + */ + cursor: string | null; + }; + url: '/api/v{api-version}/parameters/{parameterPath}'; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter that goes into the body + */ + body: ModelWithString | null; + headers: { + /** + * This is the parameter that goes into the request header + */ + 'parameter.header': string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + 'parameter.path.1'?: string; + /** + * This is the parameter that goes into the path + */ + 'parameter-path-2'?: string; + /** + * This is the parameter that goes into the path + */ + 'PARAMETER-PATH-3'?: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + /** + * This is the parameter with a reserved keyword + */ + default?: string; + /** + * This is the parameter that goes into the request query params + */ + 'parameter-query': string | null; + }; + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}'; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is a required parameter + */ + body: ModelWithOneOfEnum; + path?: never; + query?: { + /** + * This is an optional parameter + */ + page?: number; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + body?: { + offset?: number | null; + }; + path?: never; + query: { + /** + * This is a required parameter + */ + parameter: Pageable; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type PostCallWithOptionalParamResponse = PostCallWithOptionalParamResponses[keyof PostCallWithOptionalParamResponses]; + +export type PostApiVbyApiVersionRequestBodyData = { + /** + * A reusable request body + */ + body?: SimpleRequestBody; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/requestBody'; +}; + +export type PostApiVbyApiVersionFormDataData = { + /** + * A reusable request body + */ + body?: SimpleFormData; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/formData'; +}; + +export type CallWithDefaultParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string with default value + */ + parameterString?: string | null; + /** + * This is a simple number with default value + */ + parameterNumber?: number | null; + /** + * This is a simple boolean with default value + */ + parameterBoolean?: boolean | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: ModelWithString | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallWithDefaultOptionalParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallToTestOrderOfParamsData = { + body?: never; + path?: never; + query: { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: string | null; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: string | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type DuplicateNameData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName2Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName3Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName4Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type CallWithNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no-content'; +}; + +export type CallWithNoContentResponseResponses = { + /** + * Success + */ + 204: void; +}; + +export type CallWithNoContentResponseResponse = CallWithNoContentResponseResponses[keyof CallWithNoContentResponseResponses]; + +export type CallWithResponseAndNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/response-and-no-content'; +}; + +export type CallWithResponseAndNoContentResponseResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type CallWithResponseAndNoContentResponseResponse = CallWithResponseAndNoContentResponseResponses[keyof CallWithResponseAndNoContentResponseResponses]; + +export type DummyAData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/a'; +}; + +export type DummyAResponses = { + 200: _400; +}; + +export type DummyAResponse = DummyAResponses[keyof DummyAResponses]; + +export type DummyBData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/b'; +}; + +export type DummyBResponses = { + /** + * Success + */ + 204: void; +}; + +export type DummyBResponse = DummyBResponses[keyof DummyBResponses]; + +export type CallWithResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponseResponses = { + default: Import; +}; + +export type CallWithResponseResponse = CallWithResponseResponses[keyof CallWithResponseResponses]; + +export type CallWithDuplicateResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithDuplicateResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for 4XX errors + */ + '4XX': DictionaryWithArray; + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type CallWithDuplicateResponsesError = CallWithDuplicateResponsesErrors[keyof CallWithDuplicateResponsesErrors]; + +export type CallWithDuplicateResponsesResponses = { + /** + * Message for 200 response + */ + 200: ModelWithBoolean & ModelWithInteger; + /** + * Message for 201 response + */ + 201: ModelWithString; + /** + * Message for 202 response + */ + 202: ModelWithString; +}; + +export type CallWithDuplicateResponsesResponse = CallWithDuplicateResponsesResponses[keyof CallWithDuplicateResponsesResponses]; + +export type CallWithResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for default response + */ + default: ModelWithStringError; +}; + +export type CallWithResponsesError = CallWithResponsesErrors[keyof CallWithResponsesErrors]; + +export type CallWithResponsesResponses = { + /** + * Message for 200 response + */ + 200: { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + }; + /** + * Message for 201 response + */ + 201: ModelThatExtends; + /** + * Message for 202 response + */ + 202: ModelThatExtendsExtends; +}; + +export type CallWithResponsesResponse = CallWithResponsesResponses[keyof CallWithResponsesResponses]; + +export type CollectionFormatData = { + body?: never; + path?: never; + query: { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCSV: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySSV: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTSV: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + }; + url: '/api/v{api-version}/collectionFormat'; +}; + +export type TypesData = { + body?: never; + path?: { + /** + * This is a number parameter + */ + id?: number; + }; + query: { + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is a string parameter + */ + parameterString: string | null; + /** + * This is a boolean parameter + */ + parameterBoolean: boolean | null; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + }; + url: '/api/v{api-version}/types'; +}; + +export type TypesResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Response is a simple string + */ + 201: string; + /** + * Response is a simple boolean + */ + 202: boolean; + /** + * Response is a simple object + */ + 203: { + [key: string]: unknown; + }; +}; + +export type TypesResponse = TypesResponses[keyof TypesResponses]; + +export type UploadFileData = { + body: Blob | File; + path: { + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query?: never; + url: '/api/v{api-version}/upload'; +}; + +export type UploadFileResponses = { + 200: boolean; +}; + +export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; + +export type FileResponseData = { + body?: never; + path: { + id: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/file/{id}'; +}; + +export type FileResponseResponses = { + /** + * Success + */ + 200: Blob | File; +}; + +export type FileResponseResponse = FileResponseResponses[keyof FileResponseResponses]; + +export type ComplexTypesData = { + body?: never; + path?: never; + query: { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; + }; + url: '/api/v{api-version}/complex'; +}; + +export type ComplexTypesErrors = { + /** + * 400 `server` error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type ComplexTypesResponses = { + /** + * Successful response + */ + 200: Array; +}; + +export type ComplexTypesResponse = ComplexTypesResponses[keyof ComplexTypesResponses]; + +export type MultipartResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type MultipartResponseResponses = { + /** + * OK + */ + 200: { + file?: Blob | File; + metadata?: { + foo?: string; + bar?: string; + }; + }; +}; + +export type MultipartResponseResponse = MultipartResponseResponses[keyof MultipartResponseResponses]; + +export type MultipartRequestData = { + body?: { + content?: Blob | File; + data?: ModelWithString | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type ComplexParamsData = { + body?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; + path: { + id: number; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/complex/{id}'; +}; + +export type ComplexParamsResponses = { + /** + * Success + */ + 200: ModelWithString; +}; + +export type ComplexParamsResponse = ComplexParamsResponses[keyof ComplexParamsResponses]; + +export type CallWithResultFromHeaderData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/header'; +}; + +export type CallWithResultFromHeaderErrors = { + /** + * 400 server error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type CallWithResultFromHeaderResponses = { + /** + * Successful response + */ + 200: unknown; +}; + +export type TestErrorCodeData = { + body?: never; + path?: never; + query: { + /** + * Status code to return + */ + status: number; + }; + url: '/api/v{api-version}/error'; +}; + +export type TestErrorCodeErrors = { + /** + * Custom message: Internal Server Error + */ + 500: unknown; + /** + * Custom message: Not Implemented + */ + 501: unknown; + /** + * Custom message: Bad Gateway + */ + 502: unknown; + /** + * Custom message: Service Unavailable + */ + 503: unknown; +}; + +export type TestErrorCodeResponses = { + /** + * Custom message: Successful response + */ + 200: unknown; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + body?: never; + path?: never; + query: { + /** + * Dummy input param + */ + nonAsciiParamæøåÆØÅöôêÊ: number; + }; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Responses = { + /** + * Successful response + */ + 200: Array; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = NonAsciiæøåÆøÅöôêÊ字符串Responses[keyof NonAsciiæøåÆøÅöôêÊ字符串Responses]; + +export type PutWithFormUrlEncodedData = { + body: ArrayWithStrings; + path?: never; + query?: never; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client.gen.ts new file mode 100644 index 000000000..950198e01 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'http://localhost:3000/base' +})); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts new file mode 100644 index 000000000..cdc57e116 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts @@ -0,0 +1,239 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( + options as any, + ); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/index.ts new file mode 100644 index 000000000..318a84b6a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts new file mode 100644 index 000000000..e4925b81b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts new file mode 100644 index 000000000..e54e85704 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions); + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/auth.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/bodySerializer.gen.ts new file mode 100644 index 000000000..49cd8925e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/params.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..f8fd78e28 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/types.gen.ts new file mode 100644 index 000000000..643c070c9 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/utils.gen.ts new file mode 100644 index 000000000..0b5389d08 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/index.ts new file mode 100644 index 000000000..cc646f13a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen'; +export * from './sdk.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/sdk.gen.ts new file mode 100644 index 000000000..17622c66b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/sdk.gen.ts @@ -0,0 +1,409 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type Options as ClientOptions, type Client, type TDataShape, formDataBodySerializer, urlSearchParamsBodySerializer } from './client'; +import type { ExportData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, ImportData, ImportResponses, FooWowData, FooWowResponses, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationResponses, GetApiVbyApiVersionSimpleOperationErrors, DeleteCallWithoutParametersAndResponseData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, OptionsCallWithoutParametersAndResponseData, PatchCallWithoutParametersAndResponseData, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, DeleteFooData3 as DeleteFooData, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostApiVbyApiVersionRequestBodyData, PostApiVbyApiVersionFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, DuplicateNameData, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, DummyAData, DummyAResponses, DummyBData, DummyBResponses, CallWithResponseData, CallWithResponseResponses, CallWithDuplicateResponsesData, CallWithDuplicateResponsesResponses, CallWithDuplicateResponsesErrors, CallWithResponsesData, CallWithResponsesResponses, CallWithResponsesErrors, CollectionFormatData, TypesData, TypesResponses, UploadFileData, UploadFileResponses, FileResponseData, FileResponseResponses, ComplexTypesData, ComplexTypesResponses, ComplexTypesErrors, MultipartResponseData, MultipartResponseResponses, MultipartRequestData, ComplexParamsData, ComplexParamsResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderResponses, CallWithResultFromHeaderErrors, TestErrorCodeData, TestErrorCodeResponses, TestErrorCodeErrors, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, PutWithFormUrlEncodedData } from './types.gen'; +import { client } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +export const export_ = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const patchApiVbyApiVersionNoTag = (options?: Options) => { + return (options?.client ?? client).patch({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const import_ = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/no+tag', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const fooWow = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const apiVVersionODataControllerCount = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/simple/$count', + ...options + }); +}; + +export const getApiVbyApiVersionSimpleOperation = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/simple:operation', + ...options + }); +}; + +export const deleteCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).delete({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const getCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const headCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).head({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const optionsCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).options({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const patchCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).patch({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const postCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const putCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const deleteFoo = (options: Options) => { + return (options.client ?? client).delete({ + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', + ...options + }); +}; + +export const callWithDescriptions = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/descriptions', + ...options + }); +}; + +/** + * @deprecated + */ +export const deprecatedCall = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/deprecated', + ...options + }); +}; + +export const callWithParameters = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/{parameterPath}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const callWithWeirdParameterNames = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const getCallWithOptionalParam = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/parameters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postCallWithOptionalParam = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postApiVbyApiVersionRequestBody = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/requestBody', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +export const postApiVbyApiVersionFormData = (options?: Options) => { + return (options?.client ?? client).post({ + ...formDataBodySerializer, + url: '/api/v{api-version}/formData', + ...options, + headers: { + 'Content-Type': null, + ...options?.headers + } + }); +}; + +export const callWithDefaultParameters = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const callWithDefaultOptionalParameters = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const callToTestOrderOfParams = (options: Options) => { + return (options.client ?? client).put({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const duplicateName = (options?: Options) => { + return (options?.client ?? client).delete({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName2 = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName3 = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName4 = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const callWithNoContentResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/no-content', + ...options + }); +}; + +export const callWithResponseAndNoContentResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + ...options + }); +}; + +export const dummyA = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/a', + ...options + }); +}; + +export const dummyB = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/b', + ...options + }); +}; + +export const callWithResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const callWithDuplicateResponses = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const callWithResponses = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const collectionFormat = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/collectionFormat', + ...options + }); +}; + +export const types = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/types', + ...options + }); +}; + +export const uploadFile = (options: Options) => { + return (options.client ?? client).post({ + ...urlSearchParamsBodySerializer, + url: '/api/v{api-version}/upload', + ...options, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...options.headers + } + }); +}; + +export const fileResponse = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/file/{id}', + ...options + }); +}; + +export const complexTypes = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/complex', + ...options + }); +}; + +export const multipartResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multipart', + ...options + }); +}; + +export const multipartRequest = (options?: Options) => { + return (options?.client ?? client).post({ + ...formDataBodySerializer, + url: '/api/v{api-version}/multipart', + ...options, + headers: { + 'Content-Type': null, + ...options?.headers + } + }); +}; + +export const complexParams = (options: Options) => { + return (options.client ?? client).put({ + url: '/api/v{api-version}/complex/{id}', + ...options, + headers: { + 'Content-Type': 'application/json-patch+json', + ...options.headers + } + }); +}; + +export const callWithResultFromHeader = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/header', + ...options + }); +}; + +export const testErrorCode = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/error', + ...options + }); +}; + +export const nonAsciiæøåÆøÅöôêÊ字符串 = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + ...options + }); +}; + +/** + * Login User + */ +export const putWithFormUrlEncoded = (options: Options) => { + return (options.client ?? client).put({ + ...urlSearchParamsBodySerializer, + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + ...options, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...options.headers + } + }); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/types.gen.ts new file mode 100644 index 000000000..4c755476d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/types.gen.ts @@ -0,0 +1,2065 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * External ref to shared model (A) + */ +export type ExternalRefA = ExternalSharedExternalSharedModel; + +/** + * External ref to shared model (B) + */ +export type ExternalRefB = ExternalSharedExternalSharedModel; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CamelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆøÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = Blob | File; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | "'Single Quote'" | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = "'Single Quote'" | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: CamelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<{ + foo?: string; +} | { + bar?: string; +}>; + +export type AnyOfAnyAndNull = { + data?: unknown | null; +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<{ + foo?: string; + } | { + bar?: string; + }>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: string; +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: string | number | boolean | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: string; + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type ModelFromZendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: string | null; + /** + * This is a simple string property + */ + nullableProp2?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: string | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: true; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + /** + * Foo-Bar-Baz-Qux + */ + 'foo-bar-baz-qux'?: '3.0'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: 'Success' | 'Warning' | 'Error'; + }; + dictionaryWithEnumFromDescription?: { + [key: string]: number; + }; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: string; + }; +}; + +/** + * This is a deprecated model with a deprecated property + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: string; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: string; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ({ + kind: 'circle'; +} & ModelCircle) | ({ + kind: 'square'; +} & ModelSquare); + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: Array | Array; +}; + +export type _3eNum1Период = 'Bird' | 'Dog'; + +export type ConstValue = 'ConstValue'; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + /** + * Scopes + */ + propA?: Array<_3eNum1Период | ConstValue> | null; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: boolean | { + [key: string]: number; + }; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean; + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + age: number; + firstName: string; + lastname: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type File = { + /** + * Id + */ + readonly id?: string; + /** + * Updated at + */ + readonly updated_at?: string; + /** + * Created at + */ + readonly created_at?: string; + /** + * Mime + */ + mime: string; + /** + * File + */ + readonly file?: string; +}; + +export type Default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: 'String'; + number?: 0; + null?: null; + withType?: 'Some string'; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown | string | undefined; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: SimpleParameter; +} | { + bar: NonAsciiStringæøåÆøÅöôêÊ字符串; +}) & { + baz: number | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + string, + string + ]; + foo: 'Corge'; +}; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + number | string, + number | string, + number | string +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + number | string, + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + number | null | string, + number | null | string, + number | null | string +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + number | Import, + number | Import +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + number & string, + number & string +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (SimpleParameter | NonAsciiStringæøåÆøÅöôêÊ字符串) & { + baz: number | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type Import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<{ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: IoK8sApimachineryPkgApisMetaV1Preconditions; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: number; +}; + +export type OneOfAllOfIssue = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBoolean = { + item?: boolean; + error?: string | null; + readonly hasError?: boolean; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemString = { + item?: string | null; + error?: string | null; + readonly hasError?: boolean; +}; + +export type ExternalSharedExternalSharedModel = { + id: string; + name?: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithPropertiesWritable = { + required: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPatternWritable = { + key: string; + name: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type FileWritable = { + /** + * Mime + */ + mime: string; +}; + +export type ModelWithReadOnlyAndWriteOnlyWritable = { + foo: string; + baz: string; +}; + +export type AdditionalPropertiesUnknownIssueWritable = { + [key: string]: string | number; +}; + +export type GenericSchemaDuplicateIssue1SystemBooleanWritable = { + item?: boolean; + error?: string | null; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemStringWritable = { + item?: string | null; + error?: string | null; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type XFooBar = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleRequestBody = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleFormData = ModelWithString; + +export type ExportData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ImportData = { + body: ModelWithReadOnlyAndWriteOnlyWritable | ModelWithArrayReadOnlyAndWriteOnly; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type ImportResponses = { + /** + * Success + */ + 200: ModelFromZendesk; + /** + * Default success response + */ + default: ModelWithReadOnlyAndWriteOnly; +}; + +export type ImportResponse = ImportResponses[keyof ImportResponses]; + +export type FooWowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type FooWowResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ApiVVersionODataControllerCountData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple/$count'; +}; + +export type ApiVVersionODataControllerCountResponses = { + /** + * Success + */ + 200: ModelFromZendesk; +}; + +export type ApiVVersionODataControllerCountResponse = ApiVVersionODataControllerCountResponses[keyof ApiVVersionODataControllerCountResponses]; + +export type GetApiVbyApiVersionSimpleOperationData = { + body?: never; + path: { + /** + * foo in method + */ + foo_param: string; + }; + query?: never; + url: '/api/v{api-version}/simple:operation'; +}; + +export type GetApiVbyApiVersionSimpleOperationErrors = { + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type GetApiVbyApiVersionSimpleOperationError = GetApiVbyApiVersionSimpleOperationErrors[keyof GetApiVbyApiVersionSimpleOperationErrors]; + +export type GetApiVbyApiVersionSimpleOperationResponses = { + /** + * Response is a simple number + */ + 200: number; +}; + +export type GetApiVbyApiVersionSimpleOperationResponse = GetApiVbyApiVersionSimpleOperationResponses[keyof GetApiVbyApiVersionSimpleOperationResponses]; + +export type DeleteCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type GetCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type HeadCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type OptionsCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PatchCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PostCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PutCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type DeleteFooData3 = { + body?: never; + headers: { + /** + * Parameter with illegal characters + */ + 'x-Foo-Bar': ModelWithString; + }; + path: { + /** + * foo in method + */ + foo_param: string; + /** + * bar in method + */ + BarParam: string; + }; + query?: never; + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}'; +}; + +export type CallWithDescriptionsData = { + body?: never; + path?: never; + query?: { + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: string; + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: string; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: string; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: string; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: string; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: string; + }; + url: '/api/v{api-version}/descriptions'; +}; + +export type DeprecatedCallData = { + body?: never; + headers: { + /** + * This parameter is deprecated + * @deprecated + */ + parameter: DeprecatedModel | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/parameters/deprecated'; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the body + */ + body: { + [key: string]: unknown; + } | null; + headers: { + /** + * This is the parameter that goes into the header + */ + parameterHeader: string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + parameterPath: string | null; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + foo_ref_enum?: ModelWithNestedArrayEnumsDataFoo; + foo_all_of_enum: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the query params + */ + cursor: string | null; + }; + url: '/api/v{api-version}/parameters/{parameterPath}'; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter that goes into the body + */ + body: ModelWithString | null; + headers: { + /** + * This is the parameter that goes into the request header + */ + 'parameter.header': string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + 'parameter.path.1'?: string; + /** + * This is the parameter that goes into the path + */ + 'parameter-path-2'?: string; + /** + * This is the parameter that goes into the path + */ + 'PARAMETER-PATH-3'?: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + /** + * This is the parameter with a reserved keyword + */ + default?: string; + /** + * This is the parameter that goes into the request query params + */ + 'parameter-query': string | null; + }; + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}'; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is a required parameter + */ + body: ModelWithOneOfEnum; + path?: never; + query?: { + /** + * This is an optional parameter + */ + page?: number; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + body?: { + offset?: number | null; + }; + path?: never; + query: { + /** + * This is a required parameter + */ + parameter: Pageable; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type PostCallWithOptionalParamResponse = PostCallWithOptionalParamResponses[keyof PostCallWithOptionalParamResponses]; + +export type PostApiVbyApiVersionRequestBodyData = { + /** + * A reusable request body + */ + body?: SimpleRequestBody; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/requestBody'; +}; + +export type PostApiVbyApiVersionFormDataData = { + /** + * A reusable request body + */ + body?: SimpleFormData; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/formData'; +}; + +export type CallWithDefaultParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string with default value + */ + parameterString?: string | null; + /** + * This is a simple number with default value + */ + parameterNumber?: number | null; + /** + * This is a simple boolean with default value + */ + parameterBoolean?: boolean | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: ModelWithString | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallWithDefaultOptionalParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallToTestOrderOfParamsData = { + body?: never; + path?: never; + query: { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: string | null; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: string | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type DuplicateNameData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName2Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName3Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName4Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type CallWithNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no-content'; +}; + +export type CallWithNoContentResponseResponses = { + /** + * Success + */ + 204: void; +}; + +export type CallWithNoContentResponseResponse = CallWithNoContentResponseResponses[keyof CallWithNoContentResponseResponses]; + +export type CallWithResponseAndNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/response-and-no-content'; +}; + +export type CallWithResponseAndNoContentResponseResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type CallWithResponseAndNoContentResponseResponse = CallWithResponseAndNoContentResponseResponses[keyof CallWithResponseAndNoContentResponseResponses]; + +export type DummyAData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/a'; +}; + +export type DummyAResponses = { + 200: _400; +}; + +export type DummyAResponse = DummyAResponses[keyof DummyAResponses]; + +export type DummyBData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/b'; +}; + +export type DummyBResponses = { + /** + * Success + */ + 204: void; +}; + +export type DummyBResponse = DummyBResponses[keyof DummyBResponses]; + +export type CallWithResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponseResponses = { + default: Import; +}; + +export type CallWithResponseResponse = CallWithResponseResponses[keyof CallWithResponseResponses]; + +export type CallWithDuplicateResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithDuplicateResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for 4XX errors + */ + '4XX': DictionaryWithArray; + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type CallWithDuplicateResponsesError = CallWithDuplicateResponsesErrors[keyof CallWithDuplicateResponsesErrors]; + +export type CallWithDuplicateResponsesResponses = { + /** + * Message for 200 response + */ + 200: ModelWithBoolean & ModelWithInteger; + /** + * Message for 201 response + */ + 201: ModelWithString; + /** + * Message for 202 response + */ + 202: ModelWithString; +}; + +export type CallWithDuplicateResponsesResponse = CallWithDuplicateResponsesResponses[keyof CallWithDuplicateResponsesResponses]; + +export type CallWithResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for default response + */ + default: ModelWithStringError; +}; + +export type CallWithResponsesError = CallWithResponsesErrors[keyof CallWithResponsesErrors]; + +export type CallWithResponsesResponses = { + /** + * Message for 200 response + */ + 200: { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + }; + /** + * Message for 201 response + */ + 201: ModelThatExtends; + /** + * Message for 202 response + */ + 202: ModelThatExtendsExtends; +}; + +export type CallWithResponsesResponse = CallWithResponsesResponses[keyof CallWithResponsesResponses]; + +export type CollectionFormatData = { + body?: never; + path?: never; + query: { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCSV: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySSV: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTSV: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + }; + url: '/api/v{api-version}/collectionFormat'; +}; + +export type TypesData = { + body?: never; + path?: { + /** + * This is a number parameter + */ + id?: number; + }; + query: { + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is a string parameter + */ + parameterString: string | null; + /** + * This is a boolean parameter + */ + parameterBoolean: boolean | null; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + }; + url: '/api/v{api-version}/types'; +}; + +export type TypesResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Response is a simple string + */ + 201: string; + /** + * Response is a simple boolean + */ + 202: boolean; + /** + * Response is a simple object + */ + 203: { + [key: string]: unknown; + }; +}; + +export type TypesResponse = TypesResponses[keyof TypesResponses]; + +export type UploadFileData = { + body: Blob | File; + path: { + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query?: never; + url: '/api/v{api-version}/upload'; +}; + +export type UploadFileResponses = { + 200: boolean; +}; + +export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; + +export type FileResponseData = { + body?: never; + path: { + id: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/file/{id}'; +}; + +export type FileResponseResponses = { + /** + * Success + */ + 200: Blob | File; +}; + +export type FileResponseResponse = FileResponseResponses[keyof FileResponseResponses]; + +export type ComplexTypesData = { + body?: never; + path?: never; + query: { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; + }; + url: '/api/v{api-version}/complex'; +}; + +export type ComplexTypesErrors = { + /** + * 400 `server` error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type ComplexTypesResponses = { + /** + * Successful response + */ + 200: Array; +}; + +export type ComplexTypesResponse = ComplexTypesResponses[keyof ComplexTypesResponses]; + +export type MultipartResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type MultipartResponseResponses = { + /** + * OK + */ + 200: { + file?: Blob | File; + metadata?: { + foo?: string; + bar?: string; + }; + }; +}; + +export type MultipartResponseResponse = MultipartResponseResponses[keyof MultipartResponseResponses]; + +export type MultipartRequestData = { + body?: { + content?: Blob | File; + data?: ModelWithString | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type ComplexParamsData = { + body?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; + path: { + id: number; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/complex/{id}'; +}; + +export type ComplexParamsResponses = { + /** + * Success + */ + 200: ModelWithString; +}; + +export type ComplexParamsResponse = ComplexParamsResponses[keyof ComplexParamsResponses]; + +export type CallWithResultFromHeaderData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/header'; +}; + +export type CallWithResultFromHeaderErrors = { + /** + * 400 server error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type CallWithResultFromHeaderResponses = { + /** + * Successful response + */ + 200: unknown; +}; + +export type TestErrorCodeData = { + body?: never; + path?: never; + query: { + /** + * Status code to return + */ + status: number; + }; + url: '/api/v{api-version}/error'; +}; + +export type TestErrorCodeErrors = { + /** + * Custom message: Internal Server Error + */ + 500: unknown; + /** + * Custom message: Not Implemented + */ + 501: unknown; + /** + * Custom message: Bad Gateway + */ + 502: unknown; + /** + * Custom message: Service Unavailable + */ + 503: unknown; +}; + +export type TestErrorCodeResponses = { + /** + * Custom message: Successful response + */ + 200: unknown; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + body?: never; + path?: never; + query: { + /** + * Dummy input param + */ + nonAsciiParamæøåÆØÅöôêÊ: number; + }; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Responses = { + /** + * Successful response + */ + 200: Array; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = NonAsciiæøåÆøÅöôêÊ字符串Responses[keyof NonAsciiæøåÆøÅöôêÊ字符串Responses]; + +export type PutWithFormUrlEncodedData = { + body: ArrayWithStrings; + path?: never; + query?: never; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client.gen.ts new file mode 100644 index 000000000..950198e01 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'http://localhost:3000/base' +})); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts new file mode 100644 index 000000000..cdc57e116 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts @@ -0,0 +1,239 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( + options as any, + ); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/index.ts new file mode 100644 index 000000000..318a84b6a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts new file mode 100644 index 000000000..e4925b81b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts new file mode 100644 index 000000000..e54e85704 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions); + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/auth.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/bodySerializer.gen.ts new file mode 100644 index 000000000..49cd8925e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/params.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..f8fd78e28 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/types.gen.ts new file mode 100644 index 000000000..643c070c9 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/utils.gen.ts new file mode 100644 index 000000000..0b5389d08 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/index.ts new file mode 100644 index 000000000..cc646f13a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen'; +export * from './sdk.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/sdk.gen.ts new file mode 100644 index 000000000..d7073900a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/sdk.gen.ts @@ -0,0 +1,408 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type Options as ClientOptions, type Client, type TDataShape, formDataBodySerializer, urlSearchParamsBodySerializer } from './client'; +import type { ExportData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, ImportData, ImportResponses, FooWowData, FooWowResponses, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationResponses, GetApiVbyApiVersionSimpleOperationErrors, DeleteCallWithoutParametersAndResponseData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, OptionsCallWithoutParametersAndResponseData, PatchCallWithoutParametersAndResponseData, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, DeleteFooData3 as DeleteFooData, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostApiVbyApiVersionRequestBodyData, PostApiVbyApiVersionFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, DuplicateNameData, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, DummyAData, DummyAResponses, DummyBData, DummyBResponses, CallWithResponseData, CallWithResponseResponses, CallWithDuplicateResponsesData, CallWithDuplicateResponsesResponses, CallWithDuplicateResponsesErrors, CallWithResponsesData, CallWithResponsesResponses, CallWithResponsesErrors, CollectionFormatData, TypesData, TypesResponses, UploadFileData, UploadFileResponses, FileResponseData, FileResponseResponses, ComplexTypesData, ComplexTypesResponses, ComplexTypesErrors, MultipartResponseData, MultipartResponseResponses, MultipartRequestData, ComplexParamsData, ComplexParamsResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderResponses, CallWithResultFromHeaderErrors, TestErrorCodeData, TestErrorCodeResponses, TestErrorCodeErrors, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, PutWithFormUrlEncodedData } from './types.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +export const export_ = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const patchApiVbyApiVersionNoTag = (options: Options) => { + return options.client.patch({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const import_ = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/no+tag', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const fooWow = (options: Options) => { + return options.client.put({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const apiVVersionODataControllerCount = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/simple/$count', + ...options + }); +}; + +export const getApiVbyApiVersionSimpleOperation = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/simple:operation', + ...options + }); +}; + +export const deleteCallWithoutParametersAndResponse = (options: Options) => { + return options.client.delete({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const getCallWithoutParametersAndResponse = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const headCallWithoutParametersAndResponse = (options: Options) => { + return options.client.head({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const optionsCallWithoutParametersAndResponse = (options: Options) => { + return options.client.options({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const patchCallWithoutParametersAndResponse = (options: Options) => { + return options.client.patch({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const postCallWithoutParametersAndResponse = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const putCallWithoutParametersAndResponse = (options: Options) => { + return options.client.put({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const deleteFoo = (options: Options) => { + return options.client.delete({ + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', + ...options + }); +}; + +export const callWithDescriptions = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/descriptions', + ...options + }); +}; + +/** + * @deprecated + */ +export const deprecatedCall = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/parameters/deprecated', + ...options + }); +}; + +export const callWithParameters = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/parameters/{parameterPath}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const callWithWeirdParameterNames = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const getCallWithOptionalParam = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/parameters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postCallWithOptionalParam = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/parameters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postApiVbyApiVersionRequestBody = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/requestBody', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postApiVbyApiVersionFormData = (options: Options) => { + return options.client.post({ + ...formDataBodySerializer, + url: '/api/v{api-version}/formData', + ...options, + headers: { + 'Content-Type': null, + ...options.headers + } + }); +}; + +export const callWithDefaultParameters = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const callWithDefaultOptionalParameters = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const callToTestOrderOfParams = (options: Options) => { + return options.client.put({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const duplicateName = (options: Options) => { + return options.client.delete({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName2 = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName3 = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName4 = (options: Options) => { + return options.client.put({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const callWithNoContentResponse = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/no-content', + ...options + }); +}; + +export const callWithResponseAndNoContentResponse = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + ...options + }); +}; + +export const dummyA = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/multiple-tags/a', + ...options + }); +}; + +export const dummyB = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/multiple-tags/b', + ...options + }); +}; + +export const callWithResponse = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const callWithDuplicateResponses = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const callWithResponses = (options: Options) => { + return options.client.put({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const collectionFormat = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/collectionFormat', + ...options + }); +}; + +export const types = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/types', + ...options + }); +}; + +export const uploadFile = (options: Options) => { + return options.client.post({ + ...urlSearchParamsBodySerializer, + url: '/api/v{api-version}/upload', + ...options, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...options.headers + } + }); +}; + +export const fileResponse = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/file/{id}', + ...options + }); +}; + +export const complexTypes = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/complex', + ...options + }); +}; + +export const multipartResponse = (options: Options) => { + return options.client.get({ + url: '/api/v{api-version}/multipart', + ...options + }); +}; + +export const multipartRequest = (options: Options) => { + return options.client.post({ + ...formDataBodySerializer, + url: '/api/v{api-version}/multipart', + ...options, + headers: { + 'Content-Type': null, + ...options.headers + } + }); +}; + +export const complexParams = (options: Options) => { + return options.client.put({ + url: '/api/v{api-version}/complex/{id}', + ...options, + headers: { + 'Content-Type': 'application/json-patch+json', + ...options.headers + } + }); +}; + +export const callWithResultFromHeader = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/header', + ...options + }); +}; + +export const testErrorCode = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/error', + ...options + }); +}; + +export const nonAsciiæøåÆøÅöôêÊ字符串 = (options: Options) => { + return options.client.post({ + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + ...options + }); +}; + +/** + * Login User + */ +export const putWithFormUrlEncoded = (options: Options) => { + return options.client.put({ + ...urlSearchParamsBodySerializer, + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + ...options, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...options.headers + } + }); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/types.gen.ts new file mode 100644 index 000000000..4c755476d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/types.gen.ts @@ -0,0 +1,2065 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * External ref to shared model (A) + */ +export type ExternalRefA = ExternalSharedExternalSharedModel; + +/** + * External ref to shared model (B) + */ +export type ExternalRefB = ExternalSharedExternalSharedModel; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CamelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆøÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = Blob | File; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | "'Single Quote'" | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = "'Single Quote'" | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: CamelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<{ + foo?: string; +} | { + bar?: string; +}>; + +export type AnyOfAnyAndNull = { + data?: unknown | null; +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<{ + foo?: string; + } | { + bar?: string; + }>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: string; +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: string | number | boolean | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: string; + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type ModelFromZendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: string | null; + /** + * This is a simple string property + */ + nullableProp2?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: string | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: true; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + /** + * Foo-Bar-Baz-Qux + */ + 'foo-bar-baz-qux'?: '3.0'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: 'Success' | 'Warning' | 'Error'; + }; + dictionaryWithEnumFromDescription?: { + [key: string]: number; + }; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: string; + }; +}; + +/** + * This is a deprecated model with a deprecated property + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: string; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: string; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ({ + kind: 'circle'; +} & ModelCircle) | ({ + kind: 'square'; +} & ModelSquare); + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: Array | Array; +}; + +export type _3eNum1Период = 'Bird' | 'Dog'; + +export type ConstValue = 'ConstValue'; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + /** + * Scopes + */ + propA?: Array<_3eNum1Период | ConstValue> | null; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: boolean | { + [key: string]: number; + }; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean; + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + age: number; + firstName: string; + lastname: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type File = { + /** + * Id + */ + readonly id?: string; + /** + * Updated at + */ + readonly updated_at?: string; + /** + * Created at + */ + readonly created_at?: string; + /** + * Mime + */ + mime: string; + /** + * File + */ + readonly file?: string; +}; + +export type Default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: 'String'; + number?: 0; + null?: null; + withType?: 'Some string'; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown | string | undefined; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: SimpleParameter; +} | { + bar: NonAsciiStringæøåÆøÅöôêÊ字符串; +}) & { + baz: number | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + string, + string + ]; + foo: 'Corge'; +}; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + number | string, + number | string, + number | string +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + number | string, + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + number | null | string, + number | null | string, + number | null | string +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + number | Import, + number | Import +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + number & string, + number & string +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (SimpleParameter | NonAsciiStringæøåÆøÅöôêÊ字符串) & { + baz: number | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type Import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<{ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: IoK8sApimachineryPkgApisMetaV1Preconditions; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: number; +}; + +export type OneOfAllOfIssue = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBoolean = { + item?: boolean; + error?: string | null; + readonly hasError?: boolean; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemString = { + item?: string | null; + error?: string | null; + readonly hasError?: boolean; +}; + +export type ExternalSharedExternalSharedModel = { + id: string; + name?: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithPropertiesWritable = { + required: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPatternWritable = { + key: string; + name: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type FileWritable = { + /** + * Mime + */ + mime: string; +}; + +export type ModelWithReadOnlyAndWriteOnlyWritable = { + foo: string; + baz: string; +}; + +export type AdditionalPropertiesUnknownIssueWritable = { + [key: string]: string | number; +}; + +export type GenericSchemaDuplicateIssue1SystemBooleanWritable = { + item?: boolean; + error?: string | null; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemStringWritable = { + item?: string | null; + error?: string | null; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type XFooBar = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleRequestBody = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleFormData = ModelWithString; + +export type ExportData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ImportData = { + body: ModelWithReadOnlyAndWriteOnlyWritable | ModelWithArrayReadOnlyAndWriteOnly; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type ImportResponses = { + /** + * Success + */ + 200: ModelFromZendesk; + /** + * Default success response + */ + default: ModelWithReadOnlyAndWriteOnly; +}; + +export type ImportResponse = ImportResponses[keyof ImportResponses]; + +export type FooWowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type FooWowResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ApiVVersionODataControllerCountData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple/$count'; +}; + +export type ApiVVersionODataControllerCountResponses = { + /** + * Success + */ + 200: ModelFromZendesk; +}; + +export type ApiVVersionODataControllerCountResponse = ApiVVersionODataControllerCountResponses[keyof ApiVVersionODataControllerCountResponses]; + +export type GetApiVbyApiVersionSimpleOperationData = { + body?: never; + path: { + /** + * foo in method + */ + foo_param: string; + }; + query?: never; + url: '/api/v{api-version}/simple:operation'; +}; + +export type GetApiVbyApiVersionSimpleOperationErrors = { + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type GetApiVbyApiVersionSimpleOperationError = GetApiVbyApiVersionSimpleOperationErrors[keyof GetApiVbyApiVersionSimpleOperationErrors]; + +export type GetApiVbyApiVersionSimpleOperationResponses = { + /** + * Response is a simple number + */ + 200: number; +}; + +export type GetApiVbyApiVersionSimpleOperationResponse = GetApiVbyApiVersionSimpleOperationResponses[keyof GetApiVbyApiVersionSimpleOperationResponses]; + +export type DeleteCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type GetCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type HeadCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type OptionsCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PatchCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PostCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PutCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type DeleteFooData3 = { + body?: never; + headers: { + /** + * Parameter with illegal characters + */ + 'x-Foo-Bar': ModelWithString; + }; + path: { + /** + * foo in method + */ + foo_param: string; + /** + * bar in method + */ + BarParam: string; + }; + query?: never; + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}'; +}; + +export type CallWithDescriptionsData = { + body?: never; + path?: never; + query?: { + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: string; + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: string; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: string; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: string; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: string; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: string; + }; + url: '/api/v{api-version}/descriptions'; +}; + +export type DeprecatedCallData = { + body?: never; + headers: { + /** + * This parameter is deprecated + * @deprecated + */ + parameter: DeprecatedModel | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/parameters/deprecated'; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the body + */ + body: { + [key: string]: unknown; + } | null; + headers: { + /** + * This is the parameter that goes into the header + */ + parameterHeader: string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + parameterPath: string | null; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + foo_ref_enum?: ModelWithNestedArrayEnumsDataFoo; + foo_all_of_enum: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the query params + */ + cursor: string | null; + }; + url: '/api/v{api-version}/parameters/{parameterPath}'; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter that goes into the body + */ + body: ModelWithString | null; + headers: { + /** + * This is the parameter that goes into the request header + */ + 'parameter.header': string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + 'parameter.path.1'?: string; + /** + * This is the parameter that goes into the path + */ + 'parameter-path-2'?: string; + /** + * This is the parameter that goes into the path + */ + 'PARAMETER-PATH-3'?: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + /** + * This is the parameter with a reserved keyword + */ + default?: string; + /** + * This is the parameter that goes into the request query params + */ + 'parameter-query': string | null; + }; + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}'; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is a required parameter + */ + body: ModelWithOneOfEnum; + path?: never; + query?: { + /** + * This is an optional parameter + */ + page?: number; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + body?: { + offset?: number | null; + }; + path?: never; + query: { + /** + * This is a required parameter + */ + parameter: Pageable; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type PostCallWithOptionalParamResponse = PostCallWithOptionalParamResponses[keyof PostCallWithOptionalParamResponses]; + +export type PostApiVbyApiVersionRequestBodyData = { + /** + * A reusable request body + */ + body?: SimpleRequestBody; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/requestBody'; +}; + +export type PostApiVbyApiVersionFormDataData = { + /** + * A reusable request body + */ + body?: SimpleFormData; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/formData'; +}; + +export type CallWithDefaultParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string with default value + */ + parameterString?: string | null; + /** + * This is a simple number with default value + */ + parameterNumber?: number | null; + /** + * This is a simple boolean with default value + */ + parameterBoolean?: boolean | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: ModelWithString | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallWithDefaultOptionalParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallToTestOrderOfParamsData = { + body?: never; + path?: never; + query: { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: string | null; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: string | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type DuplicateNameData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName2Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName3Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName4Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type CallWithNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no-content'; +}; + +export type CallWithNoContentResponseResponses = { + /** + * Success + */ + 204: void; +}; + +export type CallWithNoContentResponseResponse = CallWithNoContentResponseResponses[keyof CallWithNoContentResponseResponses]; + +export type CallWithResponseAndNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/response-and-no-content'; +}; + +export type CallWithResponseAndNoContentResponseResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type CallWithResponseAndNoContentResponseResponse = CallWithResponseAndNoContentResponseResponses[keyof CallWithResponseAndNoContentResponseResponses]; + +export type DummyAData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/a'; +}; + +export type DummyAResponses = { + 200: _400; +}; + +export type DummyAResponse = DummyAResponses[keyof DummyAResponses]; + +export type DummyBData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/b'; +}; + +export type DummyBResponses = { + /** + * Success + */ + 204: void; +}; + +export type DummyBResponse = DummyBResponses[keyof DummyBResponses]; + +export type CallWithResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponseResponses = { + default: Import; +}; + +export type CallWithResponseResponse = CallWithResponseResponses[keyof CallWithResponseResponses]; + +export type CallWithDuplicateResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithDuplicateResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for 4XX errors + */ + '4XX': DictionaryWithArray; + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type CallWithDuplicateResponsesError = CallWithDuplicateResponsesErrors[keyof CallWithDuplicateResponsesErrors]; + +export type CallWithDuplicateResponsesResponses = { + /** + * Message for 200 response + */ + 200: ModelWithBoolean & ModelWithInteger; + /** + * Message for 201 response + */ + 201: ModelWithString; + /** + * Message for 202 response + */ + 202: ModelWithString; +}; + +export type CallWithDuplicateResponsesResponse = CallWithDuplicateResponsesResponses[keyof CallWithDuplicateResponsesResponses]; + +export type CallWithResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for default response + */ + default: ModelWithStringError; +}; + +export type CallWithResponsesError = CallWithResponsesErrors[keyof CallWithResponsesErrors]; + +export type CallWithResponsesResponses = { + /** + * Message for 200 response + */ + 200: { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + }; + /** + * Message for 201 response + */ + 201: ModelThatExtends; + /** + * Message for 202 response + */ + 202: ModelThatExtendsExtends; +}; + +export type CallWithResponsesResponse = CallWithResponsesResponses[keyof CallWithResponsesResponses]; + +export type CollectionFormatData = { + body?: never; + path?: never; + query: { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCSV: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySSV: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTSV: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + }; + url: '/api/v{api-version}/collectionFormat'; +}; + +export type TypesData = { + body?: never; + path?: { + /** + * This is a number parameter + */ + id?: number; + }; + query: { + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is a string parameter + */ + parameterString: string | null; + /** + * This is a boolean parameter + */ + parameterBoolean: boolean | null; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + }; + url: '/api/v{api-version}/types'; +}; + +export type TypesResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Response is a simple string + */ + 201: string; + /** + * Response is a simple boolean + */ + 202: boolean; + /** + * Response is a simple object + */ + 203: { + [key: string]: unknown; + }; +}; + +export type TypesResponse = TypesResponses[keyof TypesResponses]; + +export type UploadFileData = { + body: Blob | File; + path: { + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query?: never; + url: '/api/v{api-version}/upload'; +}; + +export type UploadFileResponses = { + 200: boolean; +}; + +export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; + +export type FileResponseData = { + body?: never; + path: { + id: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/file/{id}'; +}; + +export type FileResponseResponses = { + /** + * Success + */ + 200: Blob | File; +}; + +export type FileResponseResponse = FileResponseResponses[keyof FileResponseResponses]; + +export type ComplexTypesData = { + body?: never; + path?: never; + query: { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; + }; + url: '/api/v{api-version}/complex'; +}; + +export type ComplexTypesErrors = { + /** + * 400 `server` error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type ComplexTypesResponses = { + /** + * Successful response + */ + 200: Array; +}; + +export type ComplexTypesResponse = ComplexTypesResponses[keyof ComplexTypesResponses]; + +export type MultipartResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type MultipartResponseResponses = { + /** + * OK + */ + 200: { + file?: Blob | File; + metadata?: { + foo?: string; + bar?: string; + }; + }; +}; + +export type MultipartResponseResponse = MultipartResponseResponses[keyof MultipartResponseResponses]; + +export type MultipartRequestData = { + body?: { + content?: Blob | File; + data?: ModelWithString | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type ComplexParamsData = { + body?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; + path: { + id: number; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/complex/{id}'; +}; + +export type ComplexParamsResponses = { + /** + * Success + */ + 200: ModelWithString; +}; + +export type ComplexParamsResponse = ComplexParamsResponses[keyof ComplexParamsResponses]; + +export type CallWithResultFromHeaderData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/header'; +}; + +export type CallWithResultFromHeaderErrors = { + /** + * 400 server error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type CallWithResultFromHeaderResponses = { + /** + * Successful response + */ + 200: unknown; +}; + +export type TestErrorCodeData = { + body?: never; + path?: never; + query: { + /** + * Status code to return + */ + status: number; + }; + url: '/api/v{api-version}/error'; +}; + +export type TestErrorCodeErrors = { + /** + * Custom message: Internal Server Error + */ + 500: unknown; + /** + * Custom message: Not Implemented + */ + 501: unknown; + /** + * Custom message: Bad Gateway + */ + 502: unknown; + /** + * Custom message: Service Unavailable + */ + 503: unknown; +}; + +export type TestErrorCodeResponses = { + /** + * Custom message: Successful response + */ + 200: unknown; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + body?: never; + path?: never; + query: { + /** + * Dummy input param + */ + nonAsciiParamæøåÆØÅöôêÊ: number; + }; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Responses = { + /** + * Successful response + */ + 200: Array; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = NonAsciiæøåÆøÅöôêÊ字符串Responses[keyof NonAsciiæøåÆøÅöôêÊ字符串Responses]; + +export type PutWithFormUrlEncodedData = { + body: ArrayWithStrings; + path?: never; + query?: never; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client.gen.ts new file mode 100644 index 000000000..8b954d012 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen.js'; +import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'http://localhost:3000/base' +})); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts new file mode 100644 index 000000000..c87004a03 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts @@ -0,0 +1,239 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen.js'; +import type { HttpMethod } from '../core/types.gen.js'; +import { getValidRequestBody } from '../core/utils.gen.js'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen.js'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen.js'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( + options as any, + ); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/index.ts new file mode 100644 index 000000000..99df3b851 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen.js'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen.js'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen.js'; +export { buildClientParams } from '../core/params.gen.js'; +export { createClient } from './client.gen.js'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen.js'; +export { createConfig, mergeHeaders } from './utils.gen.js'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts new file mode 100644 index 000000000..7c8270bf6 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen.js'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen.js'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen.js'; +import type { Middleware } from './utils.gen.js'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts new file mode 100644 index 000000000..0a3df5a87 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen.js'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen.js'; +import { jsonBodySerializer } from '../core/bodySerializer.gen.js'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen.js'; +import { getUrl } from '../core/utils.gen.js'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen.js'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions); + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/auth.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/bodySerializer.gen.ts new file mode 100644 index 000000000..b2f0d3829 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen.js'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/params.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..d73aa0f30 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen.js'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/types.gen.ts new file mode 100644 index 000000000..cc8a9e60f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen.js'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen.js'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/utils.gen.ts new file mode 100644 index 000000000..3029f7b3c --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen.js'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen.js'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/index.ts new file mode 100644 index 000000000..cea7f39ce --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen.js'; +export * from './sdk.gen.js'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/sdk.gen.ts new file mode 100644 index 000000000..d3cddea8e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/sdk.gen.ts @@ -0,0 +1,409 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type Options as ClientOptions, type Client, type TDataShape, formDataBodySerializer, urlSearchParamsBodySerializer } from './client'; +import type { ExportData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, ImportData, ImportResponses, FooWowData, FooWowResponses, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationResponses, GetApiVbyApiVersionSimpleOperationErrors, DeleteCallWithoutParametersAndResponseData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, OptionsCallWithoutParametersAndResponseData, PatchCallWithoutParametersAndResponseData, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, DeleteFooData3 as DeleteFooData, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostApiVbyApiVersionRequestBodyData, PostApiVbyApiVersionFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, DuplicateNameData, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, DummyAData, DummyAResponses, DummyBData, DummyBResponses, CallWithResponseData, CallWithResponseResponses, CallWithDuplicateResponsesData, CallWithDuplicateResponsesResponses, CallWithDuplicateResponsesErrors, CallWithResponsesData, CallWithResponsesResponses, CallWithResponsesErrors, CollectionFormatData, TypesData, TypesResponses, UploadFileData, UploadFileResponses, FileResponseData, FileResponseResponses, ComplexTypesData, ComplexTypesResponses, ComplexTypesErrors, MultipartResponseData, MultipartResponseResponses, MultipartRequestData, ComplexParamsData, ComplexParamsResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderResponses, CallWithResultFromHeaderErrors, TestErrorCodeData, TestErrorCodeResponses, TestErrorCodeErrors, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, PutWithFormUrlEncodedData } from './types.gen.js'; +import { client } from './client.gen.js'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +export const export_ = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const patchApiVbyApiVersionNoTag = (options?: Options) => { + return (options?.client ?? client).patch({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const import_ = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/no+tag', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const fooWow = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/no+tag', + ...options + }); +}; + +export const apiVVersionODataControllerCount = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/simple/$count', + ...options + }); +}; + +export const getApiVbyApiVersionSimpleOperation = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/simple:operation', + ...options + }); +}; + +export const deleteCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).delete({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const getCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const headCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).head({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const optionsCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).options({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const patchCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).patch({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const postCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const putCallWithoutParametersAndResponse = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/simple', + ...options + }); +}; + +export const deleteFoo = (options: Options) => { + return (options.client ?? client).delete({ + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', + ...options + }); +}; + +export const callWithDescriptions = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/descriptions', + ...options + }); +}; + +/** + * @deprecated + */ +export const deprecatedCall = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/deprecated', + ...options + }); +}; + +export const callWithParameters = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/{parameterPath}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const callWithWeirdParameterNames = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const getCallWithOptionalParam = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/parameters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postCallWithOptionalParam = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/parameters', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +export const postApiVbyApiVersionRequestBody = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/requestBody', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +export const postApiVbyApiVersionFormData = (options?: Options) => { + return (options?.client ?? client).post({ + ...formDataBodySerializer, + url: '/api/v{api-version}/formData', + ...options, + headers: { + 'Content-Type': null, + ...options?.headers + } + }); +}; + +export const callWithDefaultParameters = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const callWithDefaultOptionalParameters = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const callToTestOrderOfParams = (options: Options) => { + return (options.client ?? client).put({ + url: '/api/v{api-version}/defaults', + ...options + }); +}; + +export const duplicateName = (options?: Options) => { + return (options?.client ?? client).delete({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName2 = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName3 = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const duplicateName4 = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/duplicate', + ...options + }); +}; + +export const callWithNoContentResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/no-content', + ...options + }); +}; + +export const callWithResponseAndNoContentResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + ...options + }); +}; + +export const dummyA = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/a', + ...options + }); +}; + +export const dummyB = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multiple-tags/b', + ...options + }); +}; + +export const callWithResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const callWithDuplicateResponses = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const callWithResponses = (options?: Options) => { + return (options?.client ?? client).put({ + url: '/api/v{api-version}/response', + ...options + }); +}; + +export const collectionFormat = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/collectionFormat', + ...options + }); +}; + +export const types = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/types', + ...options + }); +}; + +export const uploadFile = (options: Options) => { + return (options.client ?? client).post({ + ...urlSearchParamsBodySerializer, + url: '/api/v{api-version}/upload', + ...options, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...options.headers + } + }); +}; + +export const fileResponse = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/file/{id}', + ...options + }); +}; + +export const complexTypes = (options: Options) => { + return (options.client ?? client).get({ + url: '/api/v{api-version}/complex', + ...options + }); +}; + +export const multipartResponse = (options?: Options) => { + return (options?.client ?? client).get({ + url: '/api/v{api-version}/multipart', + ...options + }); +}; + +export const multipartRequest = (options?: Options) => { + return (options?.client ?? client).post({ + ...formDataBodySerializer, + url: '/api/v{api-version}/multipart', + ...options, + headers: { + 'Content-Type': null, + ...options?.headers + } + }); +}; + +export const complexParams = (options: Options) => { + return (options.client ?? client).put({ + url: '/api/v{api-version}/complex/{id}', + ...options, + headers: { + 'Content-Type': 'application/json-patch+json', + ...options.headers + } + }); +}; + +export const callWithResultFromHeader = (options?: Options) => { + return (options?.client ?? client).post({ + url: '/api/v{api-version}/header', + ...options + }); +}; + +export const testErrorCode = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/error', + ...options + }); +}; + +export const nonAsciiæøåÆøÅöôêÊ字符串 = (options: Options) => { + return (options.client ?? client).post({ + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + ...options + }); +}; + +/** + * Login User + */ +export const putWithFormUrlEncoded = (options: Options) => { + return (options.client ?? client).put({ + ...urlSearchParamsBodySerializer, + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串', + ...options, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...options.headers + } + }); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/types.gen.ts new file mode 100644 index 000000000..4c755476d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/types.gen.ts @@ -0,0 +1,2065 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Model with number-only name + */ +export type _400 = string; + +/** + * External ref to shared model (A) + */ +export type ExternalRefA = ExternalSharedExternalSharedModel; + +/** + * External ref to shared model (B) + */ +export type ExternalRefB = ExternalSharedExternalSharedModel; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CamelCaseCommentWithBreaks = number; + +/** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ +export type CommentWithBreaks = number; + +/** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ +export type CommentWithBackticks = number; + +/** + * Testing backticks and quotes in string: `backticks`, 'quotes', "double quotes" and ```multiple backticks``` should work + */ +export type CommentWithBackticksAndQuotes = number; + +/** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ +export type CommentWithSlashes = number; + +/** + * Testing expression placeholders in string: ${expression} should work + */ +export type CommentWithExpressionPlaceholders = number; + +/** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ +export type CommentWithQuotes = number; + +/** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ +export type CommentWithReservedCharacters = number; + +/** + * This is a simple number + */ +export type SimpleInteger = number; + +/** + * This is a simple boolean + */ +export type SimpleBoolean = boolean; + +/** + * This is a simple string + */ +export type SimpleString = string; + +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆøÅöôêÊ字符串 = string; + +/** + * This is a simple file + */ +export type SimpleFile = Blob | File; + +/** + * This is a simple reference + */ +export type SimpleReference = ModelWithString; + +/** + * This is a simple string + */ +export type SimpleStringWithPattern = string | null; + +/** + * This is a simple enum with strings + */ +export type EnumWithStrings = 'Success' | 'Warning' | 'Error' | "'Single Quote'" | '"Double Quotes"' | 'Non-ascii: øæåôöØÆÅÔÖ字符串'; + +export type EnumWithReplacedCharacters = "'Single Quote'" | '"Double Quotes"' | 'øæåôöØÆÅÔÖ字符串' | 3.1 | ''; + +/** + * This is a simple enum with numbers + */ +export type EnumWithNumbers = 1 | 2 | 3 | 1.1 | 1.2 | 1.3 | 100 | 200 | 300 | -100 | -200 | -300 | -1.1 | -1.2 | -1.3; + +/** + * Success=1,Warning=2,Error=3 + */ +export type EnumFromDescription = number; + +/** + * This is a simple enum with numbers + */ +export type EnumWithExtensions = 200 | 400 | 500; + +export type EnumWithXEnumNames = 0 | 1 | 2; + +/** + * This is a simple array with numbers + */ +export type ArrayWithNumbers = Array; + +/** + * This is a simple array with booleans + */ +export type ArrayWithBooleans = Array; + +/** + * This is a simple array with strings + */ +export type ArrayWithStrings = Array; + +/** + * This is a simple array with references + */ +export type ArrayWithReferences = Array; + +/** + * This is a simple array containing an array + */ +export type ArrayWithArray = Array>; + +/** + * This is a simple array with properties + */ +export type ArrayWithProperties = Array<{ + '16x16'?: CamelCaseCommentWithBreaks; + bar?: string; +}>; + +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<{ + foo?: string; +} | { + bar?: string; +}>; + +export type AnyOfAnyAndNull = { + data?: unknown | null; +}; + +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<{ + foo?: string; + } | { + bar?: string; + }>; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithString = { + [key: string]: string; +}; + +export type DictionaryWithPropertiesAndAdditionalProperties = { + foo?: number; + bar?: boolean; + [key: string]: string | number | boolean | undefined; +}; + +/** + * This is a string reference + */ +export type DictionaryWithReference = { + [key: string]: ModelWithString; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithArray = { + [key: string]: Array; +}; + +/** + * This is a string dictionary + */ +export type DictionaryWithDictionary = { + [key: string]: { + [key: string]: string; + }; +}; + +/** + * This is a complex dictionary + */ +export type DictionaryWithProperties = { + [key: string]: { + foo?: string; + bar?: string; + }; +}; + +/** + * This is a model with one number property + */ +export type ModelWithInteger = { + /** + * This is a simple number property + */ + prop?: number; +}; + +/** + * This is a model with one boolean property + */ +export type ModelWithBoolean = { + /** + * This is a simple boolean property + */ + prop?: boolean; +}; + +/** + * This is a model with one string property + */ +export type ModelWithString = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * This is a model with one string property + */ +export type ModelWithStringError = { + /** + * This is a simple string property + */ + prop?: string; +}; + +/** + * `Comment` or `VoiceComment`. The JSON object for adding voice comments to tickets is different. See [Adding voice comments to tickets](/documentation/ticketing/managing-tickets/adding-voice-comments-to-tickets) + */ +export type ModelFromZendesk = string; + +/** + * This is a model with one string property + */ +export type ModelWithNullableString = { + /** + * This is a simple string property + */ + nullableProp1?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp1: string | null; + /** + * This is a simple string property + */ + nullableProp2?: string | null; + /** + * This is a simple string property + */ + nullableRequiredProp2: string | null; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnum = { + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; + /** + * These are the HTTP error code enums + */ + statusCode?: '100' | '200 FOO' | '300 FOO_BAR' | '400 foo-bar' | '500 foo.bar' | '600 foo&bar'; + /** + * Simple boolean enum + */ + bool?: true; +}; + +/** + * This is a model with one enum with escaped name + */ +export type ModelWithEnumWithHyphen = { + /** + * Foo-Bar-Baz-Qux + */ + 'foo-bar-baz-qux'?: '3.0'; +}; + +/** + * This is a model with one enum + */ +export type ModelWithEnumFromDescription = { + /** + * Success=1,Warning=2,Error=3 + */ + test?: number; +}; + +/** + * This is a model with nested enums + */ +export type ModelWithNestedEnums = { + dictionaryWithEnum?: { + [key: string]: 'Success' | 'Warning' | 'Error'; + }; + dictionaryWithEnumFromDescription?: { + [key: string]: number; + }; + arrayWithEnum?: Array<'Success' | 'Warning' | 'Error'>; + arrayWithDescription?: Array; + /** + * This is a simple enum with strings + */ + 'foo_bar-enum'?: 'Success' | 'Warning' | 'Error' | 'ØÆÅ字符串'; +}; + +/** + * This is a model with one property containing a reference + */ +export type ModelWithReference = { + prop?: ModelWithProperties; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArrayReadOnlyAndWriteOnly = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing an array + */ +export type ModelWithArray = { + prop?: Array; + propWithFile?: Array; + propWithNumber?: Array; +}; + +/** + * This is a model with one property containing a dictionary + */ +export type ModelWithDictionary = { + prop?: { + [key: string]: string; + }; +}; + +/** + * This is a deprecated model with a deprecated property + * @deprecated + */ +export type DeprecatedModel = { + /** + * This is a deprecated property + * @deprecated + */ + prop?: string; +}; + +/** + * This is a model with one property containing a circular reference + */ +export type ModelWithCircularReference = { + prop?: ModelWithCircularReference; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * Circle + */ +export type ModelCircle = { + kind: string; + radius?: number; +}; + +/** + * Square + */ +export type ModelSquare = { + kind: string; + sideLength?: number; +}; + +/** + * This is a model with one property with a 'one of' relationship where the options are not $ref + */ +export type CompositionWithOneOfDiscriminator = ({ + kind: 'circle'; +} & ModelCircle) | ({ + kind: 'square'; +} & ModelSquare); + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOf = { + propA?: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; +}; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithAnyOfAnonymous = { + propA?: { + propA?: string; + } | string | number; +}; + +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: Array | Array; +}; + +export type _3eNum1Период = 'Bird' | 'Dog'; + +export type ConstValue = 'ConstValue'; + +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + /** + * Scopes + */ + propA?: Array<_3eNum1Период | ConstValue> | null; +}; + +/** + * This is a model with one property with a 'one of' relationship + */ +export type CompositionWithOneOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a model that contains a simple dictionary within composition + */ +export type CompositionWithOneOfAndSimpleDictionary = { + propA?: boolean | { + [key: string]: number; + }; +}; + +/** + * This is a model that contains a dictionary of simple arrays within composition + */ +export type CompositionWithOneOfAndSimpleArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model that contains a dictionary of complex arrays (composited) within composition + */ +export type CompositionWithOneOfAndComplexArrayDictionary = { + propA?: boolean | { + [key: string]: Array; + }; +}; + +/** + * This is a model with one property with a 'all of' relationship + */ +export type CompositionWithAllOfAndNullable = { + propA?: ({ + boolean?: boolean; + } & ModelWithEnum & ModelWithArray & ModelWithDictionary) | null; +}; + +/** + * This is a model with one property with a 'any of' relationship + */ +export type CompositionWithAnyOfAndNullable = { + propA?: { + boolean?: boolean; + } | ModelWithEnum | ModelWithArray | ModelWithDictionary | null; +}; + +/** + * This is a base model with two simple optional properties + */ +export type CompositionBaseModel = { + firstName?: string; + lastname?: string; +}; + +/** + * This is a model that extends the base model + */ +export type CompositionExtendedModel = CompositionBaseModel & { + age: number; + firstName: string; + lastname: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithProperties = { + required: string; + readonly requiredAndReadOnly: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithNestedProperties = { + readonly first: { + readonly second: { + readonly third: string | null; + } | null; + } | null; +}; + +/** + * This is a model with duplicated properties + */ +export type ModelWithDuplicateProperties = { + prop?: ModelWithString; +}; + +/** + * This is a model with ordered properties + */ +export type ModelWithOrderedProperties = { + zebra?: string; + apple?: string; + hawaii?: string; +}; + +/** + * This is a model with duplicated imports + */ +export type ModelWithDuplicateImports = { + propA?: ModelWithString; + propB?: ModelWithString; + propC?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtends = ModelWithString & { + propExtendsA?: string; + propExtendsB?: ModelWithString; +}; + +/** + * This is a model that extends another model + */ +export type ModelThatExtendsExtends = ModelWithString & ModelThatExtends & { + propExtendsC?: string; + propExtendsD?: ModelWithString; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPattern = { + key: string; + name: string; + readonly enabled?: boolean; + readonly modified?: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type File = { + /** + * Id + */ + readonly id?: string; + /** + * Updated at + */ + readonly updated_at?: string; + /** + * Created at + */ + readonly created_at?: string; + /** + * Mime + */ + mime: string; + /** + * File + */ + readonly file?: string; +}; + +export type Default = { + name?: string; +}; + +export type Pageable = { + page?: number; + size?: number; + sort?: Array; +}; + +/** + * This is a free-form object without additionalProperties. + */ +export type FreeFormObjectWithoutAdditionalProperties = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: true. + */ +export type FreeFormObjectWithAdditionalPropertiesEqTrue = { + [key: string]: unknown; +}; + +/** + * This is a free-form object with additionalProperties: {}. + */ +export type FreeFormObjectWithAdditionalPropertiesEqEmptyObject = { + [key: string]: unknown; +}; + +export type ModelWithConst = { + String?: 'String'; + number?: 0; + null?: null; + withType?: 'Some string'; +}; + +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: unknown | string | undefined; +}; + +export type NestedAnyOfArraysNullable = { + nullableArray?: Array | null; +}; + +export type CompositionWithOneOfAndProperties = ({ + foo: SimpleParameter; +} | { + bar: NonAsciiStringæøåÆøÅöôêÊ字符串; +}) & { + baz: number | null; + qux: number; +}; + +/** + * An object that can be null + */ +export type NullableObject = { + foo?: string; +} | null; + +/** + * Some % character + */ +export type CharactersInDescription = string; + +export type ModelWithNullableObject = { + data?: NullableObject; +}; + +export type ModelWithOneOfEnum = { + foo: 'Bar'; +} | { + foo: 'Baz'; +} | { + foo: 'Qux'; +} | { + content: string; + foo: 'Quux'; +} | { + content: [ + string, + string + ]; + foo: 'Corge'; +}; + +export type ModelWithNestedArrayEnumsDataFoo = 'foo' | 'bar'; + +export type ModelWithNestedArrayEnumsDataBar = 'baz' | 'qux'; + +export type ModelWithNestedArrayEnumsData = { + foo?: Array; + bar?: Array; +}; + +export type ModelWithNestedArrayEnums = { + array_strings?: Array; + data?: ModelWithNestedArrayEnumsData; +}; + +export type ModelWithNestedCompositionEnums = { + foo?: ModelWithNestedArrayEnumsDataFoo; +}; + +export type ModelWithReadOnlyAndWriteOnly = { + foo: string; + readonly bar: string; +}; + +export type ModelWithConstantSizeArray = [ + number, + number +]; + +export type ModelWithAnyOfConstantSizeArray = [ + number | string, + number | string, + number | string +]; + +export type ModelWithPrefixItemsConstantSizeArray = [ + ModelWithInteger, + number | string, + string +]; + +export type ModelWithAnyOfConstantSizeArrayNullable = [ + number | null | string, + number | null | string, + number | null | string +]; + +export type ModelWithAnyOfConstantSizeArrayWithNSizeAndOptions = [ + number | Import, + number | Import +]; + +export type ModelWithAnyOfConstantSizeArrayAndIntersect = [ + number & string, + number & string +]; + +export type ModelWithNumericEnumUnion = { + /** + * Период + */ + value?: -10 | -1 | 0 | 1 | 3 | 6 | 12; +}; + +/** + * Some description with `back ticks` + */ +export type ModelWithBackticksInDescription = { + /** + * The template `that` should be used for parsing and importing the contents of the CSV file. + * + *

There is one placeholder currently supported:

  • ${x} - refers to the n-th column in the CSV file, e.g. ${1}, ${2}, ...)

Example of a correct JSON template:

+ *
+     * [
+     * {
+     * "resourceType": "Asset",
+     * "identifier": {
+     * "name": "${1}",
+     * "domain": {
+     * "name": "${2}",
+     * "community": {
+     * "name": "Some Community"
+     * }
+     * }
+     * },
+     * "attributes" : {
+     * "00000000-0000-0000-0000-000000003115" : [ {
+     * "value" : "${3}"
+     * } ],
+     * "00000000-0000-0000-0000-000000000222" : [ {
+     * "value" : "${4}"
+     * } ]
+     * }
+     * }
+     * ]
+     * 
+ */ + template?: string; +}; + +export type ModelWithOneOfAndProperties = (SimpleParameter | NonAsciiStringæøåÆøÅöôêÊ字符串) & { + baz: number | null; + qux: number; +}; + +/** + * Model used to test deduplication strategy (unused) + */ +export type ParameterSimpleParameterUnused = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse = string; + +/** + * Model used to test deduplication strategy + */ +export type PostServiceWithEmptyTagResponse2 = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData = string; + +/** + * Model used to test deduplication strategy + */ +export type DeleteFooData2 = string; + +/** + * Model with restricted keyword name + */ +export type Import = string; + +export type SchemaWithFormRestrictedKeys = { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + object?: { + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }; + array?: Array<{ + description?: string; + 'x-enum-descriptions'?: string; + 'x-enum-varnames'?: string; + 'x-enumNames'?: string; + title?: string; + }>; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1DeleteOptions = { + /** + * Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned. + */ + preconditions?: IoK8sApimachineryPkgApisMetaV1Preconditions; +}; + +/** + * This schema was giving PascalCase transformations a hard time + */ +export type IoK8sApimachineryPkgApisMetaV1Preconditions = { + /** + * Specifies the target ResourceVersion + */ + resourceVersion?: string; + /** + * Specifies the target UID. + */ + uid?: string; +}; + +export type AdditionalPropertiesUnknownIssue = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue2 = { + [key: string]: string | number; +}; + +export type AdditionalPropertiesUnknownIssue3 = string & { + entries: { + [key: string]: AdditionalPropertiesUnknownIssue; + }; +}; + +export type AdditionalPropertiesIntegerIssue = { + value: number; + [key: string]: number; +}; + +export type OneOfAllOfIssue = ((ConstValue | GenericSchemaDuplicateIssue1SystemBoolean) & _3eNum1Период) | GenericSchemaDuplicateIssue1SystemString; + +export type GenericSchemaDuplicateIssue1SystemBoolean = { + item?: boolean; + error?: string | null; + readonly hasError?: boolean; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemString = { + item?: string | null; + error?: string | null; + readonly hasError?: boolean; +}; + +export type ExternalSharedExternalSharedModel = { + id: string; + name?: string; +}; + +/** + * This is a model with one nested property + */ +export type ModelWithPropertiesWritable = { + required: string; + requiredAndNullable: string | null; + string?: string; + number?: number; + boolean?: boolean; + reference?: ModelWithString; + 'property with space'?: string; + default?: string; + try?: string; +}; + +/** + * This is a model that contains a some patterns + */ +export type ModelWithPatternWritable = { + key: string; + name: string; + id?: string; + text?: string; + patternWithSingleQuotes?: string; + patternWithNewline?: string; + patternWithBacktick?: string; +}; + +export type FileWritable = { + /** + * Mime + */ + mime: string; +}; + +export type ModelWithReadOnlyAndWriteOnlyWritable = { + foo: string; + baz: string; +}; + +export type AdditionalPropertiesUnknownIssueWritable = { + [key: string]: string | number; +}; + +export type GenericSchemaDuplicateIssue1SystemBooleanWritable = { + item?: boolean; + error?: string | null; + data?: { + [key: string]: never; + }; +}; + +export type GenericSchemaDuplicateIssue1SystemStringWritable = { + item?: string | null; + error?: string | null; +}; + +/** + * This is a reusable parameter + */ +export type SimpleParameter = string; + +/** + * Parameter with illegal characters + */ +export type XFooBar = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleRequestBody = ModelWithString; + +/** + * A reusable request body + */ +export type SimpleFormData = ModelWithString; + +export type ExportData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type PatchApiVbyApiVersionNoTagResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ImportData = { + body: ModelWithReadOnlyAndWriteOnlyWritable | ModelWithArrayReadOnlyAndWriteOnly; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type ImportResponses = { + /** + * Success + */ + 200: ModelFromZendesk; + /** + * Default success response + */ + default: ModelWithReadOnlyAndWriteOnly; +}; + +export type ImportResponse = ImportResponses[keyof ImportResponses]; + +export type FooWowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no+tag'; +}; + +export type FooWowResponses = { + /** + * OK + */ + default: unknown; +}; + +export type ApiVVersionODataControllerCountData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple/$count'; +}; + +export type ApiVVersionODataControllerCountResponses = { + /** + * Success + */ + 200: ModelFromZendesk; +}; + +export type ApiVVersionODataControllerCountResponse = ApiVVersionODataControllerCountResponses[keyof ApiVVersionODataControllerCountResponses]; + +export type GetApiVbyApiVersionSimpleOperationData = { + body?: never; + path: { + /** + * foo in method + */ + foo_param: string; + }; + query?: never; + url: '/api/v{api-version}/simple:operation'; +}; + +export type GetApiVbyApiVersionSimpleOperationErrors = { + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type GetApiVbyApiVersionSimpleOperationError = GetApiVbyApiVersionSimpleOperationErrors[keyof GetApiVbyApiVersionSimpleOperationErrors]; + +export type GetApiVbyApiVersionSimpleOperationResponses = { + /** + * Response is a simple number + */ + 200: number; +}; + +export type GetApiVbyApiVersionSimpleOperationResponse = GetApiVbyApiVersionSimpleOperationResponses[keyof GetApiVbyApiVersionSimpleOperationResponses]; + +export type DeleteCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type GetCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type HeadCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type OptionsCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PatchCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PostCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type PutCallWithoutParametersAndResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/simple'; +}; + +export type DeleteFooData3 = { + body?: never; + headers: { + /** + * Parameter with illegal characters + */ + 'x-Foo-Bar': ModelWithString; + }; + path: { + /** + * foo in method + */ + foo_param: string; + /** + * bar in method + */ + BarParam: string; + }; + query?: never; + url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}'; +}; + +export type CallWithDescriptionsData = { + body?: never; + path?: never; + query?: { + /** + * Testing multiline comments in string: First line + * Second line + * + * Fourth line + */ + parameterWithBreaks?: string; + /** + * Testing backticks in string: `backticks` and ```multiple backticks``` should work + */ + parameterWithBackticks?: string; + /** + * Testing slashes in string: \backwards\\\ and /forwards/// should work + */ + parameterWithSlashes?: string; + /** + * Testing expression placeholders in string: ${expression} should work + */ + parameterWithExpressionPlaceholders?: string; + /** + * Testing quotes in string: 'single quote''' and "double quotes""" should work + */ + parameterWithQuotes?: string; + /** + * Testing reserved characters in string: * inline * and ** inline ** should work + */ + parameterWithReservedCharacters?: string; + }; + url: '/api/v{api-version}/descriptions'; +}; + +export type DeprecatedCallData = { + body?: never; + headers: { + /** + * This parameter is deprecated + * @deprecated + */ + parameter: DeprecatedModel | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/parameters/deprecated'; +}; + +export type CallWithParametersData = { + /** + * This is the parameter that goes into the body + */ + body: { + [key: string]: unknown; + } | null; + headers: { + /** + * This is the parameter that goes into the header + */ + parameterHeader: string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + parameterPath: string | null; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + foo_ref_enum?: ModelWithNestedArrayEnumsDataFoo; + foo_all_of_enum: ModelWithNestedArrayEnumsDataFoo; + /** + * This is the parameter that goes into the query params + */ + cursor: string | null; + }; + url: '/api/v{api-version}/parameters/{parameterPath}'; +}; + +export type CallWithWeirdParameterNamesData = { + /** + * This is the parameter that goes into the body + */ + body: ModelWithString | null; + headers: { + /** + * This is the parameter that goes into the request header + */ + 'parameter.header': string | null; + }; + path: { + /** + * This is the parameter that goes into the path + */ + 'parameter.path.1'?: string; + /** + * This is the parameter that goes into the path + */ + 'parameter-path-2'?: string; + /** + * This is the parameter that goes into the path + */ + 'PARAMETER-PATH-3'?: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query: { + /** + * This is the parameter with a reserved keyword + */ + default?: string; + /** + * This is the parameter that goes into the request query params + */ + 'parameter-query': string | null; + }; + url: '/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}'; +}; + +export type GetCallWithOptionalParamData = { + /** + * This is a required parameter + */ + body: ModelWithOneOfEnum; + path?: never; + query?: { + /** + * This is an optional parameter + */ + page?: number; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamData = { + /** + * This is an optional parameter + */ + body?: { + offset?: number | null; + }; + path?: never; + query: { + /** + * This is a required parameter + */ + parameter: Pageable; + }; + url: '/api/v{api-version}/parameters'; +}; + +export type PostCallWithOptionalParamResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type PostCallWithOptionalParamResponse = PostCallWithOptionalParamResponses[keyof PostCallWithOptionalParamResponses]; + +export type PostApiVbyApiVersionRequestBodyData = { + /** + * A reusable request body + */ + body?: SimpleRequestBody; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/requestBody'; +}; + +export type PostApiVbyApiVersionFormDataData = { + /** + * A reusable request body + */ + body?: SimpleFormData; + path?: never; + query?: { + /** + * This is a reusable parameter + */ + parameter?: string; + }; + url: '/api/v{api-version}/formData'; +}; + +export type CallWithDefaultParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string with default value + */ + parameterString?: string | null; + /** + * This is a simple number with default value + */ + parameterNumber?: number | null; + /** + * This is a simple boolean with default value + */ + parameterBoolean?: boolean | null; + /** + * This is a simple enum with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model with default value + */ + parameterModel?: ModelWithString | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallWithDefaultOptionalParametersData = { + body?: never; + path?: never; + query?: { + /** + * This is a simple string that is optional with default value + */ + parameterString?: string; + /** + * This is a simple number that is optional with default value + */ + parameterNumber?: number; + /** + * This is a simple boolean that is optional with default value + */ + parameterBoolean?: boolean; + /** + * This is a simple enum that is optional with default value + */ + parameterEnum?: 'Success' | 'Warning' | 'Error'; + /** + * This is a simple model that is optional with default value + */ + parameterModel?: ModelWithString; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type CallToTestOrderOfParamsData = { + body?: never; + path?: never; + query: { + /** + * This is a optional string with default + */ + parameterOptionalStringWithDefault?: string; + /** + * This is a optional string with empty default + */ + parameterOptionalStringWithEmptyDefault?: string; + /** + * This is a optional string with no default + */ + parameterOptionalStringWithNoDefault?: string; + /** + * This is a string with default + */ + parameterStringWithDefault: string; + /** + * This is a string with empty default + */ + parameterStringWithEmptyDefault: string; + /** + * This is a string with no default + */ + parameterStringWithNoDefault: string; + /** + * This is a string that can be null with no default + */ + parameterStringNullableWithNoDefault?: string | null; + /** + * This is a string that can be null with default + */ + parameterStringNullableWithDefault?: string | null; + }; + url: '/api/v{api-version}/defaults'; +}; + +export type DuplicateNameData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName2Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName3Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type DuplicateName4Data = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/duplicate'; +}; + +export type CallWithNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/no-content'; +}; + +export type CallWithNoContentResponseResponses = { + /** + * Success + */ + 204: void; +}; + +export type CallWithNoContentResponseResponse = CallWithNoContentResponseResponses[keyof CallWithNoContentResponseResponses]; + +export type CallWithResponseAndNoContentResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/response-and-no-content'; +}; + +export type CallWithResponseAndNoContentResponseResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Success + */ + 204: void; +}; + +export type CallWithResponseAndNoContentResponseResponse = CallWithResponseAndNoContentResponseResponses[keyof CallWithResponseAndNoContentResponseResponses]; + +export type DummyAData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/a'; +}; + +export type DummyAResponses = { + 200: _400; +}; + +export type DummyAResponse = DummyAResponses[keyof DummyAResponses]; + +export type DummyBData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multiple-tags/b'; +}; + +export type DummyBResponses = { + /** + * Success + */ + 204: void; +}; + +export type DummyBResponse = DummyBResponses[keyof DummyBResponses]; + +export type CallWithResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponseResponses = { + default: Import; +}; + +export type CallWithResponseResponse = CallWithResponseResponses[keyof CallWithResponseResponses]; + +export type CallWithDuplicateResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithDuplicateResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for 4XX errors + */ + '4XX': DictionaryWithArray; + /** + * Default error response + */ + default: ModelWithBoolean; +}; + +export type CallWithDuplicateResponsesError = CallWithDuplicateResponsesErrors[keyof CallWithDuplicateResponsesErrors]; + +export type CallWithDuplicateResponsesResponses = { + /** + * Message for 200 response + */ + 200: ModelWithBoolean & ModelWithInteger; + /** + * Message for 201 response + */ + 201: ModelWithString; + /** + * Message for 202 response + */ + 202: ModelWithString; +}; + +export type CallWithDuplicateResponsesResponse = CallWithDuplicateResponsesResponses[keyof CallWithDuplicateResponsesResponses]; + +export type CallWithResponsesData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/response'; +}; + +export type CallWithResponsesErrors = { + /** + * Message for 500 error + */ + 500: ModelWithStringError; + /** + * Message for 501 error + */ + 501: ModelWithStringError; + /** + * Message for 502 error + */ + 502: ModelWithStringError; + /** + * Message for default response + */ + default: ModelWithStringError; +}; + +export type CallWithResponsesError = CallWithResponsesErrors[keyof CallWithResponsesErrors]; + +export type CallWithResponsesResponses = { + /** + * Message for 200 response + */ + 200: { + readonly '@namespace.string'?: string; + readonly '@namespace.integer'?: number; + readonly value?: Array; + }; + /** + * Message for 201 response + */ + 201: ModelThatExtends; + /** + * Message for 202 response + */ + 202: ModelThatExtendsExtends; +}; + +export type CallWithResponsesResponse = CallWithResponsesResponses[keyof CallWithResponsesResponses]; + +export type CollectionFormatData = { + body?: never; + path?: never; + query: { + /** + * This is an array parameter that is sent as csv format (comma-separated values) + */ + parameterArrayCSV: Array | null; + /** + * This is an array parameter that is sent as ssv format (space-separated values) + */ + parameterArraySSV: Array | null; + /** + * This is an array parameter that is sent as tsv format (tab-separated values) + */ + parameterArrayTSV: Array | null; + /** + * This is an array parameter that is sent as pipes format (pipe-separated values) + */ + parameterArrayPipes: Array | null; + /** + * This is an array parameter that is sent as multi format (multiple parameter instances) + */ + parameterArrayMulti: Array | null; + }; + url: '/api/v{api-version}/collectionFormat'; +}; + +export type TypesData = { + body?: never; + path?: { + /** + * This is a number parameter + */ + id?: number; + }; + query: { + /** + * This is a number parameter + */ + parameterNumber: number; + /** + * This is a string parameter + */ + parameterString: string | null; + /** + * This is a boolean parameter + */ + parameterBoolean: boolean | null; + /** + * This is an object parameter + */ + parameterObject: { + [key: string]: unknown; + } | null; + /** + * This is an array parameter + */ + parameterArray: Array | null; + /** + * This is a dictionary parameter + */ + parameterDictionary: { + [key: string]: unknown; + } | null; + /** + * This is an enum parameter + */ + parameterEnum: 'Success' | 'Warning' | 'Error' | null; + }; + url: '/api/v{api-version}/types'; +}; + +export type TypesResponses = { + /** + * Response is a simple number + */ + 200: number; + /** + * Response is a simple string + */ + 201: string; + /** + * Response is a simple boolean + */ + 202: boolean; + /** + * Response is a simple object + */ + 203: { + [key: string]: unknown; + }; +}; + +export type TypesResponse = TypesResponses[keyof TypesResponses]; + +export type UploadFileData = { + body: Blob | File; + path: { + /** + * api-version should be required in standalone clients + */ + 'api-version': string | null; + }; + query?: never; + url: '/api/v{api-version}/upload'; +}; + +export type UploadFileResponses = { + 200: boolean; +}; + +export type UploadFileResponse = UploadFileResponses[keyof UploadFileResponses]; + +export type FileResponseData = { + body?: never; + path: { + id: string; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/file/{id}'; +}; + +export type FileResponseResponses = { + /** + * Success + */ + 200: Blob | File; +}; + +export type FileResponseResponse = FileResponseResponses[keyof FileResponseResponses]; + +export type ComplexTypesData = { + body?: never; + path?: never; + query: { + /** + * Parameter containing object + */ + parameterObject: { + first?: { + second?: { + third?: string; + }; + }; + }; + /** + * Parameter containing reference + */ + parameterReference: ModelWithString; + }; + url: '/api/v{api-version}/complex'; +}; + +export type ComplexTypesErrors = { + /** + * 400 `server` error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type ComplexTypesResponses = { + /** + * Successful response + */ + 200: Array; +}; + +export type ComplexTypesResponse = ComplexTypesResponses[keyof ComplexTypesResponses]; + +export type MultipartResponseData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type MultipartResponseResponses = { + /** + * OK + */ + 200: { + file?: Blob | File; + metadata?: { + foo?: string; + bar?: string; + }; + }; +}; + +export type MultipartResponseResponse = MultipartResponseResponses[keyof MultipartResponseResponses]; + +export type MultipartRequestData = { + body?: { + content?: Blob | File; + data?: ModelWithString | null; + }; + path?: never; + query?: never; + url: '/api/v{api-version}/multipart'; +}; + +export type ComplexParamsData = { + body?: { + readonly key: string | null; + name: string | null; + enabled?: boolean; + type: 'Monkey' | 'Horse' | 'Bird'; + listOfModels?: Array | null; + listOfStrings?: Array | null; + parameters: ModelWithString | ModelWithEnum | ModelWithArray | ModelWithDictionary; + readonly user?: { + readonly id?: number; + readonly name?: string | null; + }; + }; + path: { + id: number; + /** + * api-version should be required in standalone clients + */ + 'api-version': string; + }; + query?: never; + url: '/api/v{api-version}/complex/{id}'; +}; + +export type ComplexParamsResponses = { + /** + * Success + */ + 200: ModelWithString; +}; + +export type ComplexParamsResponse = ComplexParamsResponses[keyof ComplexParamsResponses]; + +export type CallWithResultFromHeaderData = { + body?: never; + path?: never; + query?: never; + url: '/api/v{api-version}/header'; +}; + +export type CallWithResultFromHeaderErrors = { + /** + * 400 server error + */ + 400: unknown; + /** + * 500 server error + */ + 500: unknown; +}; + +export type CallWithResultFromHeaderResponses = { + /** + * Successful response + */ + 200: unknown; +}; + +export type TestErrorCodeData = { + body?: never; + path?: never; + query: { + /** + * Status code to return + */ + status: number; + }; + url: '/api/v{api-version}/error'; +}; + +export type TestErrorCodeErrors = { + /** + * Custom message: Internal Server Error + */ + 500: unknown; + /** + * Custom message: Not Implemented + */ + 501: unknown; + /** + * Custom message: Bad Gateway + */ + 502: unknown; + /** + * Custom message: Service Unavailable + */ + 503: unknown; +}; + +export type TestErrorCodeResponses = { + /** + * Custom message: Successful response + */ + 200: unknown; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Data = { + body?: never; + path?: never; + query: { + /** + * Dummy input param + */ + nonAsciiParamæøåÆØÅöôêÊ: number; + }; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Responses = { + /** + * Successful response + */ + 200: Array; +}; + +export type NonAsciiæøåÆøÅöôêÊ字符串Response = NonAsciiæøåÆøÅöôêÊ字符串Responses[keyof NonAsciiæøåÆøÅöôêÊ字符串Responses]; + +export type PutWithFormUrlEncodedData = { + body: ArrayWithStrings; + path?: never; + query?: never; + url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; +}; + +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client.gen.ts new file mode 100644 index 000000000..fe57f118d --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig()); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts new file mode 100644 index 000000000..cdc57e116 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts @@ -0,0 +1,239 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { ofetch, type ResponseType as OfetchResponseType } from 'ofetch'; + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from './types.gen'; +import { + buildOfetchOptions, + buildUrl, + createConfig, + createInterceptors, + isRepeatableBody, + mapParseAsToResponseType, + mergeConfigs, + mergeHeaders, + parseError, + parseSuccess, + setAuthParams, + wrapDataReturn, + wrapErrorReturn, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: BodyInit | null | undefined; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); + + // Resolve final options, serialized body, network body and URL + const resolveOptions = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + // Precompute network body for retries and consistent handling + const networkBody = getValidRequestBody(opts) as + | RequestInit['body'] + | null + | undefined; + + const url = buildUrl(opts); + + return { networkBody, opts, url }; + }; + + // Apply request interceptors to a Request and reflect header/method/signal + const applyRequestInterceptors = async ( + request: Request, + opts: ResolvedRequestOptions, + ) => { + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + // Reflect any interceptor changes into opts used for network and downstream + opts.headers = request.headers; + opts.method = request.method as Uppercase; + // Note: we intentionally ignore request.body changes from interceptors to + // avoid turning serialized bodies into streams. Body is sourced solely + // from getValidRequestBody(options) for consistency. + // Attempt to reflect possible signal changes + opts.signal = (request as any).signal as AbortSignal | undefined; + return request; + }; + + // Build ofetch options with stable retry logic based on body repeatability + const buildNetworkOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + ) => { + const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + return buildOfetchOptions(opts, body, responseType, effectiveRetry); + }; + + const request: Client['request'] = async (options) => { + const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( + options as any, + ); + // Compute response type mapping once + const ofetchResponseType: OfetchResponseType | undefined = + mapParseAsToResponseType(opts.parseAs, opts.responseType); + + const $ofetch = opts.ofetch ?? ofetch; + + // Always create Request pre-network (align with client-fetch) + const networkBody = initialNetworkBody; + const requestInit: ReqInit = { + body: networkBody, + headers: opts.headers as Headers, + method: opts.method, + redirect: 'follow', + signal: opts.signal, + }; + let request = new Request(url, requestInit); + + request = await applyRequestInterceptors(request, opts); + const finalUrl = request.url; + + // Build ofetch options and perform the request + const responseOptions = buildNetworkOptions( + opts as ResolvedRequestOptions, + networkBody, + ofetchResponseType, + ); + + let response = await $ofetch.raw(finalUrl, responseOptions); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { request, response }; + + if (response.ok) { + const data = await parseSuccess(response, opts, ofetchResponseType); + return wrapDataReturn(data, result, opts.responseStyle); + } + + let finalError = await parseError(response); + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, opts); + } + } + + // Ensure error is never undefined after interceptors + finalError = (finalError as any) || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + return wrapErrorReturn(finalError, result, opts.responseStyle) as any; + }; + + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method } as any); + + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { networkBody, opts, url } = await resolveOptions(options); + const optsForSse: any = { ...opts }; + delete optsForSse.body; + return createSseClient({ + ...optsForSse, + fetch: opts.fetch, + headers: opts.headers as Headers, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + request = await applyRequestInterceptors(request, opts); + return request; + }, + serializedBody: networkBody as BodyInit | null | undefined, + signal: opts.signal, + url, + }); + }; + + return { + buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/index.ts new file mode 100644 index 000000000..318a84b6a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/index.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts new file mode 100644 index 000000000..e4925b81b --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts @@ -0,0 +1,300 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; +import type { ofetch } from 'ofetch'; + +import type { Auth } from '../core/auth.gen'; +import type { + ServerSentEventsOptions, + ServerSentEventsResult, +} from '../core/serverSentEvents.gen'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + agent?: OfetchOptions['agent']; + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** Node-only proxy/agent options */ + dispatcher?: OfetchOptions['dispatcher']; + /** Optional fetch instance used for SSE streaming */ + fetch?: typeof fetch; + // No custom fetch option: provide custom instance via `ofetch` instead + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Custom ofetch instance created via `ofetch.create()`. If provided, it will + * be used for requests instead of the default `ofetch` export. + */ + ofetch?: typeof ofetch; + /** ofetch interceptors and runtime options */ + onRequest?: OfetchOptions['onRequest']; + onRequestError?: OfetchOptions['onRequestError']; + onResponse?: OfetchOptions['onResponse']; + onResponseError?: OfetchOptions['onResponseError']; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** Custom response parser (ofetch). */ + parseResponse?: OfetchOptions['parseResponse']; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * ofetch responseType override. If provided, it will be passed directly to + * ofetch and take precedence over `parseAs`. + */ + responseType?: OfetchResponseType; + /** + * Automatically retry failed requests. + */ + retry?: OfetchOptions['retry']; + retryDelay?: OfetchOptions['retryDelay']; + retryStatusCodes?: OfetchOptions['retryStatusCodes']; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; + /** + * Abort the request after the given milliseconds. + */ + timeout?: number; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | 'onSseError' + | 'onSseEvent' + | 'sseDefaultRetryDelay' + | 'sseMaxRetryAttempts' + | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type SseFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick< + Required>, + 'method' + >, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient< + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn +> & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys< + RequestOptions, + 'body' | 'url' + > & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & + TData; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts new file mode 100644 index 000000000..e54e85704 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + FetchOptions as OfetchOptions, + ResponseType as OfetchResponseType, +} from 'ofetch'; + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { + Client, + ClientOptions, + Config, + RequestOptions, + ResolvedRequestOptions, + ResponseStyle, +} from './types.gen'; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +/** + * Map our parseAs value to ofetch responseType when not explicitly provided. + */ +export const mapParseAsToResponseType = ( + parseAs: Config['parseAs'] | undefined, + explicit?: OfetchResponseType, +): OfetchResponseType | undefined => { + if (explicit) return explicit; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'json': + case 'text': + case 'stream': + return parseAs; + case 'formData': + case 'auto': + default: + return undefined; // let ofetch auto-detect + } +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string, +): boolean => { + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get('Cookie')?.includes(`${name}=`) + ) { + return true; + } + return false; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +/** + * Heuristic to detect whether a request body can be safely retried. + */ +export const isRepeatableBody = (body: unknown): boolean => { + if (body == null) return true; // undefined/null treated as no-body + if (typeof body === 'string') return true; + if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) + return true; + if (typeof Uint8Array !== 'undefined' && body instanceof Uint8Array) + return true; + if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) + return true; + if (typeof Blob !== 'undefined' && body instanceof Blob) return true; + if (typeof FormData !== 'undefined' && body instanceof FormData) return true; + // Streams are not repeatable + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) + return false; + // Default: assume non-repeatable for unknown structured bodies + return false; +}; + +/** + * Small helper to unify data vs fields return style. + */ +export const wrapDataReturn = ( + data: T, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | T + | ((T extends Record ? { data: T } : { data: T }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? (data as any) + : ({ data, ...result } as any); + +/** + * Small helper to unify error vs fields return style. + */ +export const wrapErrorReturn = ( + error: E, + result: { request: Request; response: Response }, + responseStyle: ResponseStyle | undefined, +): + | undefined + | ((E extends Record ? { error: E } : { error: E }) & + typeof result) => + (responseStyle ?? 'fields') === 'data' + ? undefined + : ({ error, ...result } as any); + +/** + * Build options for $ofetch.raw from our resolved opts and body. + */ +export const buildOfetchOptions = ( + opts: ResolvedRequestOptions, + body: BodyInit | null | undefined, + responseType: OfetchResponseType | undefined, + retryOverride?: OfetchOptions['retry'], +): OfetchOptions => ({ + agent: opts.agent as OfetchOptions['agent'], + body, + dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], + headers: opts.headers as Headers, + method: opts.method, + onRequest: opts.onRequest as OfetchOptions['onRequest'], + onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], + onResponse: opts.onResponse as OfetchOptions['onResponse'], + onResponseError: opts.onResponseError as OfetchOptions['onResponseError'], + parseResponse: opts.parseResponse as OfetchOptions['parseResponse'], + // URL already includes query + query: undefined, + responseType, + retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], + retryStatusCodes: + opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], + signal: opts.signal, + timeout: opts.timeout as number | undefined, + } as OfetchOptions); + +/** + * Parse a successful response, handling empty bodies and stream cases. + */ +export const parseSuccess = async ( + response: Response, + opts: ResolvedRequestOptions, + ofetchResponseType?: OfetchResponseType, +): Promise => { + // Stream requested: return stream body + if (ofetchResponseType === 'stream') { + return response.body; + } + + const inferredParseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + // Handle empty responses + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + return await (response as any)[inferredParseAs](); + case 'formData': + return new FormData(); + case 'stream': + return response.body; + default: + return {}; + } + } + + // Prefer ofetch-populated data + let data: unknown = (response as any)._data; + if (typeof data === 'undefined') { + switch (inferredParseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await (response as any)[inferredParseAs](); + break; + case 'stream': + return response.body; + } + } + + if (inferredParseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return data; +}; + +/** + * Parse an error response payload. + */ +export const parseError = async (response: Response): Promise => { + let error: unknown = (response as any)._data; + if (typeof error === 'undefined') { + const textError = await response.text(); + try { + error = JSON.parse(textError); + } catch { + error = textError; + } + } + return error ?? ({} as string); +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware< + Req, + Res, + Err, + Options +> => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/auth.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/auth.gen.ts new file mode 100644 index 000000000..f8a73266f --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/auth.gen.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/bodySerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/bodySerializer.gen.ts new file mode 100644 index 000000000..49cd8925e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/bodySerializer.gen.ts @@ -0,0 +1,92 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/params.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/params.gen.ts new file mode 100644 index 000000000..71c88e852 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/params.gen.ts @@ -0,0 +1,153 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/pathSerializer.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/pathSerializer.gen.ts new file mode 100644 index 000000000..8d9993104 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/pathSerializer.gen.ts @@ -0,0 +1,181 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/serverSentEvents.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..f8fd78e28 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/serverSentEvents.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit< + RequestInit, + 'method' +> & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult< + TData = unknown, + TReturn = void, + TNext = unknown, +> = { + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; +}; + +export const createSseClient = ({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + let lastEventId: string | undefined; + + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ''), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/types.gen.ts new file mode 100644 index 000000000..643c070c9 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/types.gen.ts @@ -0,0 +1,118 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer.gen'; + +export type HttpMethod = + | 'connect' + | 'delete' + | 'get' + | 'head' + | 'options' + | 'patch' + | 'post' + | 'put' + | 'trace'; + +export type Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, +> = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/utils.gen.ts new file mode 100644 index 000000000..0b5389d08 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/core/utils.gen.ts @@ -0,0 +1,143 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}) { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/index.ts new file mode 100644 index 000000000..cc646f13a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/index.ts @@ -0,0 +1,4 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen'; +export * from './sdk.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/sdk.gen.ts new file mode 100644 index 000000000..40a25a497 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/sdk.gen.ts @@ -0,0 +1,29 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, Client, TDataShape } from './client'; +import type { EventSubscribeData, EventSubscribeResponses } from './types.gen'; +import { client } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * Get events + */ +export const eventSubscribe = (options?: Options) => { + return (options?.client ?? client).sse.get({ + url: '/event', + ...options + }); +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/types.gen.ts new file mode 100644 index 000000000..ea6b3e42a --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/types.gen.ts @@ -0,0 +1,527 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Range = { + start: { + line: number; + character: number; + }; + end: { + line: number; + character: number; + }; +}; + +export type SymbolSource = { + text: FilePartSourceText; + type: 'symbol'; + path: string; + range: Range; + name: string; + kind: number; +}; + +export type FilePartSourceText = { + value: string; + start: number; + end: number; +}; + +export type FileSource = { + text: FilePartSourceText; + type: 'file'; + path: string; +}; + +export type FilePartSource = ({ + type: 'file'; +} & FileSource) | ({ + type: 'symbol'; +} & SymbolSource); + +export type EventIdeInstalled = { + type: 'ide.installed'; + properties: { + ide: string; + }; +}; + +export type EventFileWatcherUpdated = { + type: 'file.watcher.updated'; + properties: { + file: string; + event: 'rename' | 'change'; + }; +}; + +export type EventServerConnected = { + type: 'server.connected'; + properties: { + [key: string]: unknown; + }; +}; + +export type EventSessionError = { + type: 'session.error'; + properties: { + sessionID?: string; + error?: ({ + name: 'ProviderAuthError'; + } & ProviderAuthError) | ({ + name: 'UnknownError'; + } & UnknownError) | ({ + name: 'MessageOutputLengthError'; + } & MessageOutputLengthError) | ({ + name: 'MessageAbortedError'; + } & MessageAbortedError); + }; +}; + +export type MessageAbortedError = { + name: 'MessageAbortedError'; + data: { + [key: string]: unknown; + }; +}; + +export type MessageOutputLengthError = { + name: 'MessageOutputLengthError'; + data: { + [key: string]: unknown; + }; +}; + +export type UnknownError = { + name: 'UnknownError'; + data: { + message: string; + }; +}; + +export type ProviderAuthError = { + name: 'ProviderAuthError'; + data: { + providerID: string; + message: string; + }; +}; + +export type EventSessionIdle = { + type: 'session.idle'; + properties: { + sessionID: string; + }; +}; + +export type EventSessionDeleted = { + type: 'session.deleted'; + properties: { + info: Session; + }; +}; + +export type Session = { + id: string; + parentID?: string; + share?: { + url: string; + }; + title: string; + version: string; + time: { + created: number; + updated: number; + }; + revert?: { + messageID: string; + partID?: string; + snapshot?: string; + diff?: string; + }; +}; + +export type EventSessionUpdated = { + type: 'session.updated'; + properties: { + info: Session; + }; +}; + +export type EventFileEdited = { + type: 'file.edited'; + properties: { + file: string; + }; +}; + +export type EventPermissionReplied = { + type: 'permission.replied'; + properties: { + sessionID: string; + permissionID: string; + response: string; + }; +}; + +export type Permission = { + id: string; + type: string; + pattern?: string; + sessionID: string; + messageID: string; + callID?: string; + title: string; + metadata: { + [key: string]: unknown; + }; + time: { + created: number; + }; +}; + +export type EventPermissionUpdated = { + type: 'permission.updated'; + properties: Permission; +}; + +export type EventStorageWrite = { + type: 'storage.write'; + properties: { + key: string; + content?: unknown; + }; +}; + +export type EventMessagePartRemoved = { + type: 'message.part.removed'; + properties: { + sessionID: string; + messageID: string; + partID: string; + }; +}; + +export type AgentPart = { + id: string; + sessionID: string; + messageID: string; + type: 'agent'; + name: string; + source?: { + value: string; + start: number; + end: number; + }; +}; + +export type PatchPart = { + id: string; + sessionID: string; + messageID: string; + type: 'patch'; + hash: string; + files: Array; +}; + +export type SnapshotPart = { + id: string; + sessionID: string; + messageID: string; + type: 'snapshot'; + snapshot: string; +}; + +export type StepFinishPart = { + id: string; + sessionID: string; + messageID: string; + type: 'step-finish'; + cost: number; + tokens: { + input: number; + output: number; + reasoning: number; + cache: { + read: number; + write: number; + }; + }; +}; + +export type StepStartPart = { + id: string; + sessionID: string; + messageID: string; + type: 'step-start'; +}; + +export type ToolStateError = { + status: 'error'; + input: { + [key: string]: unknown; + }; + error: string; + metadata?: { + [key: string]: unknown; + }; + time: { + start: number; + end: number; + }; +}; + +export type ToolStateCompleted = { + status: 'completed'; + input: { + [key: string]: unknown; + }; + output: string; + title: string; + metadata: { + [key: string]: unknown; + }; + time: { + start: number; + end: number; + }; +}; + +export type ToolStateRunning = { + status: 'running'; + input?: unknown; + title?: string; + metadata?: { + [key: string]: unknown; + }; + time: { + start: number; + }; +}; + +export type ToolStatePending = { + status: 'pending'; +}; + +export type ToolState = ({ + status: 'pending'; +} & ToolStatePending) | ({ + status: 'running'; +} & ToolStateRunning) | ({ + status: 'completed'; +} & ToolStateCompleted) | ({ + status: 'error'; +} & ToolStateError); + +export type ToolPart = { + id: string; + sessionID: string; + messageID: string; + type: 'tool'; + callID: string; + tool: string; + state: ToolState; +}; + +export type FilePart = { + id: string; + sessionID: string; + messageID: string; + type: 'file'; + mime: string; + filename?: string; + url: string; + source?: FilePartSource; +}; + +export type ReasoningPart = { + id: string; + sessionID: string; + messageID: string; + type: 'reasoning'; + text: string; + metadata?: { + [key: string]: unknown; + }; + time: { + start: number; + end?: number; + }; +}; + +export type TextPart = { + id: string; + sessionID: string; + messageID: string; + type: 'text'; + text: string; + synthetic?: boolean; + time?: { + start: number; + end?: number; + }; +}; + +export type Part = ({ + type: 'text'; +} & TextPart) | ({ + type: 'reasoning'; +} & ReasoningPart) | ({ + type: 'file'; +} & FilePart) | ({ + type: 'tool'; +} & ToolPart) | ({ + type: 'step-start'; +} & StepStartPart) | ({ + type: 'step-finish'; +} & StepFinishPart) | ({ + type: 'snapshot'; +} & SnapshotPart) | ({ + type: 'patch'; +} & PatchPart) | ({ + type: 'agent'; +} & AgentPart); + +export type EventMessagePartUpdated = { + type: 'message.part.updated'; + properties: { + part: Part; + }; +}; + +export type EventMessageRemoved = { + type: 'message.removed'; + properties: { + sessionID: string; + messageID: string; + }; +}; + +export type AssistantMessage = { + id: string; + sessionID: string; + role: 'assistant'; + time: { + created: number; + completed?: number; + }; + error?: ({ + name: 'ProviderAuthError'; + } & ProviderAuthError) | ({ + name: 'UnknownError'; + } & UnknownError) | ({ + name: 'MessageOutputLengthError'; + } & MessageOutputLengthError) | ({ + name: 'MessageAbortedError'; + } & MessageAbortedError); + system: Array; + modelID: string; + providerID: string; + mode: string; + path: { + cwd: string; + root: string; + }; + summary?: boolean; + cost: number; + tokens: { + input: number; + output: number; + reasoning: number; + cache: { + read: number; + write: number; + }; + }; +}; + +export type UserMessage = { + id: string; + sessionID: string; + role: 'user'; + time: { + created: number; + }; +}; + +export type Message = ({ + role: 'user'; +} & UserMessage) | ({ + role: 'assistant'; +} & AssistantMessage); + +export type EventMessageUpdated = { + type: 'message.updated'; + properties: { + info: Message; + }; +}; + +export type EventLspClientDiagnostics = { + type: 'lsp.client.diagnostics'; + properties: { + serverID: string; + path: string; + }; +}; + +export type EventInstallationUpdated = { + type: 'installation.updated'; + properties: { + version: string; + }; +}; + +export type Event = ({ + type: 'installation.updated'; +} & EventInstallationUpdated) | ({ + type: 'lsp.client.diagnostics'; +} & EventLspClientDiagnostics) | ({ + type: 'message.updated'; +} & EventMessageUpdated) | ({ + type: 'message.removed'; +} & EventMessageRemoved) | ({ + type: 'message.part.updated'; +} & EventMessagePartUpdated) | ({ + type: 'message.part.removed'; +} & EventMessagePartRemoved) | ({ + type: 'storage.write'; +} & EventStorageWrite) | ({ + type: 'permission.updated'; +} & EventPermissionUpdated) | ({ + type: 'permission.replied'; +} & EventPermissionReplied) | ({ + type: 'file.edited'; +} & EventFileEdited) | ({ + type: 'session.updated'; +} & EventSessionUpdated) | ({ + type: 'session.deleted'; +} & EventSessionDeleted) | ({ + type: 'session.idle'; +} & EventSessionIdle) | ({ + type: 'session.error'; +} & EventSessionError) | ({ + type: 'server.connected'; +} & EventServerConnected) | ({ + type: 'file.watcher.updated'; +} & EventFileWatcherUpdated) | ({ + type: 'ide.installed'; +} & EventIdeInstalled); + +export type EventSubscribeData = { + body?: never; + path?: never; + query?: never; + url: '/event'; +}; + +export type EventSubscribeResponses = { + /** + * Event stream + */ + 200: Event; +}; + +export type EventSubscribeResponse = EventSubscribeResponses[keyof EventSubscribeResponses]; + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; diff --git a/packages/openapi-ts-tests/main/test/clients.test.ts b/packages/openapi-ts-tests/main/test/clients.test.ts index af3a0b1b6..30c9cc68a 100644 --- a/packages/openapi-ts-tests/main/test/clients.test.ts +++ b/packages/openapi-ts-tests/main/test/clients.test.ts @@ -16,6 +16,7 @@ const __dirname = path.dirname(__filename); const clients: ReadonlyArray = [ '@hey-api/client-axios', '@hey-api/client-fetch', + '@hey-api/client-ofetch', '@hey-api/client-next', '@hey-api/client-nuxt', ]; From da9e05f38f8952aaf2868094c2c0bc32c402d55e Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 17:10:56 +0700 Subject: [PATCH 10/26] chore(release): add changeset for `client-ofetch` --- .changeset/init-ofetch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/init-ofetch.md diff --git a/.changeset/init-ofetch.md b/.changeset/init-ofetch.md new file mode 100644 index 000000000..836fda780 --- /dev/null +++ b/.changeset/init-ofetch.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": minor +--- + +feat: init `client-ofetch` From 27695fa9aa34308c4492f3787abc782c10647875 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 17:19:18 +0700 Subject: [PATCH 11/26] chore: add `ofetch` to keywords in `package.json` --- packages/openapi-ts/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/openapi-ts/package.json b/packages/openapi-ts/package.json index d2418c0ac..d49700bc3 100644 --- a/packages/openapi-ts/package.json +++ b/packages/openapi-ts/package.json @@ -30,6 +30,7 @@ "next.js", "node", "nuxt", + "ofetch", "openapi", "rest", "swagger", From a6f16810dd2a028ea7b4856789506a44026c2f92 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 17:22:24 +0700 Subject: [PATCH 12/26] chore: bump `ofetch` dependency --- packages/openapi-ts-tests/main/package.json | 1 + packages/openapi-ts/package.json | 2 +- pnpm-lock.yaml | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/openapi-ts-tests/main/package.json b/packages/openapi-ts-tests/main/package.json index 622448f40..9877083db 100644 --- a/packages/openapi-ts-tests/main/package.json +++ b/packages/openapi-ts-tests/main/package.json @@ -44,6 +44,7 @@ "glob": "10.4.3", "node-fetch": "3.3.2", "nuxt": "3.14.1592", + "ofetch": "1.4.1", "prettier": "3.4.2", "rxjs": "7.8.1", "ts-node": "10.9.2", diff --git a/packages/openapi-ts/package.json b/packages/openapi-ts/package.json index d49700bc3..9201e8e81 100644 --- a/packages/openapi-ts/package.json +++ b/packages/openapi-ts/package.json @@ -123,7 +123,7 @@ "glob": "10.4.3", "node-fetch": "3.3.2", "nuxt": "3.14.1592", - "ofetch": "^1.4.0", + "ofetch": "1.4.1", "prettier": "3.4.2", "rxjs": "7.8.1", "ts-node": "10.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66f06a37e..bfcc92b02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1344,7 +1344,7 @@ importers: specifier: 3.14.1592 version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) ofetch: - specifier: ^1.4.0 + specifier: 1.4.1 version: 1.4.1 prettier: specifier: 3.4.2 @@ -1460,6 +1460,9 @@ importers: nuxt: specifier: 3.14.1592 version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) + ofetch: + specifier: 1.4.1 + version: 1.4.1 prettier: specifier: 3.4.2 version: 3.4.2 From 5b4e24b67fe4b55c93d9a2f2841ea2c4cf122d14 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 19:33:43 +0700 Subject: [PATCH 13/26] chore(ofetch): pin `vue` deps version --- examples/openapi-ts-ofetch/package.json | 2 +- pnpm-lock.yaml | 280 +++++++----------------- 2 files changed, 77 insertions(+), 205 deletions(-) diff --git a/examples/openapi-ts-ofetch/package.json b/examples/openapi-ts-ofetch/package.json index 6427931e6..ba4006a6d 100644 --- a/examples/openapi-ts-ofetch/package.json +++ b/examples/openapi-ts-ofetch/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "ofetch": "1.4.1", - "vue": "3.5.21" + "vue": "3.5.13" }, "devDependencies": { "@config/vite-base": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bfcc92b02..284a56c66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -550,8 +550,8 @@ importers: specifier: 1.4.1 version: 1.4.1 vue: - specifier: 3.5.21 - version: 3.5.21(typescript@5.8.3) + specifier: 3.5.13 + version: 3.5.13(typescript@5.8.3) devDependencies: '@config/vite-base': specifier: workspace:* @@ -573,10 +573,10 @@ importers: version: 22.10.5 '@vitejs/plugin-vue': specifier: 5.2.1 - version: 5.2.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) + version: 5.2.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) '@vitejs/plugin-vue-jsx': specifier: 4.1.1 - version: 4.1.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) + version: 4.1.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) '@vue/eslint-config-prettier': specifier: 10.1.0 version: 10.1.0(@types/eslint@9.6.0)(eslint@9.17.0(jiti@2.5.1))(prettier@3.4.2) @@ -588,7 +588,7 @@ importers: version: 2.4.6 '@vue/tsconfig': specifier: 0.7.0 - version: 0.7.0(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3)) + version: 0.7.0(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)) autoprefixer: specifier: 10.4.20 version: 10.4.20(postcss@8.4.41) @@ -621,7 +621,7 @@ importers: version: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) vite-plugin-vue-devtools: specifier: 7.7.0 - version: 7.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) + version: 7.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) vitest: specifier: 3.1.1 version: 3.1.1(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) @@ -6702,27 +6702,18 @@ packages: '@vue/reactivity@3.5.20': resolution: {integrity: sha512-hS8l8x4cl1fmZpSQX/NXlqWKARqEsNmfkwOIYqtR2F616NGfsLUm0G6FQBK6uDKUCVyi1YOL8Xmt/RkZcd/jYQ==} - '@vue/reactivity@3.5.21': - resolution: {integrity: sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==} - '@vue/runtime-core@3.5.13': resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} '@vue/runtime-core@3.5.20': resolution: {integrity: sha512-vyQRiH5uSZlOa+4I/t4Qw/SsD/gbth0SW2J7oMeVlMFMAmsG1rwDD6ok0VMmjXY3eI0iHNSSOBilEDW98PLRKw==} - '@vue/runtime-core@3.5.21': - resolution: {integrity: sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==} - '@vue/runtime-dom@3.5.13': resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} '@vue/runtime-dom@3.5.20': resolution: {integrity: sha512-KBHzPld/Djw3im0CQ7tGCpgRedryIn4CcAl047EhFTCCPT2xFf4e8j6WeKLgEEoqPSl9TYqShc3Q6tpWpz/Xgw==} - '@vue/runtime-dom@3.5.21': - resolution: {integrity: sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==} - '@vue/server-renderer@3.5.13': resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} peerDependencies: @@ -6733,11 +6724,6 @@ packages: peerDependencies: vue: 3.5.20 - '@vue/server-renderer@3.5.21': - resolution: {integrity: sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==} - peerDependencies: - vue: 3.5.21 - '@vue/shared@3.5.13': resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} @@ -13549,14 +13535,6 @@ packages: typescript: optional: true - vue@3.5.21: - resolution: {integrity: sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -17787,13 +17765,13 @@ snapshots: rc9: 2.1.2 semver: 7.7.2 - '@nuxt/devtools@1.7.0(rollup@3.29.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2))': + '@nuxt/devtools@1.7.0(rollup@3.29.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2))': dependencies: '@antfu/utils': 0.7.10 '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) '@nuxt/devtools-wizard': 1.7.0 '@nuxt/kit': 3.15.4(magicast@0.3.5) - '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2)) + '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2)) '@vue/devtools-kit': 7.6.8 birpc: 0.2.19 consola: 3.4.2 @@ -17834,13 +17812,13 @@ snapshots: - utf-8-validate - vue - '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2))': + '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2))': dependencies: '@antfu/utils': 0.7.10 '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) '@nuxt/devtools-wizard': 1.7.0 '@nuxt/kit': 3.15.4(magicast@0.3.5) - '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2)) + '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2)) '@vue/devtools-kit': 7.6.8 birpc: 0.2.19 consola: 3.4.2 @@ -17881,13 +17859,13 @@ snapshots: - utf-8-validate - vue - '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))': + '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': dependencies: '@antfu/utils': 0.7.10 '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) '@nuxt/devtools-wizard': 1.7.0 '@nuxt/kit': 3.15.4(magicast@0.3.5) - '@vue/devtools-core': 7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) + '@vue/devtools-core': 7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) '@vue/devtools-kit': 7.6.8 birpc: 0.2.19 consola: 3.4.2 @@ -18146,7 +18124,7 @@ snapshots: unplugin: 2.3.10 vite: 6.3.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) vitest-environment-nuxt: 1.0.1(@types/node@22.10.5)(@vue/test-utils@2.4.6)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(magicast@0.3.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.5.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0) - vue: 3.5.21(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) optionalDependencies: '@vue/test-utils': 2.4.6 jsdom: 23.0.0 @@ -18166,12 +18144,12 @@ snapshots: - typescript - yaml - '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3))': + '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3))': dependencies: '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) '@rollup/plugin-replace': 6.0.2(rollup@4.50.0) - '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.8.3)) - '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.8.3)) + '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3)) + '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3)) autoprefixer: 10.4.20(postcss@8.5.6) clear: 0.1.0 consola: 3.4.2 @@ -18201,7 +18179,7 @@ snapshots: vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) vite-node: 2.1.9(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) vite-plugin-checker: 0.8.0(eslint@9.17.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.8.3)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) - vue: 3.5.21(typescript@5.8.3) + vue: 3.5.13(typescript@5.8.3) vue-bundle-renderer: 2.1.2 transitivePeerDependencies: - '@biomejs/biome' @@ -18226,12 +18204,12 @@ snapshots: - vti - vue-tsc - '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))': + '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2))': dependencies: '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@3.29.5) '@rollup/plugin-replace': 6.0.2(rollup@3.29.5) - '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2)) - '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2)) + '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.9.2)) + '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.9.2)) autoprefixer: 10.4.20(postcss@8.5.6) clear: 0.1.0 consola: 3.4.2 @@ -18261,7 +18239,7 @@ snapshots: vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) vite-node: 2.1.9(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) vite-plugin-checker: 0.8.0(eslint@9.35.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) - vue: 3.5.21(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) vue-bundle-renderer: 2.1.2 transitivePeerDependencies: - '@biomejs/biome' @@ -18286,12 +18264,12 @@ snapshots: - vti - vue-tsc - '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))': + '@nuxt/vite-builder@3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2))': dependencies: '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) '@rollup/plugin-replace': 6.0.2(rollup@4.50.0) - '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2)) - '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2)) + '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.9.2)) + '@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.9.2)) autoprefixer: 10.4.20(postcss@8.5.6) clear: 0.1.0 consola: 3.4.2 @@ -18321,7 +18299,7 @@ snapshots: vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) vite-node: 2.1.9(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) vite-plugin-checker: 0.8.0(eslint@9.35.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.9.2)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) - vue: 3.5.21(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) vue-bundle-renderer: 2.1.2 transitivePeerDependencies: - '@biomejs/biome' @@ -20078,21 +20056,21 @@ snapshots: '@unhead/schema': 1.11.20 '@unhead/shared': 1.11.20 - '@unhead/vue@1.11.20(vue@3.5.21(typescript@5.8.3))': + '@unhead/vue@1.11.20(vue@3.5.13(typescript@5.8.3))': dependencies: '@unhead/schema': 1.11.20 '@unhead/shared': 1.11.20 hookable: 5.5.3 unhead: 1.11.20 - vue: 3.5.21(typescript@5.8.3) + vue: 3.5.13(typescript@5.8.3) - '@unhead/vue@1.11.20(vue@3.5.21(typescript@5.9.2))': + '@unhead/vue@1.11.20(vue@3.5.13(typescript@5.9.2))': dependencies: '@unhead/schema': 1.11.20 '@unhead/shared': 1.11.20 hookable: 5.5.3 unhead: 1.11.20 - vue: 3.5.21(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -20199,23 +20177,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.8.3))': + '@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3))': dependencies: '@babel/core': 7.28.3 '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) - vue: 3.5.21(typescript@5.8.3) + vue: 3.5.13(typescript@5.8.3) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2))': + '@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.9.2))': dependencies: '@babel/core': 7.28.3 '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) - vue: 3.5.21(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) transitivePeerDependencies: - supports-color @@ -20229,36 +20207,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue-jsx@4.1.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))': - dependencies: - '@babel/core': 7.28.3 - '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) - vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) - vue: 3.5.21(typescript@5.8.3) - transitivePeerDependencies: - - supports-color - - '@vitejs/plugin-vue@5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.8.3))': + '@vitejs/plugin-vue@5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3))': dependencies: vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) - vue: 3.5.21(typescript@5.8.3) + vue: 3.5.13(typescript@5.8.3) - '@vitejs/plugin-vue@5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2))': + '@vitejs/plugin-vue@5.2.1(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.9.2))': dependencies: vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) - vue: 3.5.21(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) '@vitejs/plugin-vue@5.2.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': dependencies: vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) vue: 3.5.13(typescript@5.8.3) - '@vitejs/plugin-vue@5.2.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))': - dependencies: - vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) - vue: 3.5.21(typescript@5.8.3) - '@vitejs/plugin-vue@6.0.1(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.20(typescript@5.9.2))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 @@ -20377,27 +20340,27 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@vue-macros/common@1.16.1(vue@3.5.21(typescript@5.8.3))': + '@vue-macros/common@1.16.1(vue@3.5.13(typescript@5.8.3))': dependencies: - '@vue/compiler-sfc': 3.5.20 + '@vue/compiler-sfc': 3.5.21 ast-kit: 1.4.3 local-pkg: 1.1.2 magic-string-ast: 0.7.1 pathe: 2.0.3 picomatch: 4.0.3 optionalDependencies: - vue: 3.5.21(typescript@5.8.3) + vue: 3.5.13(typescript@5.8.3) - '@vue-macros/common@1.16.1(vue@3.5.21(typescript@5.9.2))': + '@vue-macros/common@1.16.1(vue@3.5.13(typescript@5.9.2))': dependencies: - '@vue/compiler-sfc': 3.5.20 + '@vue/compiler-sfc': 3.5.21 ast-kit: 1.4.3 local-pkg: 1.1.2 magic-string-ast: 0.7.1 pathe: 2.0.3 picomatch: 4.0.3 optionalDependencies: - vue: 3.5.21(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) '@vue/babel-helper-vue-transform-on@1.5.0': {} @@ -20529,7 +20492,7 @@ snapshots: dependencies: '@vue/devtools-kit': 8.0.1 - '@vue/devtools-core@7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2))': + '@vue/devtools-core@7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2))': dependencies: '@vue/devtools-kit': 7.6.8 '@vue/devtools-shared': 7.7.7 @@ -20537,11 +20500,11 @@ snapshots: nanoid: 5.1.5 pathe: 1.1.2 vite-hot-client: 0.2.4(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) - vue: 3.5.21(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) transitivePeerDependencies: - vite - '@vue/devtools-core@7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))': + '@vue/devtools-core@7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': dependencies: '@vue/devtools-kit': 7.6.8 '@vue/devtools-shared': 7.7.7 @@ -20549,7 +20512,7 @@ snapshots: nanoid: 5.1.5 pathe: 1.1.2 vite-hot-client: 0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) - vue: 3.5.21(typescript@5.8.3) + vue: 3.5.13(typescript@5.8.3) transitivePeerDependencies: - vite @@ -20565,18 +20528,6 @@ snapshots: transitivePeerDependencies: - vite - '@vue/devtools-core@7.7.7(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))': - dependencies: - '@vue/devtools-kit': 7.7.7 - '@vue/devtools-shared': 7.7.7 - mitt: 3.0.1 - nanoid: 5.1.5 - pathe: 2.0.3 - vite-hot-client: 2.1.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) - vue: 3.5.21(typescript@5.8.3) - transitivePeerDependencies: - - vite - '@vue/devtools-kit@7.6.8': dependencies: '@vue/devtools-shared': 7.7.7 @@ -20657,10 +20608,6 @@ snapshots: dependencies: '@vue/shared': 3.5.20 - '@vue/reactivity@3.5.21': - dependencies: - '@vue/shared': 3.5.21 - '@vue/runtime-core@3.5.13': dependencies: '@vue/reactivity': 3.5.13 @@ -20671,11 +20618,6 @@ snapshots: '@vue/reactivity': 3.5.20 '@vue/shared': 3.5.20 - '@vue/runtime-core@3.5.21': - dependencies: - '@vue/reactivity': 3.5.21 - '@vue/shared': 3.5.21 - '@vue/runtime-dom@3.5.13': dependencies: '@vue/reactivity': 3.5.13 @@ -20690,13 +20632,6 @@ snapshots: '@vue/shared': 3.5.20 csstype: 3.1.3 - '@vue/runtime-dom@3.5.21': - dependencies: - '@vue/reactivity': 3.5.21 - '@vue/runtime-core': 3.5.21 - '@vue/shared': 3.5.21 - csstype: 3.1.3 - '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3))': dependencies: '@vue/compiler-ssr': 3.5.13 @@ -20715,18 +20650,6 @@ snapshots: '@vue/shared': 3.5.20 vue: 3.5.20(typescript@5.9.2) - '@vue/server-renderer@3.5.21(vue@3.5.21(typescript@5.8.3))': - dependencies: - '@vue/compiler-ssr': 3.5.21 - '@vue/shared': 3.5.21 - vue: 3.5.21(typescript@5.8.3) - - '@vue/server-renderer@3.5.21(vue@3.5.21(typescript@5.9.2))': - dependencies: - '@vue/compiler-ssr': 3.5.21 - '@vue/shared': 3.5.21 - vue: 3.5.21(typescript@5.9.2) - '@vue/shared@3.5.13': {} '@vue/shared@3.5.20': {} @@ -20743,11 +20666,6 @@ snapshots: typescript: 5.8.3 vue: 3.5.13(typescript@5.8.3) - '@vue/tsconfig@0.7.0(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3))': - optionalDependencies: - typescript: 5.8.3 - vue: 3.5.21(typescript@5.8.3) - '@vueuse/core@13.9.0(vue@3.5.20(typescript@5.9.2))': dependencies: '@types/web-bluetooth': 0.0.21 @@ -25459,15 +25377,15 @@ snapshots: nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) + '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) '@nuxt/telemetry': 2.6.6(magicast@0.3.5) - '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3)) + '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.17.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)) '@unhead/dom': 1.11.20 '@unhead/shared': 1.11.20 '@unhead/ssr': 1.11.20 - '@unhead/vue': 1.11.20(vue@3.5.21(typescript@5.8.3)) + '@unhead/vue': 1.11.20(vue@3.5.13(typescript@5.8.3)) '@vue/shared': 3.5.20 acorn: 8.14.0 c12: 2.0.1(magicast@0.3.5) @@ -25515,13 +25433,13 @@ snapshots: unhead: 1.11.20 unimport: 3.14.6(rollup@4.50.0) unplugin: 1.16.1 - unplugin-vue-router: 0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3)) + unplugin-vue-router: 0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)) unstorage: 1.17.0(@netlify/blobs@9.1.2)(db0@0.3.2)(ioredis@5.7.0) untyped: 1.5.2 - vue: 3.5.21(typescript@5.8.3) + vue: 3.5.13(typescript@5.8.3) vue-bundle-renderer: 2.1.2 vue-devtools-stub: 0.1.0 - vue-router: 4.5.0(vue@3.5.21(typescript@5.8.3)) + vue-router: 4.5.0(vue@3.5.13(typescript@5.8.3)) optionalDependencies: '@parcel/watcher': 2.5.1 '@types/node': 22.10.5 @@ -25580,15 +25498,15 @@ snapshots: nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 1.7.0(rollup@3.29.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2)) + '@nuxt/devtools': 1.7.0(rollup@3.29.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2)) '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@3.29.5) '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@3.29.5) '@nuxt/telemetry': 2.6.6(magicast@0.3.5) - '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2)) + '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@3.29.5)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)) '@unhead/dom': 1.11.20 '@unhead/shared': 1.11.20 '@unhead/ssr': 1.11.20 - '@unhead/vue': 1.11.20(vue@3.5.21(typescript@5.9.2)) + '@unhead/vue': 1.11.20(vue@3.5.13(typescript@5.9.2)) '@vue/shared': 3.5.20 acorn: 8.14.0 c12: 2.0.1(magicast@0.3.5) @@ -25636,13 +25554,13 @@ snapshots: unhead: 1.11.20 unimport: 3.14.6(rollup@3.29.5) unplugin: 1.16.1 - unplugin-vue-router: 0.10.9(rollup@3.29.5)(vue-router@4.5.0(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)) + unplugin-vue-router: 0.10.9(rollup@3.29.5)(vue-router@4.5.0(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2)) unstorage: 1.17.0(@netlify/blobs@9.1.2)(db0@0.3.2)(ioredis@5.7.0) untyped: 1.5.2 - vue: 3.5.21(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) vue-bundle-renderer: 2.1.2 vue-devtools-stub: 0.1.0 - vue-router: 4.5.0(vue@3.5.21(typescript@5.9.2)) + vue-router: 4.5.0(vue@3.5.13(typescript@5.9.2)) optionalDependencies: '@parcel/watcher': 2.5.1 '@types/node': 22.10.5 @@ -25701,15 +25619,15 @@ snapshots: nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.35.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.9.2)) + '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2)) '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) '@nuxt/telemetry': 2.6.6(magicast@0.3.5) - '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2)) + '@nuxt/vite-builder': 3.14.1592(@types/node@22.10.5)(eslint@9.35.0(jiti@2.5.1))(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)) '@unhead/dom': 1.11.20 '@unhead/shared': 1.11.20 '@unhead/ssr': 1.11.20 - '@unhead/vue': 1.11.20(vue@3.5.21(typescript@5.9.2)) + '@unhead/vue': 1.11.20(vue@3.5.13(typescript@5.9.2)) '@vue/shared': 3.5.20 acorn: 8.14.0 c12: 2.0.1(magicast@0.3.5) @@ -25757,13 +25675,13 @@ snapshots: unhead: 1.11.20 unimport: 3.14.6(rollup@4.50.0) unplugin: 1.16.1 - unplugin-vue-router: 0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)) + unplugin-vue-router: 0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2)) unstorage: 1.17.0(@netlify/blobs@9.1.2)(db0@0.3.2)(ioredis@5.7.0) untyped: 1.5.2 - vue: 3.5.21(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) vue-bundle-renderer: 2.1.2 vue-devtools-stub: 0.1.0 - vue-router: 4.5.0(vue@3.5.21(typescript@5.9.2)) + vue-router: 4.5.0(vue@3.5.13(typescript@5.9.2)) optionalDependencies: '@parcel/watcher': 2.5.1 '@types/node': 22.10.5 @@ -28460,11 +28378,11 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.3 - unplugin-vue-router@0.10.9(rollup@3.29.5)(vue-router@4.5.0(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)): + unplugin-vue-router@0.10.9(rollup@3.29.5)(vue-router@4.5.0(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2)): dependencies: '@babel/types': 7.28.2 '@rollup/pluginutils': 5.2.0(rollup@3.29.5) - '@vue-macros/common': 1.16.1(vue@3.5.21(typescript@5.9.2)) + '@vue-macros/common': 1.16.1(vue@3.5.13(typescript@5.9.2)) ast-walker-scope: 0.6.2 chokidar: 3.6.0 fast-glob: 3.3.3 @@ -28477,16 +28395,16 @@ snapshots: unplugin: 2.0.0-beta.1 yaml: 2.8.0 optionalDependencies: - vue-router: 4.5.0(vue@3.5.21(typescript@5.9.2)) + vue-router: 4.5.0(vue@3.5.13(typescript@5.9.2)) transitivePeerDependencies: - rollup - vue - unplugin-vue-router@0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)): + unplugin-vue-router@0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3)): dependencies: '@babel/types': 7.28.2 '@rollup/pluginutils': 5.2.0(rollup@4.50.0) - '@vue-macros/common': 1.16.1(vue@3.5.21(typescript@5.9.2)) + '@vue-macros/common': 1.16.1(vue@3.5.13(typescript@5.8.3)) ast-walker-scope: 0.6.2 chokidar: 3.6.0 fast-glob: 3.3.3 @@ -28499,16 +28417,16 @@ snapshots: unplugin: 2.0.0-beta.1 yaml: 2.8.0 optionalDependencies: - vue-router: 4.5.0(vue@3.5.13(typescript@5.9.2)) + vue-router: 4.5.0(vue@3.5.13(typescript@5.8.3)) transitivePeerDependencies: - rollup - vue - unplugin-vue-router@0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3)): + unplugin-vue-router@0.10.9(rollup@4.50.0)(vue-router@4.5.0(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2)): dependencies: '@babel/types': 7.28.2 '@rollup/pluginutils': 5.2.0(rollup@4.50.0) - '@vue-macros/common': 1.16.1(vue@3.5.21(typescript@5.8.3)) + '@vue-macros/common': 1.16.1(vue@3.5.13(typescript@5.9.2)) ast-walker-scope: 0.6.2 chokidar: 3.6.0 fast-glob: 3.3.3 @@ -28521,7 +28439,7 @@ snapshots: unplugin: 2.0.0-beta.1 yaml: 2.8.0 optionalDependencies: - vue-router: 4.5.0(vue@3.5.21(typescript@5.8.3)) + vue-router: 4.5.0(vue@3.5.13(typescript@5.9.2)) transitivePeerDependencies: - rollup - vue @@ -28879,22 +28797,6 @@ snapshots: - supports-color - vue - vite-plugin-vue-devtools@7.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)): - dependencies: - '@vue/devtools-core': 7.7.7(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)) - '@vue/devtools-kit': 7.7.7 - '@vue/devtools-shared': 7.7.7 - execa: 9.6.0 - sirv: 3.0.1 - vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) - vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) - vite-plugin-vue-inspector: 5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) - transitivePeerDependencies: - - '@nuxt/kit' - - rollup - - supports-color - - vue - vite-plugin-vue-inspector@5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): dependencies: '@babel/core': 7.28.3 @@ -28903,7 +28805,7 @@ snapshots: '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) - '@vue/compiler-dom': 3.5.20 + '@vue/compiler-dom': 3.5.21 kolorist: 1.8.0 magic-string: 0.30.18 vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) @@ -28918,7 +28820,7 @@ snapshots: '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) - '@vue/compiler-dom': 3.5.20 + '@vue/compiler-dom': 3.5.21 kolorist: 1.8.0 magic-string: 0.30.18 vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) @@ -29274,16 +29176,6 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.13(typescript@5.9.2) - vue-router@4.5.0(vue@3.5.21(typescript@5.8.3)): - dependencies: - '@vue/devtools-api': 6.6.4 - vue: 3.5.21(typescript@5.8.3) - - vue-router@4.5.0(vue@3.5.21(typescript@5.9.2)): - dependencies: - '@vue/devtools-api': 6.6.4 - vue: 3.5.21(typescript@5.9.2) - vue-tsc@2.2.0(typescript@5.8.3): dependencies: '@volar/typescript': 2.4.23 @@ -29326,26 +29218,6 @@ snapshots: optionalDependencies: typescript: 5.9.2 - vue@3.5.21(typescript@5.8.3): - dependencies: - '@vue/compiler-dom': 3.5.21 - '@vue/compiler-sfc': 3.5.21 - '@vue/runtime-dom': 3.5.21 - '@vue/server-renderer': 3.5.21(vue@3.5.21(typescript@5.8.3)) - '@vue/shared': 3.5.21 - optionalDependencies: - typescript: 5.8.3 - - vue@3.5.21(typescript@5.9.2): - dependencies: - '@vue/compiler-dom': 3.5.21 - '@vue/compiler-sfc': 3.5.21 - '@vue/runtime-dom': 3.5.21 - '@vue/server-renderer': 3.5.21(vue@3.5.21(typescript@5.9.2)) - '@vue/shared': 3.5.21 - optionalDependencies: - typescript: 5.9.2 - w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 From cfd6dcf5029263fbf39681605de268d5dca9a364 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Sun, 14 Sep 2025 19:34:50 +0700 Subject: [PATCH 14/26] chore(ofetch): update snapshots --- .../base-url-false/client/client.gen.ts | 44 +++++++++++++++++-- .../base-url-false/client/utils.gen.ts | 19 ++++++-- .../base-url-number/client/client.gen.ts | 44 +++++++++++++++++-- .../base-url-number/client/utils.gen.ts | 19 ++++++-- .../base-url-strict/client/client.gen.ts | 44 +++++++++++++++++-- .../base-url-strict/client/utils.gen.ts | 19 ++++++-- .../base-url-string/client/client.gen.ts | 44 +++++++++++++++++-- .../base-url-string/client/utils.gen.ts | 19 ++++++-- .../clean-false/client/client.gen.ts | 44 +++++++++++++++++-- .../clean-false/client/utils.gen.ts | 19 ++++++-- .../default/client/client.gen.ts | 44 +++++++++++++++++-- .../client-ofetch/default/client/utils.gen.ts | 19 ++++++-- .../sdk-client-optional/client/client.gen.ts | 44 +++++++++++++++++-- .../sdk-client-optional/client/utils.gen.ts | 19 ++++++-- .../sdk-client-required/client/client.gen.ts | 44 +++++++++++++++++-- .../sdk-client-required/client/utils.gen.ts | 19 ++++++-- .../client/client.gen.ts | 44 +++++++++++++++++-- .../tsconfig-nodenext-sdk/client/utils.gen.ts | 19 ++++++-- .../3.1.x/sse-ofetch/client/client.gen.ts | 44 +++++++++++++++++-- .../3.1.x/sse-ofetch/client/utils.gen.ts | 19 ++++++-- 20 files changed, 550 insertions(+), 80 deletions(-) diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts index cdc57e116..aa213e780 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts @@ -78,6 +78,38 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + // Precompute network body for retries and consistent handling const networkBody = getValidRequestBody(opts) as | RequestInit['body'] @@ -116,14 +148,18 @@ export const createClient = (config: Config = {}): Client => { body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, ) => { - const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); return buildOfetchOptions(opts, body, responseType, effectiveRetry); }; const request: Client['request'] = async (options) => { - const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( - options as any, - ); + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); // Compute response type mapping once const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts index e54e85704..afc030c6e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts @@ -318,7 +318,8 @@ export const buildOfetchOptions = ( body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, retryOverride?: OfetchOptions['retry'], -): OfetchOptions => ({ +): OfetchOptions => + ({ agent: opts.agent as OfetchOptions['agent'], body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], @@ -332,13 +333,13 @@ export const buildOfetchOptions = ( // URL already includes query query: undefined, responseType, - retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], signal: opts.signal, timeout: opts.timeout as number | undefined, - } as OfetchOptions); + }) as OfetchOptions; /** * Parse a successful response, handling empty bodies and stream cases. @@ -384,10 +385,20 @@ export const parseSuccess = async ( case 'arrayBuffer': case 'blob': case 'formData': - case 'json': case 'text': data = await (response as any)[inferredParseAs](); break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } case 'stream': return response.body; } diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts index cdc57e116..aa213e780 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts @@ -78,6 +78,38 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + // Precompute network body for retries and consistent handling const networkBody = getValidRequestBody(opts) as | RequestInit['body'] @@ -116,14 +148,18 @@ export const createClient = (config: Config = {}): Client => { body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, ) => { - const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); return buildOfetchOptions(opts, body, responseType, effectiveRetry); }; const request: Client['request'] = async (options) => { - const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( - options as any, - ); + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); // Compute response type mapping once const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts index e54e85704..afc030c6e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts @@ -318,7 +318,8 @@ export const buildOfetchOptions = ( body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, retryOverride?: OfetchOptions['retry'], -): OfetchOptions => ({ +): OfetchOptions => + ({ agent: opts.agent as OfetchOptions['agent'], body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], @@ -332,13 +333,13 @@ export const buildOfetchOptions = ( // URL already includes query query: undefined, responseType, - retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], signal: opts.signal, timeout: opts.timeout as number | undefined, - } as OfetchOptions); + }) as OfetchOptions; /** * Parse a successful response, handling empty bodies and stream cases. @@ -384,10 +385,20 @@ export const parseSuccess = async ( case 'arrayBuffer': case 'blob': case 'formData': - case 'json': case 'text': data = await (response as any)[inferredParseAs](); break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } case 'stream': return response.body; } diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts index cdc57e116..aa213e780 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts @@ -78,6 +78,38 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + // Precompute network body for retries and consistent handling const networkBody = getValidRequestBody(opts) as | RequestInit['body'] @@ -116,14 +148,18 @@ export const createClient = (config: Config = {}): Client => { body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, ) => { - const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); return buildOfetchOptions(opts, body, responseType, effectiveRetry); }; const request: Client['request'] = async (options) => { - const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( - options as any, - ); + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); // Compute response type mapping once const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts index e54e85704..afc030c6e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts @@ -318,7 +318,8 @@ export const buildOfetchOptions = ( body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, retryOverride?: OfetchOptions['retry'], -): OfetchOptions => ({ +): OfetchOptions => + ({ agent: opts.agent as OfetchOptions['agent'], body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], @@ -332,13 +333,13 @@ export const buildOfetchOptions = ( // URL already includes query query: undefined, responseType, - retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], signal: opts.signal, timeout: opts.timeout as number | undefined, - } as OfetchOptions); + }) as OfetchOptions; /** * Parse a successful response, handling empty bodies and stream cases. @@ -384,10 +385,20 @@ export const parseSuccess = async ( case 'arrayBuffer': case 'blob': case 'formData': - case 'json': case 'text': data = await (response as any)[inferredParseAs](); break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } case 'stream': return response.body; } diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts index cdc57e116..aa213e780 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts @@ -78,6 +78,38 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + // Precompute network body for retries and consistent handling const networkBody = getValidRequestBody(opts) as | RequestInit['body'] @@ -116,14 +148,18 @@ export const createClient = (config: Config = {}): Client => { body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, ) => { - const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); return buildOfetchOptions(opts, body, responseType, effectiveRetry); }; const request: Client['request'] = async (options) => { - const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( - options as any, - ); + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); // Compute response type mapping once const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts index e54e85704..afc030c6e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts @@ -318,7 +318,8 @@ export const buildOfetchOptions = ( body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, retryOverride?: OfetchOptions['retry'], -): OfetchOptions => ({ +): OfetchOptions => + ({ agent: opts.agent as OfetchOptions['agent'], body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], @@ -332,13 +333,13 @@ export const buildOfetchOptions = ( // URL already includes query query: undefined, responseType, - retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], signal: opts.signal, timeout: opts.timeout as number | undefined, - } as OfetchOptions); + }) as OfetchOptions; /** * Parse a successful response, handling empty bodies and stream cases. @@ -384,10 +385,20 @@ export const parseSuccess = async ( case 'arrayBuffer': case 'blob': case 'formData': - case 'json': case 'text': data = await (response as any)[inferredParseAs](); break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } case 'stream': return response.body; } diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts index cdc57e116..aa213e780 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts @@ -78,6 +78,38 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + // Precompute network body for retries and consistent handling const networkBody = getValidRequestBody(opts) as | RequestInit['body'] @@ -116,14 +148,18 @@ export const createClient = (config: Config = {}): Client => { body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, ) => { - const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); return buildOfetchOptions(opts, body, responseType, effectiveRetry); }; const request: Client['request'] = async (options) => { - const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( - options as any, - ); + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); // Compute response type mapping once const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts index e54e85704..afc030c6e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts @@ -318,7 +318,8 @@ export const buildOfetchOptions = ( body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, retryOverride?: OfetchOptions['retry'], -): OfetchOptions => ({ +): OfetchOptions => + ({ agent: opts.agent as OfetchOptions['agent'], body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], @@ -332,13 +333,13 @@ export const buildOfetchOptions = ( // URL already includes query query: undefined, responseType, - retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], signal: opts.signal, timeout: opts.timeout as number | undefined, - } as OfetchOptions); + }) as OfetchOptions; /** * Parse a successful response, handling empty bodies and stream cases. @@ -384,10 +385,20 @@ export const parseSuccess = async ( case 'arrayBuffer': case 'blob': case 'formData': - case 'json': case 'text': data = await (response as any)[inferredParseAs](); break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } case 'stream': return response.body; } diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts index cdc57e116..aa213e780 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts @@ -78,6 +78,38 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + // Precompute network body for retries and consistent handling const networkBody = getValidRequestBody(opts) as | RequestInit['body'] @@ -116,14 +148,18 @@ export const createClient = (config: Config = {}): Client => { body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, ) => { - const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); return buildOfetchOptions(opts, body, responseType, effectiveRetry); }; const request: Client['request'] = async (options) => { - const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( - options as any, - ); + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); // Compute response type mapping once const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts index e54e85704..afc030c6e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts @@ -318,7 +318,8 @@ export const buildOfetchOptions = ( body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, retryOverride?: OfetchOptions['retry'], -): OfetchOptions => ({ +): OfetchOptions => + ({ agent: opts.agent as OfetchOptions['agent'], body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], @@ -332,13 +333,13 @@ export const buildOfetchOptions = ( // URL already includes query query: undefined, responseType, - retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], signal: opts.signal, timeout: opts.timeout as number | undefined, - } as OfetchOptions); + }) as OfetchOptions; /** * Parse a successful response, handling empty bodies and stream cases. @@ -384,10 +385,20 @@ export const parseSuccess = async ( case 'arrayBuffer': case 'blob': case 'formData': - case 'json': case 'text': data = await (response as any)[inferredParseAs](); break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } case 'stream': return response.body; } diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts index cdc57e116..aa213e780 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts @@ -78,6 +78,38 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + // Precompute network body for retries and consistent handling const networkBody = getValidRequestBody(opts) as | RequestInit['body'] @@ -116,14 +148,18 @@ export const createClient = (config: Config = {}): Client => { body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, ) => { - const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); return buildOfetchOptions(opts, body, responseType, effectiveRetry); }; const request: Client['request'] = async (options) => { - const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( - options as any, - ); + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); // Compute response type mapping once const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts index e54e85704..afc030c6e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts @@ -318,7 +318,8 @@ export const buildOfetchOptions = ( body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, retryOverride?: OfetchOptions['retry'], -): OfetchOptions => ({ +): OfetchOptions => + ({ agent: opts.agent as OfetchOptions['agent'], body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], @@ -332,13 +333,13 @@ export const buildOfetchOptions = ( // URL already includes query query: undefined, responseType, - retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], signal: opts.signal, timeout: opts.timeout as number | undefined, - } as OfetchOptions); + }) as OfetchOptions; /** * Parse a successful response, handling empty bodies and stream cases. @@ -384,10 +385,20 @@ export const parseSuccess = async ( case 'arrayBuffer': case 'blob': case 'formData': - case 'json': case 'text': data = await (response as any)[inferredParseAs](); break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } case 'stream': return response.body; } diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts index cdc57e116..aa213e780 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts @@ -78,6 +78,38 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + // Precompute network body for retries and consistent handling const networkBody = getValidRequestBody(opts) as | RequestInit['body'] @@ -116,14 +148,18 @@ export const createClient = (config: Config = {}): Client => { body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, ) => { - const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); return buildOfetchOptions(opts, body, responseType, effectiveRetry); }; const request: Client['request'] = async (options) => { - const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( - options as any, - ); + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); // Compute response type mapping once const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts index e54e85704..afc030c6e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts @@ -318,7 +318,8 @@ export const buildOfetchOptions = ( body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, retryOverride?: OfetchOptions['retry'], -): OfetchOptions => ({ +): OfetchOptions => + ({ agent: opts.agent as OfetchOptions['agent'], body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], @@ -332,13 +333,13 @@ export const buildOfetchOptions = ( // URL already includes query query: undefined, responseType, - retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], signal: opts.signal, timeout: opts.timeout as number | undefined, - } as OfetchOptions); + }) as OfetchOptions; /** * Parse a successful response, handling empty bodies and stream cases. @@ -384,10 +385,20 @@ export const parseSuccess = async ( case 'arrayBuffer': case 'blob': case 'formData': - case 'json': case 'text': data = await (response as any)[inferredParseAs](); break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } case 'stream': return response.body; } diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts index c87004a03..2a0e427f0 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts @@ -78,6 +78,38 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + // Precompute network body for retries and consistent handling const networkBody = getValidRequestBody(opts) as | RequestInit['body'] @@ -116,14 +148,18 @@ export const createClient = (config: Config = {}): Client => { body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, ) => { - const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); return buildOfetchOptions(opts, body, responseType, effectiveRetry); }; const request: Client['request'] = async (options) => { - const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( - options as any, - ); + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); // Compute response type mapping once const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts index 0a3df5a87..48fbf4d6a 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts @@ -318,7 +318,8 @@ export const buildOfetchOptions = ( body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, retryOverride?: OfetchOptions['retry'], -): OfetchOptions => ({ +): OfetchOptions => + ({ agent: opts.agent as OfetchOptions['agent'], body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], @@ -332,13 +333,13 @@ export const buildOfetchOptions = ( // URL already includes query query: undefined, responseType, - retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], signal: opts.signal, timeout: opts.timeout as number | undefined, - } as OfetchOptions); + }) as OfetchOptions; /** * Parse a successful response, handling empty bodies and stream cases. @@ -384,10 +385,20 @@ export const parseSuccess = async ( case 'arrayBuffer': case 'blob': case 'formData': - case 'json': case 'text': data = await (response as any)[inferredParseAs](); break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } case 'stream': return response.body; } diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts index cdc57e116..aa213e780 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts @@ -78,6 +78,38 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } + // If user provides a raw body (no serializer), adjust Content-Type sensibly. + // Avoid overriding explicit user-defined headers; only correct the default JSON header. + if ( + opts.body !== undefined && + opts.bodySerializer === null && + (opts.headers.get('Content-Type') || '').toLowerCase() === + 'application/json' + ) { + const b: unknown = opts.body; + if (typeof FormData !== 'undefined' && b instanceof FormData) { + // Let the runtime set proper boundary + opts.headers.delete('Content-Type'); + } else if ( + typeof URLSearchParams !== 'undefined' && + b instanceof URLSearchParams + ) { + // Set standard urlencoded content type with charset + opts.headers.set( + 'Content-Type', + 'application/x-www-form-urlencoded;charset=UTF-8', + ); + } else if (typeof Blob !== 'undefined' && b instanceof Blob) { + const t = b.type?.trim(); + if (t) { + opts.headers.set('Content-Type', t); + } else { + // No known type for the blob: avoid sending misleading JSON header + opts.headers.delete('Content-Type'); + } + } + } + // Precompute network body for retries and consistent handling const networkBody = getValidRequestBody(opts) as | RequestInit['body'] @@ -116,14 +148,18 @@ export const createClient = (config: Config = {}): Client => { body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, ) => { - const effectiveRetry = isRepeatableBody(body) ? (opts.retry as any) : (0 as any); + const effectiveRetry = isRepeatableBody(body) + ? (opts.retry as any) + : (0 as any); return buildOfetchOptions(opts, body, responseType, effectiveRetry); }; const request: Client['request'] = async (options) => { - const { networkBody: initialNetworkBody, opts, url } = await resolveOptions( - options as any, - ); + const { + networkBody: initialNetworkBody, + opts, + url, + } = await resolveOptions(options as any); // Compute response type mapping once const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts index e54e85704..afc030c6e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts @@ -318,7 +318,8 @@ export const buildOfetchOptions = ( body: BodyInit | null | undefined, responseType: OfetchResponseType | undefined, retryOverride?: OfetchOptions['retry'], -): OfetchOptions => ({ +): OfetchOptions => + ({ agent: opts.agent as OfetchOptions['agent'], body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], @@ -332,13 +333,13 @@ export const buildOfetchOptions = ( // URL already includes query query: undefined, responseType, - retry: (retryOverride ?? (opts.retry as OfetchOptions['retry'])), + retry: retryOverride ?? (opts.retry as OfetchOptions['retry']), retryDelay: opts.retryDelay as OfetchOptions['retryDelay'], retryStatusCodes: opts.retryStatusCodes as OfetchOptions['retryStatusCodes'], signal: opts.signal, timeout: opts.timeout as number | undefined, - } as OfetchOptions); + }) as OfetchOptions; /** * Parse a successful response, handling empty bodies and stream cases. @@ -384,10 +385,20 @@ export const parseSuccess = async ( case 'arrayBuffer': case 'blob': case 'formData': - case 'json': case 'text': data = await (response as any)[inferredParseAs](); break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; detect empty via clone().text() first. + const txt = await response.clone().text(); + if (!txt) { + data = {}; + } else { + data = await (response as any).json(); + } + break; + } case 'stream': return response.body; } From 300f79a6cdb38f443bbdddbc1098128559371ec5 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Mon, 15 Sep 2025 12:37:32 +0700 Subject: [PATCH 15/26] docs(ofetch): init `SSE` section --- docs/openapi-ts/clients/ofetch.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/openapi-ts/clients/ofetch.md b/docs/openapi-ts/clients/ofetch.md index 5a8c59549..ee4bdca3d 100644 --- a/docs/openapi-ts/clients/ofetch.md +++ b/docs/openapi-ts/clients/ofetch.md @@ -282,6 +282,22 @@ client.setConfig({ }); ``` +## SSE + +The ofetch client supports Server‑Sent Events (SSE) via `client.sse.`. Use this for streaming endpoints. + +```ts +const result = await client.sse.get({ + url: '/events', + onSseEvent(event) { + // event.data (string) or parsed data, depending on server + }, + onSseError(error) { + // handle stream errors + }, +}); +``` + ## Build URL If you need to access the compiled URL, you can use the `buildUrl()` method. It's loosely typed by default to accept almost any value; in practice, you will want to pass a type hint. From 845318cf4fe5536aaecd8edf73bdfef2dc35af91 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Mon, 15 Sep 2025 12:41:22 +0700 Subject: [PATCH 16/26] docs: cap of ofetch -> OFetch --- docs/.vitepress/config/en.ts | 2 +- docs/openapi-ts/clients.md | 2 +- docs/openapi-ts/clients/ofetch.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/.vitepress/config/en.ts b/docs/.vitepress/config/en.ts index ebabcce14..89994a6cb 100644 --- a/docs/.vitepress/config/en.ts +++ b/docs/.vitepress/config/en.ts @@ -105,7 +105,7 @@ export default defineConfig({ }, { link: '/openapi-ts/clients/ofetch', - text: 'ofetch', + text: 'OFetch', }, { link: '/openapi-ts/clients/effect', diff --git a/docs/openapi-ts/clients.md b/docs/openapi-ts/clients.md index eed7b5016..e267a60be 100644 --- a/docs/openapi-ts/clients.md +++ b/docs/openapi-ts/clients.md @@ -30,7 +30,7 @@ Hey API natively supports the following clients. - [Axios](/openapi-ts/clients/axios) - [Next.js](/openapi-ts/clients/next-js) - [Nuxt](/openapi-ts/clients/nuxt) -- [ofetch](/openapi-ts/clients/ofetch) +- [OFetch](/openapi-ts/clients/ofetch) - [Effect](/openapi-ts/clients/effect) Soon - [Legacy](/openapi-ts/clients/legacy) diff --git a/docs/openapi-ts/clients/ofetch.md b/docs/openapi-ts/clients/ofetch.md index ee4bdca3d..411211cda 100644 --- a/docs/openapi-ts/clients/ofetch.md +++ b/docs/openapi-ts/clients/ofetch.md @@ -1,5 +1,5 @@ --- -title: ofetch Client +title: OFetch Client description: Generate a type-safe ofetch client from OpenAPI with the ofetch client for openapi-ts. Fully compatible with validators, transformers, and all core features. --- @@ -7,7 +7,7 @@ description: Generate a type-safe ofetch client from OpenAPI with the ofetch cli import { embedProject } from '../../embed' -# ofetch +# OFetch ### About From a37d0a3b8a1f551ed490d9511014038993ce7f55 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Mon, 15 Sep 2025 12:47:07 +0700 Subject: [PATCH 17/26] docs(pinia-colada): add nuxt tips for compatibility --- docs/openapi-ts/plugins/pinia-colada.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/openapi-ts/plugins/pinia-colada.md b/docs/openapi-ts/plugins/pinia-colada.md index a58f6341a..674d25641 100644 --- a/docs/openapi-ts/plugins/pinia-colada.md +++ b/docs/openapi-ts/plugins/pinia-colada.md @@ -35,6 +35,12 @@ The Pinia Colada plugin for Hey API generates functions and query keys from your ## Installation +::: tip Nuxt +When using this plugin in a Nuxt app, prefer the [ofetch client](/openapi-ts/clients/ofetch) for universal compatibility. + +The [nuxt client](/openapi-ts/clients/nuxt) is tailored for working directly with Nuxt composables (`$fetch` / `useFetch` / `useAsyncData`) and is not intended as a universal HTTP client for libraries like `@pinia/colada`. +::: + In your [configuration](/openapi-ts/get-started), add `@pinia/colada` to your plugins and you'll be ready to generate Pinia Colada artifacts. :tada: ```js From 9cf1458aacf9bb7a676cf14f828ed1538cbc877d Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Mon, 15 Sep 2025 18:20:11 +0700 Subject: [PATCH 18/26] chore(ofetch): enhance jsdoc for `Config` interface --- .../@hey-api/client-ofetch/bundle/types.ts | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts index b6e236789..b16d13631 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts @@ -20,14 +20,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -42,10 +52,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -80,7 +103,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? From e1d6b27a958f34b60e51dc7400073e0a86399279 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Mon, 15 Sep 2025 18:30:00 +0700 Subject: [PATCH 19/26] chore(ofetch): cleanup comments --- .../@hey-api/client-ofetch/bundle/client.ts | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/client.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/client.ts index 29d6467d0..5d331e015 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/client.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/client.ts @@ -47,7 +47,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -71,13 +71,13 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && @@ -86,13 +86,13 @@ export const createClient = (config: Config = {}): Client => { ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -102,13 +102,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -119,7 +119,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -129,18 +129,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -158,13 +157,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -178,7 +177,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -208,7 +207,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -226,7 +225,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, From 43e59ffdfbc9bb8fc94e66ad63cbdb2563f7b385 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Mon, 15 Sep 2025 18:34:14 +0700 Subject: [PATCH 20/26] chore: bump snapshots --- .../base-url-false/client/client.gen.ts | 41 +++++++++---------- .../base-url-false/client/types.gen.ts | 35 ++++++++++++++-- .../base-url-number/client/client.gen.ts | 41 +++++++++---------- .../base-url-number/client/types.gen.ts | 35 ++++++++++++++-- .../base-url-strict/client/client.gen.ts | 41 +++++++++---------- .../base-url-strict/client/types.gen.ts | 35 ++++++++++++++-- .../base-url-string/client/client.gen.ts | 41 +++++++++---------- .../base-url-string/client/types.gen.ts | 35 ++++++++++++++-- .../clean-false/client/client.gen.ts | 41 +++++++++---------- .../clean-false/client/types.gen.ts | 35 ++++++++++++++-- .../default/client/client.gen.ts | 41 +++++++++---------- .../client-ofetch/default/client/types.gen.ts | 35 ++++++++++++++-- .../sdk-client-optional/client/client.gen.ts | 41 +++++++++---------- .../sdk-client-optional/client/types.gen.ts | 35 ++++++++++++++-- .../sdk-client-required/client/client.gen.ts | 41 +++++++++---------- .../sdk-client-required/client/types.gen.ts | 35 ++++++++++++++-- .../client/client.gen.ts | 41 +++++++++---------- .../tsconfig-nodenext-sdk/client/types.gen.ts | 35 ++++++++++++++-- .../3.1.x/sse-ofetch/client/client.gen.ts | 41 +++++++++---------- .../3.1.x/sse-ofetch/client/types.gen.ts | 35 ++++++++++++++-- 20 files changed, 520 insertions(+), 240 deletions(-) diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts index aa213e780..c19e53df7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts @@ -49,7 +49,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -73,28 +73,28 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -104,13 +104,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -121,7 +121,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -131,18 +131,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -160,13 +159,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -180,7 +179,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -210,7 +209,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -228,7 +227,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts index e4925b81b..59cfbd7c3 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts @@ -22,14 +22,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -44,10 +54,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -82,7 +105,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts index aa213e780..c19e53df7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts @@ -49,7 +49,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -73,28 +73,28 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -104,13 +104,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -121,7 +121,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -131,18 +131,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -160,13 +159,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -180,7 +179,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -210,7 +209,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -228,7 +227,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts index e4925b81b..59cfbd7c3 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts @@ -22,14 +22,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -44,10 +54,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -82,7 +105,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts index aa213e780..c19e53df7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts @@ -49,7 +49,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -73,28 +73,28 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -104,13 +104,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -121,7 +121,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -131,18 +131,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -160,13 +159,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -180,7 +179,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -210,7 +209,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -228,7 +227,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts index e4925b81b..59cfbd7c3 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts @@ -22,14 +22,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -44,10 +54,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -82,7 +105,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts index aa213e780..c19e53df7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts @@ -49,7 +49,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -73,28 +73,28 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -104,13 +104,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -121,7 +121,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -131,18 +131,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -160,13 +159,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -180,7 +179,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -210,7 +209,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -228,7 +227,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts index e4925b81b..59cfbd7c3 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts @@ -22,14 +22,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -44,10 +54,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -82,7 +105,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts index aa213e780..c19e53df7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts @@ -49,7 +49,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -73,28 +73,28 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -104,13 +104,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -121,7 +121,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -131,18 +131,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -160,13 +159,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -180,7 +179,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -210,7 +209,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -228,7 +227,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts index e4925b81b..59cfbd7c3 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts @@ -22,14 +22,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -44,10 +54,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -82,7 +105,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts index aa213e780..c19e53df7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts @@ -49,7 +49,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -73,28 +73,28 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -104,13 +104,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -121,7 +121,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -131,18 +131,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -160,13 +159,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -180,7 +179,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -210,7 +209,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -228,7 +227,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts index e4925b81b..59cfbd7c3 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts @@ -22,14 +22,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -44,10 +54,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -82,7 +105,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts index aa213e780..c19e53df7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts @@ -49,7 +49,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -73,28 +73,28 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -104,13 +104,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -121,7 +121,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -131,18 +131,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -160,13 +159,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -180,7 +179,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -210,7 +209,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -228,7 +227,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts index e4925b81b..59cfbd7c3 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts @@ -22,14 +22,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -44,10 +54,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -82,7 +105,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts index aa213e780..c19e53df7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts @@ -49,7 +49,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -73,28 +73,28 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -104,13 +104,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -121,7 +121,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -131,18 +131,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -160,13 +159,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -180,7 +179,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -210,7 +209,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -228,7 +227,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts index e4925b81b..59cfbd7c3 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts @@ -22,14 +22,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -44,10 +54,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -82,7 +105,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts index 2a0e427f0..3a9aa8556 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts @@ -49,7 +49,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -73,28 +73,28 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -104,13 +104,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -121,7 +121,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -131,18 +131,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -160,13 +159,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -180,7 +179,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -210,7 +209,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -228,7 +227,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts index 7c8270bf6..6374073d7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts @@ -22,14 +22,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -44,10 +54,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -82,7 +105,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts index aa213e780..c19e53df7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts @@ -49,7 +49,7 @@ export const createClient = (config: Config = {}): Client => { ResolvedRequestOptions >(); - // Resolve final options, serialized body, network body and URL + // precompute serialized / network body const resolveOptions = async (options: RequestOptions) => { const opts = { ..._config, @@ -73,28 +73,28 @@ export const createClient = (config: Config = {}): Client => { opts.serializedBody = opts.bodySerializer(opts.body); } - // remove Content-Type header if body is empty to avoid sending invalid requests + // remove Content-Type if body is empty to avoid invalid requests if (opts.body === undefined || opts.serializedBody === '') { opts.headers.delete('Content-Type'); } - // If user provides a raw body (no serializer), adjust Content-Type sensibly. - // Avoid overriding explicit user-defined headers; only correct the default JSON header. + // if a raw body is provided (no serializer), adjust Content-Type only when it + // equals the default JSON value to better match the concrete body type if ( opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { - // Let the runtime set proper boundary + // let the runtime set the multipart boundary opts.headers.delete('Content-Type'); } else if ( typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams ) { - // Set standard urlencoded content type with charset + // standard urlencoded content type (+ charset) opts.headers.set( 'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8', @@ -104,13 +104,13 @@ export const createClient = (config: Config = {}): Client => { if (t) { opts.headers.set('Content-Type', t); } else { - // No known type for the blob: avoid sending misleading JSON header + // unknown blob type: avoid sending a misleading JSON header opts.headers.delete('Content-Type'); } } } - // Precompute network body for retries and consistent handling + // precompute network body (stability for retries and interceptors) const networkBody = getValidRequestBody(opts) as | RequestInit['body'] | null @@ -121,7 +121,7 @@ export const createClient = (config: Config = {}): Client => { return { networkBody, opts, url }; }; - // Apply request interceptors to a Request and reflect header/method/signal + // apply request interceptors and mirror header/method/signal back to opts const applyRequestInterceptors = async ( request: Request, opts: ResolvedRequestOptions, @@ -131,18 +131,17 @@ export const createClient = (config: Config = {}): Client => { request = await fn(request, opts); } } - // Reflect any interceptor changes into opts used for network and downstream + // reflect interceptor changes into opts used by the network layer opts.headers = request.headers; opts.method = request.method as Uppercase; - // Note: we intentionally ignore request.body changes from interceptors to - // avoid turning serialized bodies into streams. Body is sourced solely - // from getValidRequestBody(options) for consistency. - // Attempt to reflect possible signal changes + // ignore request.body changes to avoid turning serialized bodies into streams + // body comes only from getValidRequestBody(options) + // reflect signal if present opts.signal = (request as any).signal as AbortSignal | undefined; return request; }; - // Build ofetch options with stable retry logic based on body repeatability + // build ofetch options with stable retry logic based on body repeatability const buildNetworkOptions = ( opts: ResolvedRequestOptions, body: BodyInit | null | undefined, @@ -160,13 +159,13 @@ export const createClient = (config: Config = {}): Client => { opts, url, } = await resolveOptions(options as any); - // Compute response type mapping once + // map parseAs -> ofetch responseType once per request const ofetchResponseType: OfetchResponseType | undefined = mapParseAsToResponseType(opts.parseAs, opts.responseType); const $ofetch = opts.ofetch ?? ofetch; - // Always create Request pre-network (align with client-fetch) + // create Request before network to run middleware consistently const networkBody = initialNetworkBody; const requestInit: ReqInit = { body: networkBody, @@ -180,7 +179,7 @@ export const createClient = (config: Config = {}): Client => { request = await applyRequestInterceptors(request, opts); const finalUrl = request.url; - // Build ofetch options and perform the request + // build ofetch options and perform the request (.raw keeps the Response) const responseOptions = buildNetworkOptions( opts as ResolvedRequestOptions, networkBody, @@ -210,7 +209,7 @@ export const createClient = (config: Config = {}): Client => { } } - // Ensure error is never undefined after interceptors + // ensure error is never undefined after interceptors finalError = (finalError as any) || ({} as string); if (opts.throwOnError) { @@ -228,7 +227,7 @@ export const createClient = (config: Config = {}): Client => { (method: Uppercase) => async (options: RequestOptions) => { const { networkBody, opts, url } = await resolveOptions(options); const optsForSse: any = { ...opts }; - delete optsForSse.body; + delete optsForSse.body; // body is provided via serializedBody below return createSseClient({ ...optsForSse, fetch: opts.fetch, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts index e4925b81b..59cfbd7c3 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts @@ -22,14 +22,24 @@ export type ResponseStyle = 'data' | 'fields'; export interface Config extends Omit, CoreConfig { + /** + * HTTP(S) agent configuration (Node.js only). Passed through to ofetch. + */ agent?: OfetchOptions['agent']; /** * Base URL for all requests made by this client. */ baseUrl?: T['baseUrl']; - /** Node-only proxy/agent options */ + /** + * Node-only proxy/agent options. + */ dispatcher?: OfetchOptions['dispatcher']; - /** Optional fetch instance used for SSE streaming */ + /** + * Fetch API implementation. Used for SSE streaming. You can use this option + * to provide a custom fetch instance. + * + * @default globalThis.fetch + */ fetch?: typeof fetch; // No custom fetch option: provide custom instance via `ofetch` instead /** @@ -44,10 +54,23 @@ export interface Config * be used for requests instead of the default `ofetch` export. */ ofetch?: typeof ofetch; - /** ofetch interceptors and runtime options */ + /** + * ofetch hook called before a request is sent. + */ onRequest?: OfetchOptions['onRequest']; + /** + * ofetch hook called when a request fails before receiving a response + * (e.g., network errors or aborted requests). + */ onRequestError?: OfetchOptions['onRequestError']; + /** + * ofetch hook called after a successful response is received and parsed. + */ onResponse?: OfetchOptions['onResponse']; + /** + * ofetch hook called when the response indicates an error (non-ok status) + * or when response parsing fails. + */ onResponseError?: OfetchOptions['onResponseError']; /** * Return the response data parsed in a specified format. By default, `auto` @@ -82,7 +105,13 @@ export interface Config * Automatically retry failed requests. */ retry?: OfetchOptions['retry']; + /** + * Delay (in ms) between retry attempts. + */ retryDelay?: OfetchOptions['retryDelay']; + /** + * HTTP status codes that should trigger a retry. + */ retryStatusCodes?: OfetchOptions['retryStatusCodes']; /** * Throw an error instead of returning it in the response? From 9793e70b96bffc664410a7a17c2c5c56cefb1980 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Wed, 17 Sep 2025 16:10:34 +0700 Subject: [PATCH 21/26] fix(client-ofetch): keep error responses from throwing --- .../src/plugins/@hey-api/client-ofetch/bundle/types.ts | 6 ++++++ .../src/plugins/@hey-api/client-ofetch/bundle/utils.ts | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts index b16d13631..3864bae84 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/types.ts @@ -39,6 +39,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts index 2e7db688d..9a02d8d1e 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts @@ -322,6 +322,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -530,6 +533,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, From f8cb3fdbcbce3114f8959db0ac29147e5e96a7c4 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Wed, 17 Sep 2025 16:23:23 +0700 Subject: [PATCH 22/26] fix(client-ofetch): return `FormData` when `parseAs` is `formData` --- .../src/plugins/@hey-api/client-ofetch/bundle/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts index 9a02d8d1e..b2f1d5552 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/bundle/utils.ts @@ -379,9 +379,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': From ef329e3e7899f37183363507ebf21374837c80f4 Mon Sep 17 00:00:00 2001 From: Dmitriy Brolnickij Date: Wed, 17 Sep 2025 16:28:42 +0700 Subject: [PATCH 23/26] chore(client-ofetch): bump snapshots --- .../client-ofetch/base-url-false/client/client.gen.ts | 2 +- .../client-ofetch/base-url-false/client/types.gen.ts | 6 ++++++ .../client-ofetch/base-url-false/client/utils.gen.ts | 8 ++++++-- .../client-ofetch/base-url-number/client/client.gen.ts | 2 +- .../client-ofetch/base-url-number/client/types.gen.ts | 6 ++++++ .../client-ofetch/base-url-number/client/utils.gen.ts | 8 ++++++-- .../client-ofetch/base-url-strict/client/client.gen.ts | 2 +- .../client-ofetch/base-url-strict/client/types.gen.ts | 6 ++++++ .../client-ofetch/base-url-strict/client/utils.gen.ts | 8 ++++++-- .../client-ofetch/base-url-string/client/client.gen.ts | 2 +- .../client-ofetch/base-url-string/client/types.gen.ts | 6 ++++++ .../client-ofetch/base-url-string/client/utils.gen.ts | 8 ++++++-- .../client-ofetch/clean-false/client/client.gen.ts | 2 +- .../client-ofetch/clean-false/client/types.gen.ts | 6 ++++++ .../client-ofetch/clean-false/client/utils.gen.ts | 8 ++++++-- .../@hey-api/client-ofetch/default/client/client.gen.ts | 2 +- .../@hey-api/client-ofetch/default/client/types.gen.ts | 6 ++++++ .../@hey-api/client-ofetch/default/client/utils.gen.ts | 8 ++++++-- .../sdk-client-optional/client/client.gen.ts | 2 +- .../client-ofetch/sdk-client-optional/client/types.gen.ts | 6 ++++++ .../client-ofetch/sdk-client-optional/client/utils.gen.ts | 8 ++++++-- .../sdk-client-required/client/client.gen.ts | 2 +- .../client-ofetch/sdk-client-required/client/types.gen.ts | 6 ++++++ .../client-ofetch/sdk-client-required/client/utils.gen.ts | 8 ++++++-- .../tsconfig-nodenext-sdk/client/client.gen.ts | 2 +- .../tsconfig-nodenext-sdk/client/types.gen.ts | 6 ++++++ .../tsconfig-nodenext-sdk/client/utils.gen.ts | 8 ++++++-- .../__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts | 2 +- .../__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts | 6 ++++++ .../__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts | 8 ++++++-- 30 files changed, 130 insertions(+), 30 deletions(-) diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts index c19e53df7..e0e3c2a41 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/client.gen.ts @@ -84,7 +84,7 @@ export const createClient = (config: Config = {}): Client => { opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts index 59cfbd7c3..b44644598 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/types.gen.ts @@ -41,6 +41,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts index afc030c6e..1d6d23856 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client/utils.gen.ts @@ -324,6 +324,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -378,9 +381,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': @@ -532,6 +535,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts index c19e53df7..e0e3c2a41 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/client.gen.ts @@ -84,7 +84,7 @@ export const createClient = (config: Config = {}): Client => { opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts index 59cfbd7c3..b44644598 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/types.gen.ts @@ -41,6 +41,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts index afc030c6e..1d6d23856 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client/utils.gen.ts @@ -324,6 +324,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -378,9 +381,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': @@ -532,6 +535,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts index c19e53df7..e0e3c2a41 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/client.gen.ts @@ -84,7 +84,7 @@ export const createClient = (config: Config = {}): Client => { opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts index 59cfbd7c3..b44644598 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/types.gen.ts @@ -41,6 +41,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts index afc030c6e..1d6d23856 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client/utils.gen.ts @@ -324,6 +324,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -378,9 +381,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': @@ -532,6 +535,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts index c19e53df7..e0e3c2a41 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/client.gen.ts @@ -84,7 +84,7 @@ export const createClient = (config: Config = {}): Client => { opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts index 59cfbd7c3..b44644598 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/types.gen.ts @@ -41,6 +41,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts index afc030c6e..1d6d23856 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client/utils.gen.ts @@ -324,6 +324,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -378,9 +381,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': @@ -532,6 +535,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts index c19e53df7..e0e3c2a41 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/client.gen.ts @@ -84,7 +84,7 @@ export const createClient = (config: Config = {}): Client => { opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts index 59cfbd7c3..b44644598 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/types.gen.ts @@ -41,6 +41,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts index afc030c6e..1d6d23856 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client/utils.gen.ts @@ -324,6 +324,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -378,9 +381,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': @@ -532,6 +535,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts index c19e53df7..e0e3c2a41 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/client.gen.ts @@ -84,7 +84,7 @@ export const createClient = (config: Config = {}): Client => { opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts index 59cfbd7c3..b44644598 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/types.gen.ts @@ -41,6 +41,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts index afc030c6e..1d6d23856 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client/utils.gen.ts @@ -324,6 +324,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -378,9 +381,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': @@ -532,6 +535,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts index c19e53df7..e0e3c2a41 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/client.gen.ts @@ -84,7 +84,7 @@ export const createClient = (config: Config = {}): Client => { opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts index 59cfbd7c3..b44644598 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/types.gen.ts @@ -41,6 +41,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts index afc030c6e..1d6d23856 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client/utils.gen.ts @@ -324,6 +324,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -378,9 +381,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': @@ -532,6 +535,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts index c19e53df7..e0e3c2a41 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/client.gen.ts @@ -84,7 +84,7 @@ export const createClient = (config: Config = {}): Client => { opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts index 59cfbd7c3..b44644598 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/types.gen.ts @@ -41,6 +41,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts index afc030c6e..1d6d23856 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client/utils.gen.ts @@ -324,6 +324,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -378,9 +381,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': @@ -532,6 +535,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts index 3a9aa8556..3e1cf2053 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/client.gen.ts @@ -84,7 +84,7 @@ export const createClient = (config: Config = {}): Client => { opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts index 6374073d7..3dc1783a7 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/types.gen.ts @@ -41,6 +41,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts index 48fbf4d6a..b42a0a97c 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client/utils.gen.ts @@ -324,6 +324,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -378,9 +381,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': @@ -532,6 +535,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts index c19e53df7..e0e3c2a41 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/client.gen.ts @@ -84,7 +84,7 @@ export const createClient = (config: Config = {}): Client => { opts.body !== undefined && opts.bodySerializer === null && (opts.headers.get('Content-Type') || '').toLowerCase() === - 'application/json' + 'application/json' ) { const b: unknown = opts.body; if (typeof FormData !== 'undefined' && b instanceof FormData) { diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts index 59cfbd7c3..b44644598 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/types.gen.ts @@ -41,6 +41,12 @@ export interface Config * @default globalThis.fetch */ fetch?: typeof fetch; + /** + * Controls the native ofetch behaviour that throws `FetchError` when + * `response.ok === false`. We default to suppressing it to match the fetch + * client semantics and let `throwOnError` drive the outcome. + */ + ignoreResponseError?: OfetchOptions['ignoreResponseError']; // No custom fetch option: provide custom instance via `ofetch` instead /** * Please don't use the Fetch client for Next.js applications. The `next` diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts index afc030c6e..1d6d23856 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client/utils.gen.ts @@ -324,6 +324,9 @@ export const buildOfetchOptions = ( body, dispatcher: opts.dispatcher as OfetchOptions['dispatcher'], headers: opts.headers as Headers, + ignoreResponseError: + (opts.ignoreResponseError as OfetchOptions['ignoreResponseError']) ?? + true, method: opts.method, onRequest: opts.onRequest as OfetchOptions['onRequest'], onRequestError: opts.onRequestError as OfetchOptions['onRequestError'], @@ -378,9 +381,9 @@ export const parseSuccess = async ( } } - // Prefer ofetch-populated data + // Prefer ofetch-populated data unless we explicitly need raw `formData` let data: unknown = (response as any)._data; - if (typeof data === 'undefined') { + if (inferredParseAs === 'formData' || typeof data === 'undefined') { switch (inferredParseAs) { case 'arrayBuffer': case 'blob': @@ -532,6 +535,7 @@ export const createConfig = ( ): Config & T> => ({ ...jsonBodySerializer, headers: defaultHeaders, + ignoreResponseError: true, parseAs: 'auto', querySerializer: defaultQuerySerializer, ...override, From 7322c677f348a0a1ce1e57450d1f96868d711e7c Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 23 Sep 2025 03:51:29 +0800 Subject: [PATCH 24/26] docs: update client pages --- .changeset/init-ofetch.md | 4 +- docs/openapi-ts/clients/angular.md | 6 +- docs/openapi-ts/clients/angular/v19.md | 6 +- docs/openapi-ts/clients/axios.md | 4 +- docs/openapi-ts/clients/fetch.md | 40 +++++++------ docs/openapi-ts/clients/next-js.md | 40 +++++++------ docs/openapi-ts/clients/nuxt.md | 6 +- docs/openapi-ts/clients/ofetch.md | 80 +++++++++++--------------- 8 files changed, 93 insertions(+), 93 deletions(-) diff --git a/.changeset/init-ofetch.md b/.changeset/init-ofetch.md index 836fda780..7311a0907 100644 --- a/.changeset/init-ofetch.md +++ b/.changeset/init-ofetch.md @@ -1,5 +1,5 @@ --- -"@hey-api/openapi-ts": minor +"@hey-api/openapi-ts": patch --- -feat: init `client-ofetch` +feat: add `ofetch` client available as `@hey-api/client-ofetch` diff --git a/docs/openapi-ts/clients/angular.md b/docs/openapi-ts/clients/angular.md index 87a2e22ce..13905848b 100644 --- a/docs/openapi-ts/clients/angular.md +++ b/docs/openapi-ts/clients/angular.md @@ -231,9 +231,9 @@ const url = client.buildUrl({ console.log(url); // prints '/foo/1?bar=baz' ``` -## Custom `httpClient` +## Custom Instance -You can implement your own `httpClient`. This is useful if you need to extend the default `httpClient` methods with extra functionality, or replace it altogether. +You can provide a custom `httpClient` instance. This is useful if you need to extend the default instance with extra functionality, or replace it altogether. ```js import { client } from 'client/client.gen'; @@ -243,7 +243,7 @@ client.setConfig({ }); ``` -You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom client to be. +You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom instance to be. ## Plugins diff --git a/docs/openapi-ts/clients/angular/v19.md b/docs/openapi-ts/clients/angular/v19.md index b755acde4..235abafac 100644 --- a/docs/openapi-ts/clients/angular/v19.md +++ b/docs/openapi-ts/clients/angular/v19.md @@ -231,9 +231,9 @@ const url = client.buildUrl({ console.log(url); // prints '/foo/1?bar=baz' ``` -## Custom `httpClient` +## Custom Instance -You can implement your own `httpClient`. This is useful if you need to extend the default `httpClient` methods with extra functionality, or replace it altogether. +You can provide a custom `httpClient` instance. This is useful if you need to extend the default instance with extra functionality, or replace it altogether. ```js import { client } from 'client/client.gen'; @@ -243,7 +243,7 @@ client.setConfig({ }); ``` -You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom client to be. +You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom instance to be. ## Plugins diff --git a/docs/openapi-ts/clients/axios.md b/docs/openapi-ts/clients/axios.md index b485ee1d8..1e2312228 100644 --- a/docs/openapi-ts/clients/axios.md +++ b/docs/openapi-ts/clients/axios.md @@ -211,9 +211,9 @@ const url = client.buildUrl({ console.log(url); // prints '/foo/1?bar=baz' ``` -## Custom `axios` +## Custom Instance -You can implement your own `axios` instance. This is useful if you need to extend the default `axios` instance with extra functionality, or replace it altogether. +You can provide a custom `axios` instance. This is useful if you need to extend the default instance with extra functionality, or replace it altogether. ```js import axios from 'axios'; diff --git a/docs/openapi-ts/clients/fetch.md b/docs/openapi-ts/clients/fetch.md index 9691d3fa4..192791758 100644 --- a/docs/openapi-ts/clients/fetch.md +++ b/docs/openapi-ts/clients/fetch.md @@ -146,27 +146,32 @@ const response = await getFoo({ ## Interceptors -Interceptors (middleware) can be used to modify requests before they're sent or responses before they're returned to your application. They can be added with `use`, removed with `eject`, and updated wth `update`. The `use` and `update` methods will return the id of the interceptor for use with `eject` and `update`. Fetch API does not have the interceptor functionality, so we implement our own. Below is an example request interceptor +Interceptors (middleware) can be used to modify requests before they're sent or responses before they're returned to your application. + +They can be added with `use`, removed with `eject`, and updated wth `update`. The `use` and `update` methods will return the ID of the interceptor for use with `eject` and `update`. Fetch API does not have the interceptor functionality, so we implement our own. + +### Example: Request interceptor ::: code-group ```js [use] import { client } from 'client/client.gen'; -// Supports async functions + async function myInterceptor(request) { // do something return request; } + interceptorId = client.interceptors.request.use(myInterceptor); ``` ```js [eject] import { client } from 'client/client.gen'; -// eject interceptor by interceptor id +// eject by ID client.interceptors.request.eject(interceptorId); -// eject interceptor by reference to interceptor function +// eject by reference client.interceptors.request.eject(myInterceptor); ``` @@ -177,36 +182,38 @@ async function myNewInterceptor(request) { // do something return request; } -// update interceptor by interceptor id + +// update by ID client.interceptors.request.update(interceptorId, myNewInterceptor); -// update interceptor by reference to interceptor function +// update by reference client.interceptors.request.update(myInterceptor, myNewInterceptor); ``` ::: -and an example response interceptor +### Example: Response interceptor ::: code-group ```js [use] import { client } from 'client/client.gen'; + async function myInterceptor(response) { // do something return response; } -// Supports async functions + interceptorId = client.interceptors.response.use(myInterceptor); ``` ```js [eject] import { client } from 'client/client.gen'; -// eject interceptor by interceptor id +// eject by ID client.interceptors.response.eject(interceptorId); -// eject interceptor by reference to interceptor function +// eject by reference client.interceptors.response.eject(myInterceptor); ``` @@ -217,17 +224,18 @@ async function myNewInterceptor(response) { // do something return response; } -// update interceptor by interceptor id + +// update by ID client.interceptors.response.update(interceptorId, myNewInterceptor); -// update interceptor by reference to interceptor function +// update by reference client.interceptors.response.update(myInterceptor, myNewInterceptor); ``` ::: ::: tip -To eject, you must provide the id or reference of the interceptor passed to `use()`, the id is the value returned by `use()` and `update()`. +To eject, you must provide the ID or reference of the interceptor passed to `use()`, the ID is the value returned by `use()` and `update()`. ::: ## Auth @@ -281,9 +289,9 @@ const url = client.buildUrl({ console.log(url); // prints '/foo/1?bar=baz' ``` -## Custom `fetch` +## Custom Instance -You can implement your own `fetch` method. This is useful if you need to extend the default `fetch` method with extra functionality, or replace it altogether. +You can provide a custom `fetch` instance. This is useful if you need to extend the default instance with extra functionality, or replace it altogether. ```js import { client } from 'client/client.gen'; @@ -295,7 +303,7 @@ client.setConfig({ }); ``` -You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom method to be. +You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom instance to be. ## API diff --git a/docs/openapi-ts/clients/next-js.md b/docs/openapi-ts/clients/next-js.md index f9013ad65..2ede4ffd2 100644 --- a/docs/openapi-ts/clients/next-js.md +++ b/docs/openapi-ts/clients/next-js.md @@ -130,27 +130,32 @@ const response = await getFoo({ ## Interceptors -Interceptors (middleware) can be used to modify requests before they're sent or responses before they're returned to your application. They can be added with `use`, removed with `eject`, and updated wth `update`. The `use` and `update` methods will return the id of the interceptor for use with `eject` and `update`. Fetch API does not have the interceptor functionality, so we implement our own. Below is an example request interceptor +Interceptors (middleware) can be used to modify requests before they're sent or responses before they're returned to your application. + +They can be added with `use`, removed with `eject`, and updated wth `update`. The `use` and `update` methods will return the ID of the interceptor for use with `eject` and `update`. Fetch API does not have the interceptor functionality, so we implement our own. + +### Example: Request interceptor ::: code-group ```js [use] import { client } from 'client/client.gen'; -// Supports async functions + async function myInterceptor(request) { // do something return request; } + interceptorId = client.interceptors.request.use(myInterceptor); ``` ```js [eject] import { client } from 'client/client.gen'; -// eject interceptor by interceptor id +// eject by ID client.interceptors.request.eject(interceptorId); -// eject interceptor by reference to interceptor function +// eject by reference client.interceptors.request.eject(myInterceptor); ``` @@ -161,36 +166,38 @@ async function myNewInterceptor(request) { // do something return request; } -// update interceptor by interceptor id + +// update by ID client.interceptors.request.update(interceptorId, myNewInterceptor); -// update interceptor by reference to interceptor function +// update by reference client.interceptors.request.update(myInterceptor, myNewInterceptor); ``` ::: -and an example response interceptor +### Example: Response interceptor ::: code-group ```js [use] import { client } from 'client/client.gen'; + async function myInterceptor(response) { // do something return response; } -// Supports async functions + interceptorId = client.interceptors.response.use(myInterceptor); ``` ```js [eject] import { client } from 'client/client.gen'; -// eject interceptor by interceptor id +// eject by ID client.interceptors.response.eject(interceptorId); -// eject interceptor by reference to interceptor function +// eject by reference client.interceptors.response.eject(myInterceptor); ``` @@ -201,17 +208,18 @@ async function myNewInterceptor(response) { // do something return response; } -// update interceptor by interceptor id + +// update by ID client.interceptors.response.update(interceptorId, myNewInterceptor); -// update interceptor by reference to interceptor function +// update by reference client.interceptors.response.update(myInterceptor, myNewInterceptor); ``` ::: ::: tip -To eject, you must provide the id or reference of the interceptor passed to `use()`, the id is the value returned by `use()` and `update()`. +To eject, you must provide the ID or reference of the interceptor passed to `use()`, the ID is the value returned by `use()` and `update()`. ::: ## Auth @@ -264,9 +272,9 @@ const url = client.buildUrl({ console.log(url); // prints '/foo/1?bar=baz' ``` -## Custom `fetch` +## Custom Instance -You can implement your own `fetch` method. This is useful if you need to extend the default `fetch` method with extra functionality, or replace it altogether. +You can provide a custom `fetch` instance. This is useful if you need to extend the default instance with extra functionality, or replace it altogether. ```js import { client } from 'client/client.gen'; @@ -278,7 +286,7 @@ client.setConfig({ }); ``` -You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom method to be. +You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom instance to be. ## API diff --git a/docs/openapi-ts/clients/nuxt.md b/docs/openapi-ts/clients/nuxt.md index 333914f49..21fe7c723 100644 --- a/docs/openapi-ts/clients/nuxt.md +++ b/docs/openapi-ts/clients/nuxt.md @@ -243,9 +243,9 @@ const url = client.buildUrl({ console.log(url); // prints '/foo/1?bar=baz' ``` -## Custom `$fetch` +## Custom Instance -You can implement your own `$fetch` method. This is useful if you need to extend the default `$fetch` method with extra functionality, or replace it altogether. +You can provide a custom `$fetch` instance. This is useful if you need to extend the default instance with extra functionality, or replace it altogether. ```js import { client } from 'client/client.gen'; @@ -257,7 +257,7 @@ client.setConfig({ }); ``` -You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom method to be. +You can use any of the approaches mentioned in [Configuration](#configuration), depending on how granular you want your custom instance to be. ## API diff --git a/docs/openapi-ts/clients/ofetch.md b/docs/openapi-ts/clients/ofetch.md index 411211cda..4798a0b1b 100644 --- a/docs/openapi-ts/clients/ofetch.md +++ b/docs/openapi-ts/clients/ofetch.md @@ -3,17 +3,13 @@ title: OFetch Client description: Generate a type-safe ofetch client from OpenAPI with the ofetch client for openapi-ts. Fully compatible with validators, transformers, and all core features. --- - - # OFetch ### About -[ofetch](https://github.com/unjs/ofetch) is a lightweight wrapper around the Fetch API that adds useful defaults and features such as automatic response parsing, request/response hooks, and Node.js support. +[`ofetch`](https://github.com/unjs/ofetch) is a better Fetch API that adds useful defaults and features such as automatic response parsing, request/response hooks, and it works in Node, browser, and workers. -The ofetch client for Hey API generates a type-safe client from your OpenAPI spec, fully compatible with validators, transformers, and all core features. +The `ofetch` client for Hey API generates a type-safe client from your OpenAPI spec, fully compatible with validators, transformers, and all core features. ## Features @@ -50,13 +46,13 @@ npx @hey-api/openapi-ts \ ## Configuration -The ofetch client is built as a thin wrapper on top of ofetch, extending its functionality to work with Hey API. If you're already familiar with ofetch, configuring your client will feel like working directly with ofetch. +The `ofetch` client is built as a thin wrapper on top of `ofetch`, extending its functionality to work with Hey API. If you're already familiar with `ofetch`, configuring your client will feel like working directly with `ofetch`. When we installed the client above, it created a [`client.gen.ts`](/openapi-ts/output#client) file. You will most likely want to configure the exported `client` instance. There are two ways to do that. ### `setConfig()` -This is the simpler approach. You can call the `setConfig()` method at the beginning of your application or anytime you need to update the client configuration. You can pass any ofetch configuration option to `setConfig()` (including ofetch hooks), and even your own [ofetch](#custom-ofetch) instance. +This is the simpler approach. You can call the `setConfig()` method at the beginning of your application or anytime you need to update the client configuration. You can pass any `ofetch` configuration option to `setConfig()`, and even your own [`ofetch`](#custom-ofetch) instance. ```js import { client } from 'client/client.gen'; @@ -136,32 +132,33 @@ const response = await getFoo({ Interceptors (middleware) can be used to modify requests before they're sent or responses before they're returned to your application. -The ofetch client supports two complementary options: +The `ofetch` client supports two complementary options: -- Built-in interceptors exposed via `client.interceptors` (request/response/error) — same API across Hey API clients. -- Native ofetch hooks passed through config: `onRequest`, `onRequestError`, `onResponse`, `onResponseError`. +- built-in Hey API interceptors exposed via `client.interceptors` +- native `ofetch` hooks passed through config (e.g. `onRequest`) -Below is an example request interceptor using the built-in API. +### Example: Request interceptor ::: code-group ```js [use] import { client } from 'client/client.gen'; -// Supports async functions + async function myInterceptor(request) { // do something return request; } + interceptorId = client.interceptors.request.use(myInterceptor); ``` ```js [eject] import { client } from 'client/client.gen'; -// eject interceptor by interceptor id +// eject by ID client.interceptors.request.eject(interceptorId); -// eject interceptor by reference to interceptor function +// eject by reference client.interceptors.request.eject(myInterceptor); ``` @@ -172,36 +169,38 @@ async function myNewInterceptor(request) { // do something return request; } -// update interceptor by interceptor id + +// update by ID client.interceptors.request.update(interceptorId, myNewInterceptor); -// update interceptor by reference to interceptor function +// update by reference client.interceptors.request.update(myInterceptor, myNewInterceptor); ``` ::: -and an example response interceptor +### Example: Response interceptor ::: code-group ```js [use] import { client } from 'client/client.gen'; + async function myInterceptor(response) { // do something return response; } -// Supports async functions + interceptorId = client.interceptors.response.use(myInterceptor); ``` ```js [eject] import { client } from 'client/client.gen'; -// eject interceptor by interceptor id +// eject by ID client.interceptors.response.eject(interceptorId); -// eject interceptor by reference to interceptor function +// eject by reference client.interceptors.response.eject(myInterceptor); ``` @@ -212,7 +211,8 @@ async function myNewInterceptor(response) { // do something return response; } -// update interceptor by interceptor id + +// update interceptor by interceptor ID client.interceptors.response.update(interceptorId, myNewInterceptor); // update interceptor by reference to interceptor function @@ -221,7 +221,11 @@ client.interceptors.response.update(myInterceptor, myNewInterceptor); ::: -You can also use native ofetch hooks by passing them in config: +::: tip +To eject, you must provide the ID or reference of the interceptor passed to `use()`, the ID is the value returned by `use()` and `update()`. +::: + +### Example: `ofetch` hooks ```js import { client } from 'client/client.gen'; @@ -242,10 +246,6 @@ client.setConfig({ }); ``` -::: tip -To eject, you must provide the id or reference of the interceptor passed to `use()`, the id is the value returned by `use()` and `update()`. -::: - ## Auth The SDKs include auth mechanisms for every endpoint. You will want to configure the `auth` field to pass the right token for each request. The `auth` field can be a string or a function returning a string representing the token. The returned value will be attached only to requests that require auth. @@ -270,7 +270,7 @@ client.interceptors.request.use((request, options) => { }); ``` -or with ofetch hooks: +You can also use the `ofetch` hooks. ```js import { client } from 'client/client.gen'; @@ -282,22 +282,6 @@ client.setConfig({ }); ``` -## SSE - -The ofetch client supports Server‑Sent Events (SSE) via `client.sse.`. Use this for streaming endpoints. - -```ts -const result = await client.sse.get({ - url: '/events', - onSseEvent(event) { - // event.data (string) or parsed data, depending on server - }, - onSseError(error) { - // handle stream errors - }, -}); -``` - ## Build URL If you need to access the compiled URL, you can use the `buildUrl()` method. It's loosely typed by default to accept almost any value; in practice, you will want to pass a type hint. @@ -325,15 +309,15 @@ const url = client.buildUrl({ console.log(url); // prints '/foo/1?bar=baz' ``` -## Custom `ofetch` +## Custom Instance -You can provide a custom ofetch instance. This is useful if you need to extend ofetch with extra functionality (hooks, retry behavior, etc.) or replace it altogether. +You can provide a custom `ofetch` instance. This is useful if you need to extend the default instance with extra functionality, or replace it altogether. ```js import { ofetch } from 'ofetch'; import { client } from 'client/client.gen'; -const $ofetch = ofetch.create({ +const customOFetchInstance = ofetch.create({ onRequest: ({ options }) => { // customize request }, @@ -343,7 +327,7 @@ const $ofetch = ofetch.create({ }); client.setConfig({ - ofetch: $ofetch, + ofetch: customOFetchInstance, }); ``` From 492a4e71cbdda10298db32e55c54c0625f1b9af3 Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 23 Sep 2025 04:06:18 +0800 Subject: [PATCH 25/26] chore: fix types --- docs/openapi-ts/plugins/pinia-colada.md | 12 ++++++------ packages/openapi-ts-tests/main/test/clients.test.ts | 2 +- .../src/plugins/@hey-api/client-core/types.d.ts | 2 +- .../src/plugins/@hey-api/client-ofetch/api.ts | 10 ++++------ packages/openapi-ts/tsup.config.ts | 2 +- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/openapi-ts/plugins/pinia-colada.md b/docs/openapi-ts/plugins/pinia-colada.md index 674d25641..5ec2a00cc 100644 --- a/docs/openapi-ts/plugins/pinia-colada.md +++ b/docs/openapi-ts/plugins/pinia-colada.md @@ -35,12 +35,6 @@ The Pinia Colada plugin for Hey API generates functions and query keys from your ## Installation -::: tip Nuxt -When using this plugin in a Nuxt app, prefer the [ofetch client](/openapi-ts/clients/ofetch) for universal compatibility. - -The [nuxt client](/openapi-ts/clients/nuxt) is tailored for working directly with Nuxt composables (`$fetch` / `useFetch` / `useAsyncData`) and is not intended as a universal HTTP client for libraries like `@pinia/colada`. -::: - In your [configuration](/openapi-ts/get-started), add `@pinia/colada` to your plugins and you'll be ready to generate Pinia Colada artifacts. :tada: ```js @@ -54,6 +48,12 @@ export default { }; ``` +::: tip +When using this plugin in a Nuxt app, prefer the [ofetch client](/openapi-ts/clients/ofetch) for universal compatibility. + +The [nuxt client](/openapi-ts/clients/nuxt) is tailored for working directly with Nuxt composables (`$fetch` / `useFetch` / `useAsyncData`) and is not intended as a universal HTTP client for libraries like `@pinia/colada`. +::: + ## Output The Pinia Colada plugin will generate the following artifacts, depending on the input specification. diff --git a/packages/openapi-ts-tests/main/test/clients.test.ts b/packages/openapi-ts-tests/main/test/clients.test.ts index 30c9cc68a..cfbce6b60 100644 --- a/packages/openapi-ts-tests/main/test/clients.test.ts +++ b/packages/openapi-ts-tests/main/test/clients.test.ts @@ -16,9 +16,9 @@ const __dirname = path.dirname(__filename); const clients: ReadonlyArray = [ '@hey-api/client-axios', '@hey-api/client-fetch', - '@hey-api/client-ofetch', '@hey-api/client-next', '@hey-api/client-nuxt', + '@hey-api/client-ofetch', ]; for (const client of clients) { diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-core/types.d.ts b/packages/openapi-ts/src/plugins/@hey-api/client-core/types.d.ts index 97fcc7371..0b328ab78 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-core/types.d.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-core/types.d.ts @@ -10,9 +10,9 @@ export interface PluginHandler { (...args: Parameters): void; (...args: Parameters): void; (...args: Parameters): void; - (...args: Parameters): void; (...args: Parameters): void; (...args: Parameters): void; + (...args: Parameters): void; } /** diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/api.ts b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/api.ts index a54a8ec1b..11a238387 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/api.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-ofetch/api.ts @@ -1,4 +1,4 @@ -import type { ICodegenSymbolSelector } from '@hey-api/codegen-core'; +import type { Selector } from '@hey-api/codegen-core'; import type { Plugin } from '../../types'; @@ -11,15 +11,13 @@ export type IApi = { * - `client`: never * @returns Selector array */ - getSelector: (type: SelectorType, value?: string) => ICodegenSymbolSelector; + getSelector: (type: SelectorType, value?: string) => Selector; }; export class Api implements IApi { constructor(public meta: Plugin.Name<'@hey-api/client-ofetch'>) {} - getSelector( - ...args: ReadonlyArray - ): ICodegenSymbolSelector { - return [this.meta.name, ...(args as ICodegenSymbolSelector)]; + getSelector(...args: ReadonlyArray): Selector { + return [this.meta.name, ...(args as Selector)]; } } diff --git a/packages/openapi-ts/tsup.config.ts b/packages/openapi-ts/tsup.config.ts index 31c377e97..5cef330f1 100644 --- a/packages/openapi-ts/tsup.config.ts +++ b/packages/openapi-ts/tsup.config.ts @@ -39,9 +39,9 @@ export default defineConfig((options) => ({ 'client-axios', 'client-core', 'client-fetch', - 'client-ofetch', 'client-next', 'client-nuxt', + 'client-ofetch', ]; for (const pluginName of pluginNames) { From 0e4a06380620d4704891e16dbbaaca03486d5fd8 Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 23 Sep 2025 04:10:09 +0800 Subject: [PATCH 26/26] test: update snapshots --- .../main/scripts/gen-ofetch.mjs | 133 ------------------ .../base-url-false/client.gen.ts | 8 +- .../client-ofetch/base-url-false/index.ts | 2 +- .../client-ofetch/base-url-false/types.gen.ts | 8 +- .../base-url-number/client.gen.ts | 8 +- .../client-ofetch/base-url-number/index.ts | 2 +- .../base-url-number/types.gen.ts | 8 +- .../base-url-strict/client.gen.ts | 8 +- .../client-ofetch/base-url-strict/index.ts | 2 +- .../base-url-strict/types.gen.ts | 8 +- .../base-url-string/client.gen.ts | 8 +- .../client-ofetch/base-url-string/index.ts | 2 +- .../base-url-string/types.gen.ts | 8 +- .../client-ofetch/clean-false/client.gen.ts | 8 +- .../client-ofetch/clean-false/index.ts | 2 +- .../client-ofetch/clean-false/sdk.gen.ts | 8 +- .../client-ofetch/clean-false/types.gen.ts | 8 +- .../client-ofetch/default/client.gen.ts | 8 +- .../@hey-api/client-ofetch/default/index.ts | 2 +- .../@hey-api/client-ofetch/default/sdk.gen.ts | 8 +- .../client-ofetch/default/types.gen.ts | 8 +- .../sdk-client-optional/client.gen.ts | 8 +- .../sdk-client-optional/index.ts | 2 +- .../sdk-client-optional/sdk.gen.ts | 8 +- .../sdk-client-optional/types.gen.ts | 8 +- .../sdk-client-required/client.gen.ts | 8 +- .../sdk-client-required/index.ts | 2 +- .../sdk-client-required/sdk.gen.ts | 8 +- .../sdk-client-required/types.gen.ts | 8 +- .../tsconfig-nodenext-sdk/client.gen.ts | 8 +- .../tsconfig-nodenext-sdk/index.ts | 2 +- .../tsconfig-nodenext-sdk/sdk.gen.ts | 8 +- .../tsconfig-nodenext-sdk/types.gen.ts | 8 +- .../3.1.x/sse-ofetch/client.gen.ts | 8 +- .../__snapshots__/3.1.x/sse-ofetch/index.ts | 2 +- .../__snapshots__/3.1.x/sse-ofetch/sdk.gen.ts | 6 +- .../3.1.x/sse-ofetch/types.gen.ts | 8 +- 37 files changed, 113 insertions(+), 246 deletions(-) delete mode 100644 packages/openapi-ts-tests/main/scripts/gen-ofetch.mjs diff --git a/packages/openapi-ts-tests/main/scripts/gen-ofetch.mjs b/packages/openapi-ts-tests/main/scripts/gen-ofetch.mjs deleted file mode 100644 index a7963e1ef..000000000 --- a/packages/openapi-ts-tests/main/scripts/gen-ofetch.mjs +++ /dev/null @@ -1,133 +0,0 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -import { createClient } from '@hey-api/openapi-ts'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const root = path.resolve(__dirname, '..'); -const getSpecsPath = () => path.resolve(root, '..', 'specs'); - -const version = '3.1.x'; -const namespace = 'clients'; -const clientName = '@hey-api/client-ofetch'; -const outputDir = path.join( - root, - 'test', - 'generated', - version, - namespace, - clientName, -); - -const createConfig = (userConfig) => ({ - ...userConfig, - input: path.join(getSpecsPath(), version, 'full.yaml'), - logs: { level: 'silent' }, - output: - typeof userConfig.output === 'string' - ? path.join(outputDir, userConfig.output) - : { - ...userConfig.output, - path: path.join(outputDir, userConfig.output.path), - }, -}); - -const scenarios = [ - { - config: createConfig({ output: 'default', plugins: [clientName] }), - description: 'default output', - }, - { - config: createConfig({ - output: 'sdk-client-optional', - plugins: [clientName, { client: true, name: '@hey-api/sdk' }], - }), - description: 'SDK with optional client option', - }, - { - config: createConfig({ - output: 'sdk-client-required', - plugins: [clientName, { client: false, name: '@hey-api/sdk' }], - }), - description: 'SDK with required client option', - }, - { - config: createConfig({ - output: 'base-url-false', - plugins: [{ baseUrl: false, name: clientName }, '@hey-api/typescript'], - }), - description: 'client without base URL', - }, - { - config: createConfig({ - output: 'base-url-number', - plugins: [{ baseUrl: 0, name: clientName }, '@hey-api/typescript'], - }), - description: 'client with numeric base URL', - }, - { - config: createConfig({ - output: 'base-url-string', - plugins: [ - { baseUrl: 'https://foo.com', name: clientName }, - '@hey-api/typescript', - ], - }), - description: 'client with custom string base URL', - }, - { - config: createConfig({ - output: 'base-url-strict', - plugins: [{ baseUrl: true, name: clientName }, '@hey-api/typescript'], - }), - description: 'client with strict base URL', - }, - { - config: createConfig({ - output: { - path: 'tsconfig-nodenext-sdk', - tsConfigPath: path.join( - root, - 'test', - 'tsconfig', - 'tsconfig.nodenext.json', - ), - }, - plugins: [clientName, '@hey-api/sdk'], - }), - description: 'SDK with NodeNext tsconfig', - }, - { - config: createConfig({ - output: { clean: false, path: 'clean-false' }, - plugins: [clientName, '@hey-api/sdk'], - }), - description: 'avoid appending extension multiple times | twice', - }, -]; - -async function main() { - for (const { config, description } of scenarios) { - console.log('Generating:', description); - await createClient(config); - if (description.endsWith('twice')) { - await createClient(config); - } - } - // Generate SSE for ofetch as well (mirrors 3.1.x.test.ts scenario) - const sseOut = path.join(root, 'test', 'generated', version, 'sse-ofetch'); - await createClient({ - input: path.join(getSpecsPath(), version, 'opencode.yaml'), - logs: { level: 'silent' }, - output: sseOut, - parser: { filters: { operations: { include: ['GET /event'] } } }, - plugins: [clientName, '@hey-api/sdk'], - }); -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client.gen.ts index fe57f118d..cab3c7019 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/client.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { ClientOptions } from './types.gen'; -import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -11,6 +11,6 @@ import { type ClientOptions as DefaultClientOptions, type Config, createClient, * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig()); +export const client = createClient(createConfig()); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/index.ts index 0339b6e31..b43a5238d 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; +export type * from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/types.gen.ts index 4c755476d..28ad7b64b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-false/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; + /** * Model with number-only name */ @@ -2059,7 +2063,3 @@ export type PutWithFormUrlEncodedData = { query?: never; url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; }; - -export type ClientOptions = { - baseUrl: 'http://localhost:3000/base' | (string & {}); -}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client.gen.ts index 950198e01..6b55ac80f 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/client.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { ClientOptions } from './types.gen'; -import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -11,8 +11,8 @@ import { type ClientOptions as DefaultClientOptions, type Config, createClient, * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig({ +export const client = createClient(createConfig({ baseUrl: 'http://localhost:3000/base' })); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/index.ts index 0339b6e31..b43a5238d 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; +export type * from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/types.gen.ts index 4c755476d..28ad7b64b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-number/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; + /** * Model with number-only name */ @@ -2059,7 +2063,3 @@ export type PutWithFormUrlEncodedData = { query?: never; url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; }; - -export type ClientOptions = { - baseUrl: 'http://localhost:3000/base' | (string & {}); -}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client.gen.ts index 950198e01..6b55ac80f 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/client.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { ClientOptions } from './types.gen'; -import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -11,8 +11,8 @@ import { type ClientOptions as DefaultClientOptions, type Config, createClient, * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig({ +export const client = createClient(createConfig({ baseUrl: 'http://localhost:3000/base' })); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/index.ts index 0339b6e31..b43a5238d 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; +export type * from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/types.gen.ts index e671e5b13..c1b190d6e 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-strict/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base'; +}; + /** * Model with number-only name */ @@ -2059,7 +2063,3 @@ export type PutWithFormUrlEncodedData = { query?: never; url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; }; - -export type ClientOptions = { - baseUrl: 'http://localhost:3000/base'; -}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client.gen.ts index ccd37e81c..cf9533810 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/client.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { ClientOptions } from './types.gen'; -import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -11,8 +11,8 @@ import { type ClientOptions as DefaultClientOptions, type Config, createClient, * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig({ +export const client = createClient(createConfig({ baseUrl: 'https://foo.com' })); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/index.ts index 0339b6e31..b43a5238d 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; +export type * from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/types.gen.ts index 4c755476d..28ad7b64b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/base-url-string/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; + /** * Model with number-only name */ @@ -2059,7 +2063,3 @@ export type PutWithFormUrlEncodedData = { query?: never; url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; }; - -export type ClientOptions = { - baseUrl: 'http://localhost:3000/base' | (string & {}); -}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client.gen.ts index 950198e01..6b55ac80f 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/client.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { ClientOptions } from './types.gen'; -import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -11,8 +11,8 @@ import { type ClientOptions as DefaultClientOptions, type Config, createClient, * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig({ +export const client = createClient(createConfig({ baseUrl: 'http://localhost:3000/base' })); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/index.ts index cc646f13a..c352c1047 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; +export type * from './types.gen'; export * from './sdk.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/sdk.gen.ts index 17622c66b..c8a906452 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/sdk.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/sdk.gen.ts @@ -1,10 +1,10 @@ // This file is auto-generated by @hey-api/openapi-ts -import { type Options as ClientOptions, type Client, type TDataShape, formDataBodySerializer, urlSearchParamsBodySerializer } from './client'; -import type { ExportData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, ImportData, ImportResponses, FooWowData, FooWowResponses, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationResponses, GetApiVbyApiVersionSimpleOperationErrors, DeleteCallWithoutParametersAndResponseData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, OptionsCallWithoutParametersAndResponseData, PatchCallWithoutParametersAndResponseData, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, DeleteFooData3 as DeleteFooData, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostApiVbyApiVersionRequestBodyData, PostApiVbyApiVersionFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, DuplicateNameData, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, DummyAData, DummyAResponses, DummyBData, DummyBResponses, CallWithResponseData, CallWithResponseResponses, CallWithDuplicateResponsesData, CallWithDuplicateResponsesResponses, CallWithDuplicateResponsesErrors, CallWithResponsesData, CallWithResponsesResponses, CallWithResponsesErrors, CollectionFormatData, TypesData, TypesResponses, UploadFileData, UploadFileResponses, FileResponseData, FileResponseResponses, ComplexTypesData, ComplexTypesResponses, ComplexTypesErrors, MultipartResponseData, MultipartResponseResponses, MultipartRequestData, ComplexParamsData, ComplexParamsResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderResponses, CallWithResultFromHeaderErrors, TestErrorCodeData, TestErrorCodeResponses, TestErrorCodeErrors, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, PutWithFormUrlEncodedData } from './types.gen'; +import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape, urlSearchParamsBodySerializer } from './client'; import { client } from './client.gen'; +import type { ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, CallToTestOrderOfParamsData, CallWithDefaultOptionalParametersData, CallWithDefaultParametersData, CallWithDescriptionsData, CallWithDuplicateResponsesData, CallWithDuplicateResponsesErrors, CallWithDuplicateResponsesResponses, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithParametersData, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, CallWithResponseData, CallWithResponseResponses, CallWithResponsesData, CallWithResponsesErrors, CallWithResponsesResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderErrors, CallWithResultFromHeaderResponses, CallWithWeirdParameterNamesData, CollectionFormatData, ComplexParamsData, ComplexParamsResponses, ComplexTypesData, ComplexTypesErrors, ComplexTypesResponses, DeleteCallWithoutParametersAndResponseData, DeleteFooData3, DeprecatedCallData, DummyAData, DummyAResponses, DummyBData, DummyBResponses, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, DuplicateNameData, ExportData, FileResponseData, FileResponseResponses, FooWowData, FooWowResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationErrors, GetApiVbyApiVersionSimpleOperationResponses, GetCallWithOptionalParamData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, ImportData, ImportResponses, MultipartRequestData, MultipartResponseData, MultipartResponseResponses, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, OptionsCallWithoutParametersAndResponseData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, PatchCallWithoutParametersAndResponseData, PostApiVbyApiVersionFormDataData, PostApiVbyApiVersionRequestBodyData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, PutWithFormUrlEncodedData, TestErrorCodeData, TestErrorCodeErrors, TestErrorCodeResponses, TypesData, TypesResponses, UploadFileData, UploadFileResponses } from './types.gen'; -export type Options = ClientOptions & { +export type Options = Options2 & { /** * You can provide a client instance returned by `createClient()` instead of * individual options. This might be also useful if you want to implement a @@ -113,7 +113,7 @@ export const putCallWithoutParametersAndResponse = (options: Options) => { +export const deleteFoo = (options: Options) => { return (options.client ?? client).delete({ url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', ...options diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/types.gen.ts index 4c755476d..28ad7b64b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/clean-false/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; + /** * Model with number-only name */ @@ -2059,7 +2063,3 @@ export type PutWithFormUrlEncodedData = { query?: never; url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; }; - -export type ClientOptions = { - baseUrl: 'http://localhost:3000/base' | (string & {}); -}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client.gen.ts index 950198e01..6b55ac80f 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/client.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { ClientOptions } from './types.gen'; -import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -11,8 +11,8 @@ import { type ClientOptions as DefaultClientOptions, type Config, createClient, * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig({ +export const client = createClient(createConfig({ baseUrl: 'http://localhost:3000/base' })); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/index.ts index cc646f13a..c352c1047 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; +export type * from './types.gen'; export * from './sdk.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/sdk.gen.ts index 17622c66b..c8a906452 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/sdk.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/sdk.gen.ts @@ -1,10 +1,10 @@ // This file is auto-generated by @hey-api/openapi-ts -import { type Options as ClientOptions, type Client, type TDataShape, formDataBodySerializer, urlSearchParamsBodySerializer } from './client'; -import type { ExportData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, ImportData, ImportResponses, FooWowData, FooWowResponses, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationResponses, GetApiVbyApiVersionSimpleOperationErrors, DeleteCallWithoutParametersAndResponseData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, OptionsCallWithoutParametersAndResponseData, PatchCallWithoutParametersAndResponseData, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, DeleteFooData3 as DeleteFooData, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostApiVbyApiVersionRequestBodyData, PostApiVbyApiVersionFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, DuplicateNameData, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, DummyAData, DummyAResponses, DummyBData, DummyBResponses, CallWithResponseData, CallWithResponseResponses, CallWithDuplicateResponsesData, CallWithDuplicateResponsesResponses, CallWithDuplicateResponsesErrors, CallWithResponsesData, CallWithResponsesResponses, CallWithResponsesErrors, CollectionFormatData, TypesData, TypesResponses, UploadFileData, UploadFileResponses, FileResponseData, FileResponseResponses, ComplexTypesData, ComplexTypesResponses, ComplexTypesErrors, MultipartResponseData, MultipartResponseResponses, MultipartRequestData, ComplexParamsData, ComplexParamsResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderResponses, CallWithResultFromHeaderErrors, TestErrorCodeData, TestErrorCodeResponses, TestErrorCodeErrors, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, PutWithFormUrlEncodedData } from './types.gen'; +import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape, urlSearchParamsBodySerializer } from './client'; import { client } from './client.gen'; +import type { ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, CallToTestOrderOfParamsData, CallWithDefaultOptionalParametersData, CallWithDefaultParametersData, CallWithDescriptionsData, CallWithDuplicateResponsesData, CallWithDuplicateResponsesErrors, CallWithDuplicateResponsesResponses, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithParametersData, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, CallWithResponseData, CallWithResponseResponses, CallWithResponsesData, CallWithResponsesErrors, CallWithResponsesResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderErrors, CallWithResultFromHeaderResponses, CallWithWeirdParameterNamesData, CollectionFormatData, ComplexParamsData, ComplexParamsResponses, ComplexTypesData, ComplexTypesErrors, ComplexTypesResponses, DeleteCallWithoutParametersAndResponseData, DeleteFooData3, DeprecatedCallData, DummyAData, DummyAResponses, DummyBData, DummyBResponses, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, DuplicateNameData, ExportData, FileResponseData, FileResponseResponses, FooWowData, FooWowResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationErrors, GetApiVbyApiVersionSimpleOperationResponses, GetCallWithOptionalParamData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, ImportData, ImportResponses, MultipartRequestData, MultipartResponseData, MultipartResponseResponses, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, OptionsCallWithoutParametersAndResponseData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, PatchCallWithoutParametersAndResponseData, PostApiVbyApiVersionFormDataData, PostApiVbyApiVersionRequestBodyData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, PutWithFormUrlEncodedData, TestErrorCodeData, TestErrorCodeErrors, TestErrorCodeResponses, TypesData, TypesResponses, UploadFileData, UploadFileResponses } from './types.gen'; -export type Options = ClientOptions & { +export type Options = Options2 & { /** * You can provide a client instance returned by `createClient()` instead of * individual options. This might be also useful if you want to implement a @@ -113,7 +113,7 @@ export const putCallWithoutParametersAndResponse = (options: Options) => { +export const deleteFoo = (options: Options) => { return (options.client ?? client).delete({ url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', ...options diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/types.gen.ts index 4c755476d..28ad7b64b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/default/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; + /** * Model with number-only name */ @@ -2059,7 +2063,3 @@ export type PutWithFormUrlEncodedData = { query?: never; url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; }; - -export type ClientOptions = { - baseUrl: 'http://localhost:3000/base' | (string & {}); -}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client.gen.ts index 950198e01..6b55ac80f 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/client.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { ClientOptions } from './types.gen'; -import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -11,8 +11,8 @@ import { type ClientOptions as DefaultClientOptions, type Config, createClient, * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig({ +export const client = createClient(createConfig({ baseUrl: 'http://localhost:3000/base' })); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/index.ts index cc646f13a..c352c1047 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; +export type * from './types.gen'; export * from './sdk.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/sdk.gen.ts index 17622c66b..c8a906452 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/sdk.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/sdk.gen.ts @@ -1,10 +1,10 @@ // This file is auto-generated by @hey-api/openapi-ts -import { type Options as ClientOptions, type Client, type TDataShape, formDataBodySerializer, urlSearchParamsBodySerializer } from './client'; -import type { ExportData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, ImportData, ImportResponses, FooWowData, FooWowResponses, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationResponses, GetApiVbyApiVersionSimpleOperationErrors, DeleteCallWithoutParametersAndResponseData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, OptionsCallWithoutParametersAndResponseData, PatchCallWithoutParametersAndResponseData, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, DeleteFooData3 as DeleteFooData, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostApiVbyApiVersionRequestBodyData, PostApiVbyApiVersionFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, DuplicateNameData, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, DummyAData, DummyAResponses, DummyBData, DummyBResponses, CallWithResponseData, CallWithResponseResponses, CallWithDuplicateResponsesData, CallWithDuplicateResponsesResponses, CallWithDuplicateResponsesErrors, CallWithResponsesData, CallWithResponsesResponses, CallWithResponsesErrors, CollectionFormatData, TypesData, TypesResponses, UploadFileData, UploadFileResponses, FileResponseData, FileResponseResponses, ComplexTypesData, ComplexTypesResponses, ComplexTypesErrors, MultipartResponseData, MultipartResponseResponses, MultipartRequestData, ComplexParamsData, ComplexParamsResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderResponses, CallWithResultFromHeaderErrors, TestErrorCodeData, TestErrorCodeResponses, TestErrorCodeErrors, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, PutWithFormUrlEncodedData } from './types.gen'; +import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape, urlSearchParamsBodySerializer } from './client'; import { client } from './client.gen'; +import type { ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, CallToTestOrderOfParamsData, CallWithDefaultOptionalParametersData, CallWithDefaultParametersData, CallWithDescriptionsData, CallWithDuplicateResponsesData, CallWithDuplicateResponsesErrors, CallWithDuplicateResponsesResponses, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithParametersData, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, CallWithResponseData, CallWithResponseResponses, CallWithResponsesData, CallWithResponsesErrors, CallWithResponsesResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderErrors, CallWithResultFromHeaderResponses, CallWithWeirdParameterNamesData, CollectionFormatData, ComplexParamsData, ComplexParamsResponses, ComplexTypesData, ComplexTypesErrors, ComplexTypesResponses, DeleteCallWithoutParametersAndResponseData, DeleteFooData3, DeprecatedCallData, DummyAData, DummyAResponses, DummyBData, DummyBResponses, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, DuplicateNameData, ExportData, FileResponseData, FileResponseResponses, FooWowData, FooWowResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationErrors, GetApiVbyApiVersionSimpleOperationResponses, GetCallWithOptionalParamData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, ImportData, ImportResponses, MultipartRequestData, MultipartResponseData, MultipartResponseResponses, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, OptionsCallWithoutParametersAndResponseData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, PatchCallWithoutParametersAndResponseData, PostApiVbyApiVersionFormDataData, PostApiVbyApiVersionRequestBodyData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, PutWithFormUrlEncodedData, TestErrorCodeData, TestErrorCodeErrors, TestErrorCodeResponses, TypesData, TypesResponses, UploadFileData, UploadFileResponses } from './types.gen'; -export type Options = ClientOptions & { +export type Options = Options2 & { /** * You can provide a client instance returned by `createClient()` instead of * individual options. This might be also useful if you want to implement a @@ -113,7 +113,7 @@ export const putCallWithoutParametersAndResponse = (options: Options) => { +export const deleteFoo = (options: Options) => { return (options.client ?? client).delete({ url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', ...options diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/types.gen.ts index 4c755476d..28ad7b64b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-optional/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; + /** * Model with number-only name */ @@ -2059,7 +2063,3 @@ export type PutWithFormUrlEncodedData = { query?: never; url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; }; - -export type ClientOptions = { - baseUrl: 'http://localhost:3000/base' | (string & {}); -}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client.gen.ts index 950198e01..6b55ac80f 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/client.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { ClientOptions } from './types.gen'; -import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -11,8 +11,8 @@ import { type ClientOptions as DefaultClientOptions, type Config, createClient, * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig({ +export const client = createClient(createConfig({ baseUrl: 'http://localhost:3000/base' })); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/index.ts index cc646f13a..c352c1047 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; +export type * from './types.gen'; export * from './sdk.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/sdk.gen.ts index d7073900a..5a7dd0b9c 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/sdk.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/sdk.gen.ts @@ -1,9 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts -import { type Options as ClientOptions, type Client, type TDataShape, formDataBodySerializer, urlSearchParamsBodySerializer } from './client'; -import type { ExportData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, ImportData, ImportResponses, FooWowData, FooWowResponses, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationResponses, GetApiVbyApiVersionSimpleOperationErrors, DeleteCallWithoutParametersAndResponseData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, OptionsCallWithoutParametersAndResponseData, PatchCallWithoutParametersAndResponseData, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, DeleteFooData3 as DeleteFooData, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostApiVbyApiVersionRequestBodyData, PostApiVbyApiVersionFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, DuplicateNameData, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, DummyAData, DummyAResponses, DummyBData, DummyBResponses, CallWithResponseData, CallWithResponseResponses, CallWithDuplicateResponsesData, CallWithDuplicateResponsesResponses, CallWithDuplicateResponsesErrors, CallWithResponsesData, CallWithResponsesResponses, CallWithResponsesErrors, CollectionFormatData, TypesData, TypesResponses, UploadFileData, UploadFileResponses, FileResponseData, FileResponseResponses, ComplexTypesData, ComplexTypesResponses, ComplexTypesErrors, MultipartResponseData, MultipartResponseResponses, MultipartRequestData, ComplexParamsData, ComplexParamsResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderResponses, CallWithResultFromHeaderErrors, TestErrorCodeData, TestErrorCodeResponses, TestErrorCodeErrors, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, PutWithFormUrlEncodedData } from './types.gen'; +import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape, urlSearchParamsBodySerializer } from './client'; +import type { ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, CallToTestOrderOfParamsData, CallWithDefaultOptionalParametersData, CallWithDefaultParametersData, CallWithDescriptionsData, CallWithDuplicateResponsesData, CallWithDuplicateResponsesErrors, CallWithDuplicateResponsesResponses, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithParametersData, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, CallWithResponseData, CallWithResponseResponses, CallWithResponsesData, CallWithResponsesErrors, CallWithResponsesResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderErrors, CallWithResultFromHeaderResponses, CallWithWeirdParameterNamesData, CollectionFormatData, ComplexParamsData, ComplexParamsResponses, ComplexTypesData, ComplexTypesErrors, ComplexTypesResponses, DeleteCallWithoutParametersAndResponseData, DeleteFooData3, DeprecatedCallData, DummyAData, DummyAResponses, DummyBData, DummyBResponses, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, DuplicateNameData, ExportData, FileResponseData, FileResponseResponses, FooWowData, FooWowResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationErrors, GetApiVbyApiVersionSimpleOperationResponses, GetCallWithOptionalParamData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, ImportData, ImportResponses, MultipartRequestData, MultipartResponseData, MultipartResponseResponses, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, OptionsCallWithoutParametersAndResponseData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, PatchCallWithoutParametersAndResponseData, PostApiVbyApiVersionFormDataData, PostApiVbyApiVersionRequestBodyData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, PutWithFormUrlEncodedData, TestErrorCodeData, TestErrorCodeErrors, TestErrorCodeResponses, TypesData, TypesResponses, UploadFileData, UploadFileResponses } from './types.gen'; -export type Options = ClientOptions & { +export type Options = Options2 & { /** * You can provide a client instance returned by `createClient()` instead of * individual options. This might be also useful if you want to implement a @@ -112,7 +112,7 @@ export const putCallWithoutParametersAndResponse = (options: Options) => { +export const deleteFoo = (options: Options) => { return options.client.delete({ url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', ...options diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/types.gen.ts index 4c755476d..28ad7b64b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/sdk-client-required/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; + /** * Model with number-only name */ @@ -2059,7 +2063,3 @@ export type PutWithFormUrlEncodedData = { query?: never; url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; }; - -export type ClientOptions = { - baseUrl: 'http://localhost:3000/base' | (string & {}); -}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client.gen.ts index 8b954d012..b47036454 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/client.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { ClientOptions } from './types.gen.js'; -import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; +import { type ClientOptions, type Config, createClient, createConfig } from './client/index.js'; +import type { ClientOptions as ClientOptions2 } from './types.gen.js'; /** * The `createClientConfig()` function will be called on client initialization @@ -11,8 +11,8 @@ import { type ClientOptions as DefaultClientOptions, type Config, createClient, * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig({ +export const client = createClient(createConfig({ baseUrl: 'http://localhost:3000/base' })); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/index.ts index cea7f39ce..5556b3a70 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen.js'; +export type * from './types.gen.js'; export * from './sdk.gen.js'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/sdk.gen.ts index d3cddea8e..d7daef95a 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/sdk.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/sdk.gen.ts @@ -1,10 +1,10 @@ // This file is auto-generated by @hey-api/openapi-ts -import { type Options as ClientOptions, type Client, type TDataShape, formDataBodySerializer, urlSearchParamsBodySerializer } from './client'; -import type { ExportData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, ImportData, ImportResponses, FooWowData, FooWowResponses, ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationResponses, GetApiVbyApiVersionSimpleOperationErrors, DeleteCallWithoutParametersAndResponseData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, OptionsCallWithoutParametersAndResponseData, PatchCallWithoutParametersAndResponseData, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, DeleteFooData3 as DeleteFooData, CallWithDescriptionsData, DeprecatedCallData, CallWithParametersData, CallWithWeirdParameterNamesData, GetCallWithOptionalParamData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostApiVbyApiVersionRequestBodyData, PostApiVbyApiVersionFormDataData, CallWithDefaultParametersData, CallWithDefaultOptionalParametersData, CallToTestOrderOfParamsData, DuplicateNameData, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, DummyAData, DummyAResponses, DummyBData, DummyBResponses, CallWithResponseData, CallWithResponseResponses, CallWithDuplicateResponsesData, CallWithDuplicateResponsesResponses, CallWithDuplicateResponsesErrors, CallWithResponsesData, CallWithResponsesResponses, CallWithResponsesErrors, CollectionFormatData, TypesData, TypesResponses, UploadFileData, UploadFileResponses, FileResponseData, FileResponseResponses, ComplexTypesData, ComplexTypesResponses, ComplexTypesErrors, MultipartResponseData, MultipartResponseResponses, MultipartRequestData, ComplexParamsData, ComplexParamsResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderResponses, CallWithResultFromHeaderErrors, TestErrorCodeData, TestErrorCodeResponses, TestErrorCodeErrors, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, PutWithFormUrlEncodedData } from './types.gen.js'; import { client } from './client.gen.js'; +import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape, urlSearchParamsBodySerializer } from './client/index.js'; +import type { ApiVVersionODataControllerCountData, ApiVVersionODataControllerCountResponses, CallToTestOrderOfParamsData, CallWithDefaultOptionalParametersData, CallWithDefaultParametersData, CallWithDescriptionsData, CallWithDuplicateResponsesData, CallWithDuplicateResponsesErrors, CallWithDuplicateResponsesResponses, CallWithNoContentResponseData, CallWithNoContentResponseResponses, CallWithParametersData, CallWithResponseAndNoContentResponseData, CallWithResponseAndNoContentResponseResponses, CallWithResponseData, CallWithResponseResponses, CallWithResponsesData, CallWithResponsesErrors, CallWithResponsesResponses, CallWithResultFromHeaderData, CallWithResultFromHeaderErrors, CallWithResultFromHeaderResponses, CallWithWeirdParameterNamesData, CollectionFormatData, ComplexParamsData, ComplexParamsResponses, ComplexTypesData, ComplexTypesErrors, ComplexTypesResponses, DeleteCallWithoutParametersAndResponseData, DeleteFooData3, DeprecatedCallData, DummyAData, DummyAResponses, DummyBData, DummyBResponses, DuplicateName2Data, DuplicateName3Data, DuplicateName4Data, DuplicateNameData, ExportData, FileResponseData, FileResponseResponses, FooWowData, FooWowResponses, GetApiVbyApiVersionSimpleOperationData, GetApiVbyApiVersionSimpleOperationErrors, GetApiVbyApiVersionSimpleOperationResponses, GetCallWithOptionalParamData, GetCallWithoutParametersAndResponseData, HeadCallWithoutParametersAndResponseData, ImportData, ImportResponses, MultipartRequestData, MultipartResponseData, MultipartResponseResponses, NonAsciiæøåÆøÅöôêÊ字符串Data, NonAsciiæøåÆøÅöôêÊ字符串Responses, OptionsCallWithoutParametersAndResponseData, PatchApiVbyApiVersionNoTagData, PatchApiVbyApiVersionNoTagResponses, PatchCallWithoutParametersAndResponseData, PostApiVbyApiVersionFormDataData, PostApiVbyApiVersionRequestBodyData, PostCallWithOptionalParamData, PostCallWithOptionalParamResponses, PostCallWithoutParametersAndResponseData, PutCallWithoutParametersAndResponseData, PutWithFormUrlEncodedData, TestErrorCodeData, TestErrorCodeErrors, TestErrorCodeResponses, TypesData, TypesResponses, UploadFileData, UploadFileResponses } from './types.gen.js'; -export type Options = ClientOptions & { +export type Options = Options2 & { /** * You can provide a client instance returned by `createClient()` instead of * individual options. This might be also useful if you want to implement a @@ -113,7 +113,7 @@ export const putCallWithoutParametersAndResponse = (options: Options) => { +export const deleteFoo = (options: Options) => { return (options.client ?? client).delete({ url: '/api/v{api-version}/foo/{foo_param}/bar/{BarParam}', ...options diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/types.gen.ts index 4c755476d..28ad7b64b 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/clients/@hey-api/client-ofetch/tsconfig-nodenext-sdk/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'http://localhost:3000/base' | (string & {}); +}; + /** * Model with number-only name */ @@ -2059,7 +2063,3 @@ export type PutWithFormUrlEncodedData = { query?: never; url: '/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串'; }; - -export type ClientOptions = { - baseUrl: 'http://localhost:3000/base' | (string & {}); -}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client.gen.ts index fe57f118d..cab3c7019 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/client.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { ClientOptions } from './types.gen'; -import { type ClientOptions as DefaultClientOptions, type Config, createClient, createConfig } from './client'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -11,6 +11,6 @@ import { type ClientOptions as DefaultClientOptions, type Config, createClient, * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient(createConfig()); +export const client = createClient(createConfig()); diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/index.ts index cc646f13a..c352c1047 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/index.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; +export type * from './types.gen'; export * from './sdk.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/sdk.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/sdk.gen.ts index 40a25a497..ffa7224eb 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/sdk.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/sdk.gen.ts @@ -1,10 +1,10 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Options as ClientOptions, Client, TDataShape } from './client'; -import type { EventSubscribeData, EventSubscribeResponses } from './types.gen'; +import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; +import type { EventSubscribeData, EventSubscribeResponses } from './types.gen'; -export type Options = ClientOptions & { +export type Options = Options2 & { /** * You can provide a client instance returned by `createClient()` instead of * individual options. This might be also useful if you want to implement a diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/types.gen.ts index ea6b3e42a..d3fe8ca92 100644 --- a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/types.gen.ts +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/sse-ofetch/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; + export type Range = { start: { line: number; @@ -521,7 +525,3 @@ export type EventSubscribeResponses = { }; export type EventSubscribeResponse = EventSubscribeResponses[keyof EventSubscribeResponses]; - -export type ClientOptions = { - baseUrl: `${string}://${string}` | (string & {}); -};