@shipworthy/openapi-fetch 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/helpers.d.ts
2
1
  type HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";
3
2
  /** 2XX statuses */
4
3
  type OkStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207 | "2XX";
@@ -190,8 +189,29 @@ type Readable<T> = T extends $Write<any> ? never : T extends $Read<infer U> ? Re
190
189
  * - object → recursively resolve
191
190
  */
192
191
  type Writable<T> = T extends $Read<any> ? never : T extends $Write<infer U> ? Writable<U> : T extends (infer E)[] ? Writable<E>[] : T extends object ? { [K in keyof T as NonNullable<T[K]> extends $Read<any> ? never : K]: Writable<T[K]>; } & { [K in keyof T as NonNullable<T[K]> extends $Read<any> ? K : never]?: never; } : T;
193
- //#endregion
194
- //#region src/types.d.ts
192
+ interface CaseSchema {
193
+ readonly ref?: string;
194
+ readonly properties?: Readonly<Record<string, {
195
+ readonly name: string;
196
+ readonly schema?: CaseSchema;
197
+ }>>;
198
+ readonly declared?: readonly string[];
199
+ readonly items?: CaseSchema;
200
+ readonly prefixItems?: readonly (CaseSchema | null)[];
201
+ readonly additionalProperties?: CaseSchema;
202
+ readonly patternProperties?: Readonly<Record<string, CaseSchema | null>>;
203
+ readonly variants?: readonly CaseSchema[];
204
+ }
205
+ type CaseMediaMap = Readonly<Record<string, CaseSchema | null>>;
206
+ interface CaseOperation {
207
+ readonly parameters?: Readonly<Partial<Record<"path" | "query" | "header" | "cookie", CaseSchema>>>;
208
+ readonly requestBodies?: CaseMediaMap;
209
+ readonly responses?: Readonly<Record<string, CaseMediaMap | null>>;
210
+ }
211
+ interface CaseMapping {
212
+ readonly schemas?: Readonly<Record<string, CaseSchema>>;
213
+ readonly operations?: Readonly<Record<string, CaseOperation>>;
214
+ }
195
215
  /** Options for each client instance */
196
216
  interface ClientOptions extends Omit<RequestInit, "headers"> {
197
217
  /** set the common root URL for all API requests */
@@ -206,6 +226,8 @@ interface ClientOptions extends Omit<RequestInit, "headers"> {
206
226
  bodySerializer?: BodySerializer<unknown>;
207
227
  /** global pathSerializer */
208
228
  pathSerializer?: PathSerializer;
229
+ /** schema-based field mapping applied to every request and parsed result */
230
+ caseMapping?: CaseMapping;
209
231
  headers?: HeadersOptions;
210
232
  /** RequestInit extension object to pass as 2nd argument to fetch when supported (defaults to undefined) */
211
233
  requestInitExt?: Record<string, unknown>;
@@ -301,10 +323,28 @@ type MergedOptions<T = unknown> = {
301
323
  };
302
324
  interface MiddlewareRequestParams {
303
325
  query?: Record<string, unknown>;
304
- header?: Record<string, unknown>;
326
+ header?: HeadersOptions;
305
327
  path?: Record<string, unknown>;
306
328
  cookie?: Record<string, unknown>;
307
329
  }
330
+ interface MiddlewareRequestInput {
331
+ readonly params: MiddlewareRequestParams;
332
+ readonly body: unknown;
333
+ }
334
+ interface MiddlewareRequestInitParams {
335
+ readonly schemaPath: string;
336
+ readonly method: string;
337
+ readonly input: MiddlewareRequestInput;
338
+ /** Client, request, and parameter headers merged before serialization. */
339
+ readonly headers: Headers;
340
+ readonly id: string;
341
+ readonly options: MergedOptions;
342
+ }
343
+ interface MiddlewareResult {
344
+ readonly data?: unknown;
345
+ readonly error?: unknown;
346
+ readonly response: Response;
347
+ }
308
348
  interface MiddlewareCallbackParams {
309
349
  /** Current Request object */
310
350
  request: Request;
@@ -317,6 +357,12 @@ interface MiddlewareCallbackParams {
317
357
  /** createClient options (read-only) */
318
358
  readonly options: MergedOptions;
319
359
  }
360
+ interface MiddlewareResultParams extends MiddlewareCallbackParams {
361
+ /** The method requested through the client, before HTTP middleware can replace the Request. */
362
+ readonly method: string;
363
+ readonly result: MiddlewareResult;
364
+ }
365
+ type MiddlewareOnRequestInit = (options: MiddlewareRequestInitParams) => void | MiddlewareRequestInput | Promise<MiddlewareRequestInput | void>;
320
366
  type MiddlewareOnRequest = (options: MiddlewareCallbackParams) => void | Request | Response | undefined | Promise<Request | Response | undefined | void>;
321
367
  type MiddlewareOnResponse = (options: MiddlewareCallbackParams & {
322
368
  response: Response;
@@ -324,19 +370,25 @@ type MiddlewareOnResponse = (options: MiddlewareCallbackParams & {
324
370
  type MiddlewareOnError = (options: MiddlewareCallbackParams & {
325
371
  error: unknown;
326
372
  }) => void | Response | Error | Promise<void | Response | Error>;
327
- type Middleware = {
328
- onRequest: MiddlewareOnRequest;
373
+ type MiddlewareOnResult = (options: MiddlewareResultParams) => void | Partial<Pick<MiddlewareResult, "data" | "error">> | Promise<Partial<Pick<MiddlewareResult, "data" | "error">> | void>;
374
+ interface MiddlewareHooks {
375
+ onRequestInit?: MiddlewareOnRequestInit;
376
+ onRequest?: MiddlewareOnRequest;
329
377
  onResponse?: MiddlewareOnResponse;
330
378
  onError?: MiddlewareOnError;
379
+ onResult?: MiddlewareOnResult;
380
+ }
381
+ type Middleware = MiddlewareHooks & ({
382
+ onRequestInit: MiddlewareOnRequestInit;
383
+ } | {
384
+ onRequest: MiddlewareOnRequest;
331
385
  } | {
332
- onRequest?: MiddlewareOnRequest;
333
386
  onResponse: MiddlewareOnResponse;
334
- onError?: MiddlewareOnError;
335
387
  } | {
336
- onRequest?: MiddlewareOnRequest;
337
- onResponse?: MiddlewareOnResponse;
338
388
  onError: MiddlewareOnError;
339
- };
389
+ } | {
390
+ onResult: MiddlewareOnResult;
391
+ });
340
392
  /** This type helper makes the 2nd function param required if params/requestBody are required; otherwise, optional */
341
393
  type MaybeOptionalInit<Params, Location extends keyof Params> = RequiredKeysOf<FetchOptions<FilterKeys<Params, Location>>> extends never ? FetchOptions<FilterKeys<Params, Location>> | undefined : FetchOptions<FilterKeys<Params, Location>>;
342
394
  type InitParam<Init> = RequiredKeysOf<Init> extends never ? [(Init & {
@@ -375,14 +427,10 @@ type MethodResponse<CreatedClient extends Client<any, any>, Method extends HttpM
375
427
  [key: string]: any;
376
428
  }, infer Media extends MediaType> ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]> : never;
377
429
  type PathBasedClient<Paths extends Record<string | number, any>, Media extends MediaType = MediaType> = { [Path in keyof Paths]: ClientForPath<Paths[Path], Media>; };
378
- //#endregion
379
- //#region src/client.d.ts
380
430
  /**
381
431
  * Create an openapi-fetch client.
382
432
  */
383
433
  export default function createClient<Paths extends {}, Media extends MediaType = MediaType>(clientOptions?: ClientOptions): Client<Paths, Media>;
384
- //#endregion
385
- //#region src/path-client.d.ts
386
434
  /**
387
435
  * Wrap openapi-fetch client to support a path based API.
388
436
  */
@@ -392,12 +440,8 @@ export declare function wrapAsPathBasedClient<Paths extends {}, Media extends Me
392
440
  * Strictly equivalent to `wrapAsPathBasedClient(createClient(...))`.
393
441
  */
394
442
  export declare function createPathBasedClient<Paths extends {}, Media extends MediaType = MediaType>(clientOptions?: ClientOptions): PathBasedClient<Paths, Media>;
395
- //#endregion
396
- //#region src/headers.d.ts
397
443
  /** Merge headers a and b, with b taking priority */
398
444
  export declare function mergeHeaders(...allHeaders: (HeadersOptions | undefined)[]): Headers;
399
- //#endregion
400
- //#region src/serializers/param.d.ts
401
445
  type SerializePrimitiveOptions = {
402
446
  allowReserved?: boolean;
403
447
  };
@@ -417,23 +461,15 @@ type SerializeArrayOptions = {
417
461
  };
418
462
  /** Serialize array param (shallow only) */
419
463
  export declare function serializeArrayParam(name: string, value: unknown[], options: SerializeArrayOptions): string;
420
- //#endregion
421
- //#region src/serializers/query.d.ts
422
464
  /** Serialize query params to string */
423
465
  export declare function createQuerySerializer<T = unknown>(options?: QuerySerializerOptions): (queryParams: T) => string;
424
- //#endregion
425
- //#region src/serializers/path.d.ts
426
466
  /**
427
467
  * Handle different OpenAPI 3.x serialization styles
428
468
  * @see https://swagger.io/docs/specification/serialization/#path
429
469
  */
430
470
  export declare function defaultPathSerializer(pathname: string, pathParams: Record<string, unknown>): string;
431
- //#endregion
432
- //#region src/serializers/body.d.ts
433
471
  /** Serialize body object to string */
434
472
  export declare function defaultBodySerializer<T>(body: T, headers?: Headers | Record<string, unknown>): string | FormData;
435
- //#endregion
436
- //#region src/serializers/url.d.ts
437
473
  /** Construct URL string from baseUrl and handle path and query params */
438
474
  export declare function createFinalURL<O>(pathname: string, options: {
439
475
  baseUrl: string;
@@ -446,5 +482,4 @@ export declare function createFinalURL<O>(pathname: string, options: {
446
482
  }): string;
447
483
  /** Remove trailing slash from url */
448
484
  export declare function removeTrailingSlash(url: string): string;
449
- //#endregion
450
- export { type $Read, type $Write, type BodySerializer, type Client, type ClientForPath, type ClientMethod, type ClientOptions, type ClientPathsWithMethod, type ClientRequestMethod, type DefaultParamsOption, type ErrorResponse, type ErrorResponseJSON, type ErrorStatus, type FetchOptions, type FetchResponse, type FilterKeys, type FirstErrorStatus, type GetResponseContent, type HeadersOptions, type HttpMethod, type IsOperationRequestBodyOptional, type JSONLike, type MaybeOptionalInit, type MediaType, type MergedOptions, type MethodResponse, type Middleware, type MiddlewareCallbackParams, type MiddlewareRequestParams, type OKStatusUnion, type OkStatus, type OperationObject, type OperationRequestBody, type OperationRequestBodyContent, type OperationRequestBodyMediaContent, type ParamsOption, type ParseAs, type ParseAsResponse, type PathBasedClient, type PathItemObject, type PathSerializer, type PathsWithMethod, type QuerySerializer, type QuerySerializerOptions, type Readable, type RequestBodyJSON, type RequestBodyOption, type RequestOptions, type RequiredKeysOf, type ResponseContent, type ResponseObjectMap, type SuccessResponse, type SuccessResponseJSON, type Writable, createClient };
485
+ export { type $Read, type $Write, type BodySerializer, type Client, type ClientForPath, type ClientMethod, type ClientOptions, type ClientPathsWithMethod, type ClientRequestMethod, type DefaultParamsOption, type ErrorResponse, type ErrorResponseJSON, type ErrorStatus, type FetchOptions, type FetchResponse, type FilterKeys, type FirstErrorStatus, type GetResponseContent, type HeadersOptions, type HttpMethod, type IsOperationRequestBodyOptional, type JSONLike, type MaybeOptionalInit, type MediaType, type MergedOptions, type MethodResponse, type Middleware, type MiddlewareCallbackParams, type MiddlewareRequestInitParams, type MiddlewareRequestInput, type MiddlewareRequestParams, type MiddlewareResult, type MiddlewareResultParams, type OKStatusUnion, type OkStatus, type OperationObject, type OperationRequestBody, type OperationRequestBodyContent, type OperationRequestBodyMediaContent, type ParamsOption, type ParseAs, type ParseAsResponse, type PathBasedClient, type PathItemObject, type PathSerializer, type PathsWithMethod, type QuerySerializer, type QuerySerializerOptions, type Readable, type RequestBodyJSON, type RequestBodyOption, type RequestOptions, type RequiredKeysOf, type ResponseContent, type ResponseObjectMap, type SuccessResponse, type SuccessResponseJSON, type Writable, createClient };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/headers.ts
1
+ // src/headers.ts
2
2
  /** Iterate any `HeadersOptions` form (plain object, `Headers`, or `[name, value]` tuples) as entries */
3
3
  function headerEntries(h) {
4
4
  if (h instanceof Headers) return h.entries();
@@ -16,8 +16,166 @@ function mergeHeaders(...allHeaders) {
16
16
  }
17
17
  return finalHeaders;
18
18
  }
19
- //#endregion
20
- //#region src/serializers/body.ts
19
+ // src/middleware/transform-casing.ts
20
+ function expandSchemas(schemas, mapping, visited = /* @__PURE__ */ new Set()) {
21
+ return schemas.flatMap((schema) => {
22
+ const nested = schema.variants ?? [];
23
+ if (!schema.ref || visited.has(schema.ref)) return [schema, ...expandSchemas(nested, mapping, visited)];
24
+ const referenced = mapping.schemas?.[schema.ref];
25
+ const next = new Set(visited).add(schema.ref);
26
+ return [schema, ...expandSchemas(referenced ? [referenced, ...nested] : nested, mapping, next)];
27
+ });
28
+ }
29
+ function createConverter(mapping) {
30
+ const schemaIds = /* @__PURE__ */ new WeakMap();
31
+ const plans = /* @__PURE__ */ new Map();
32
+ let nextId = 0;
33
+ function planFor(schemas, direction) {
34
+ if (schemas.length === 0) return void 0;
35
+ const key = `${direction}:${schemas.map((schema) => {
36
+ let id = schemaIds.get(schema);
37
+ if (id === void 0) {
38
+ id = ++nextId;
39
+ schemaIds.set(schema, id);
40
+ }
41
+ return id;
42
+ }).join(",")}`;
43
+ const existing = plans.get(key);
44
+ if (existing) return existing;
45
+ const expanded = expandSchemas(schemas, mapping);
46
+ const fields = /* @__PURE__ */ new Map();
47
+ for (const schema of expanded) for (const [wireName, field] of Object.entries(schema.properties ?? {})) {
48
+ const sourceName = direction === "toApi" ? field.name : wireName;
49
+ const targetName = direction === "toApi" ? wireName : field.name;
50
+ const current = fields.get(sourceName);
51
+ if (current) {
52
+ if (current.name !== targetName) throw new Error(`Conflicting case mapping for ${sourceName}`);
53
+ if (field.schema) current.schemas.push(field.schema);
54
+ } else fields.set(sourceName, {
55
+ name: targetName,
56
+ schemas: field.schema ? [field.schema] : []
57
+ });
58
+ }
59
+ const plan = {
60
+ expanded,
61
+ fields,
62
+ declared: new Set(expanded.flatMap((schema) => schema.declared ?? [])),
63
+ patterns: expanded.flatMap((schema) => Object.entries(schema.patternProperties ?? {}).map(([pattern, nested]) => ({
64
+ regex: new RegExp(pattern),
65
+ schema: nested
66
+ }))),
67
+ additional: expanded.flatMap((schema) => schema.additionalProperties ? [schema.additionalProperties] : []),
68
+ prefixLength: expanded.reduce((max, schema) => Math.max(max, schema.prefixItems?.length ?? 0), 0)
69
+ };
70
+ plans.set(key, plan);
71
+ return plan;
72
+ }
73
+ function itemPlan(plan, index, direction) {
74
+ if (index >= plan.prefixLength) {
75
+ if (plan.items === void 0) plan.items = planFor(plan.expanded.flatMap((schema) => schema.items ? [schema.items] : []), direction) ?? null;
76
+ return plan.items ?? void 0;
77
+ }
78
+ const prefixItems = plan.prefixItems ??= [];
79
+ if (prefixItems[index] === void 0) prefixItems[index] = planFor(plan.expanded.flatMap((schema) => {
80
+ const prefix = schema.prefixItems;
81
+ const item = prefix && index < prefix.length ? prefix[index] : schema.items;
82
+ return item ? [item] : [];
83
+ }), direction) ?? null;
84
+ return prefixItems[index] ?? void 0;
85
+ }
86
+ function convertWithPlan(value, plan, direction) {
87
+ if (!plan || value === null || value === void 0) return value;
88
+ if (Array.isArray(value)) return value.map((item, index) => convertWithPlan(item, itemPlan(plan, index, direction), direction));
89
+ if (typeof value !== "object") return value;
90
+ const prototype = Object.getPrototypeOf(value);
91
+ if (prototype !== Object.prototype && prototype !== null) return value;
92
+ const convertedNames = /* @__PURE__ */ new Set();
93
+ const entries = Object.entries(value).map(([name, item]) => {
94
+ const field = plan.fields.get(name);
95
+ const matched = [];
96
+ let hasPattern = false;
97
+ for (const pattern of plan.patterns) if (pattern.regex.test(name)) {
98
+ hasPattern = true;
99
+ if (pattern.schema) matched.push(pattern.schema);
100
+ }
101
+ let nested;
102
+ if (matched.length > 0) nested = planFor([...field?.schemas ?? [], ...matched], direction);
103
+ else if (field) {
104
+ if (field.nested === void 0) field.nested = planFor(field.schemas, direction) ?? null;
105
+ nested = field.nested ?? void 0;
106
+ } else if (!plan.declared.has(name) && !hasPattern) {
107
+ if (plan.additionalPlan === void 0) plan.additionalPlan = planFor(plan.additional, direction) ?? null;
108
+ nested = plan.additionalPlan ?? void 0;
109
+ }
110
+ const targetName = field?.name ?? name;
111
+ if (convertedNames.has(targetName)) throw new Error(`Conflicting case mapping for ${targetName}`);
112
+ convertedNames.add(targetName);
113
+ return [targetName, convertWithPlan(item, nested, direction)];
114
+ });
115
+ return Object.fromEntries(entries);
116
+ }
117
+ return (value, schemas, direction) => {
118
+ if (schemas.length === 0 || value === null || value === void 0) return value;
119
+ return convertWithPlan(value, planFor(schemas, direction), direction);
120
+ };
121
+ }
122
+ function isJsonMediaType(mediaType) {
123
+ return mediaType === "application/json" || mediaType.endsWith("+json");
124
+ }
125
+ function mediaSchema(media, contentType) {
126
+ if (!media) return void 0;
127
+ const type = contentType?.split(";", 1)[0]?.trim().toLowerCase();
128
+ if (type && !isJsonMediaType(type)) return void 0;
129
+ const entries = Object.entries(media).filter(([name]) => isJsonMediaType(name.toLowerCase()));
130
+ const exact = entries.find(([name]) => name.toLowerCase() === type);
131
+ if (exact) return exact[1] ?? void 0;
132
+ const json = entries.find(([name]) => name.toLowerCase() === "application/json");
133
+ if (json) return json[1] ?? void 0;
134
+ return entries.length === 1 ? entries[0]?.[1] ?? void 0 : void 0;
135
+ }
136
+ function responseMedia(operation, response) {
137
+ const responses = operation.responses;
138
+ if (!responses) return void 0;
139
+ const status = String(response.status);
140
+ const range = `${status[0]}XX`;
141
+ return mediaSchema(Object.hasOwn(responses, status) ? responses[status] : Object.hasOwn(responses, range) ? responses[range] : responses.default, response.headers.get("content-type") ?? void 0);
142
+ }
143
+ function convertRequest(input, operation, convertValue, headers) {
144
+ const parameters = operation.parameters;
145
+ const params = input.params;
146
+ const bodySchema = mediaSchema(operation.requestBodies, headers.get("content-type") ?? void 0);
147
+ return {
148
+ params: parameters ? {
149
+ ...params,
150
+ path: convertValue(params.path, parameters.path ? [parameters.path] : [], "toApi"),
151
+ query: convertValue(params.query, parameters.query ? [parameters.query] : [], "toApi"),
152
+ header: convertValue(params.header, parameters.header ? [parameters.header] : [], "toApi"),
153
+ cookie: convertValue(params.cookie, parameters.cookie ? [parameters.cookie] : [], "toApi")
154
+ } : params,
155
+ body: bodySchema && input.body !== void 0 ? convertValue(input.body, [bodySchema], "toApi") : input.body
156
+ };
157
+ }
158
+ /** Convert generated camelCase values at the openapi-fetch request and result stages. */
159
+ function createCaseMappingMiddleware(mapping) {
160
+ const convertValue = createConverter(mapping);
161
+ return {
162
+ onRequestInit({ method, schemaPath, input, headers }) {
163
+ const operation = mapping.operations?.[`${method} ${schemaPath}`];
164
+ return operation ? convertRequest(input, operation, convertValue, headers) : void 0;
165
+ },
166
+ onResult({ method, schemaPath, result, options }) {
167
+ const operation = mapping.operations?.[`${method} ${schemaPath}`];
168
+ if (!operation) return;
169
+ const schema = responseMedia(operation, result.response);
170
+ if (!schema) return;
171
+ return {
172
+ ...options.parseAs === "json" && result.data !== void 0 ? { data: convertValue(result.data, [schema], "fromApi") } : {},
173
+ ...result.error !== void 0 ? { error: convertValue(result.error, [schema], "fromApi") } : {}
174
+ };
175
+ }
176
+ };
177
+ }
178
+ // src/serializers/body.ts
21
179
  /** Serialize body object to string */
22
180
  function defaultBodySerializer(body, headers) {
23
181
  if (body instanceof FormData) return body;
@@ -26,8 +184,7 @@ function defaultBodySerializer(body, headers) {
26
184
  }
27
185
  return JSON.stringify(body);
28
186
  }
29
- //#endregion
30
- //#region src/serializers/param.ts
187
+ // src/serializers/param.ts
31
188
  /** Serialize primitive params to string */
32
189
  function serializePrimitiveParam(name, value, options) {
33
190
  if (value === void 0 || value === null) return "";
@@ -87,8 +244,7 @@ function serializeArrayParam(name, value, options) {
87
244
  else values.push(serializePrimitiveParam(name, v, options));
88
245
  return options.style === "label" || options.style === "matrix" ? `${joiner}${values.join(joiner)}` : values.join(joiner);
89
246
  }
90
- //#endregion
91
- //#region src/serializers/cookie.ts
247
+ // src/serializers/cookie.ts
92
248
  /**
93
249
  * Serialize cookie params to a `Cookie` header value using the OpenAPI default
94
250
  * (style: form, explode: true). Arrays repeat the name, objects emit one pair per key,
@@ -110,8 +266,7 @@ function serializeCookieParams(cookieParams) {
110
266
  }
111
267
  return pairs.join("; ");
112
268
  }
113
- //#endregion
114
- //#region src/serializers/path.ts
269
+ // src/serializers/path.ts
115
270
  const PATH_PARAM_RE = /\{[^{}]+\}/g;
116
271
  /**
117
272
  * Handle different OpenAPI 3.x serialization styles
@@ -158,8 +313,7 @@ function defaultPathSerializer(pathname, pathParams) {
158
313
  }
159
314
  return nextURL;
160
315
  }
161
- //#endregion
162
- //#region src/serializers/query.ts
316
+ // src/serializers/query.ts
163
317
  /** Serialize query params to string */
164
318
  function createQuerySerializer(options) {
165
319
  return function querySerializer(queryParams) {
@@ -191,8 +345,7 @@ function createQuerySerializer(options) {
191
345
  return search.join("&");
192
346
  };
193
347
  }
194
- //#endregion
195
- //#region src/serializers/url.ts
348
+ // src/serializers/url.ts
196
349
  /** Construct URL string from baseUrl and handle path and query params */
197
350
  function createFinalURL(pathname, options) {
198
351
  let finalURL = `${options.baseUrl}${pathname}`;
@@ -207,8 +360,7 @@ function removeTrailingSlash(url) {
207
360
  if (url.endsWith("/")) return url.substring(0, url.length - 1);
208
361
  return url;
209
362
  }
210
- //#endregion
211
- //#region src/client.ts
363
+ // src/client.ts
212
364
  function supportsRequestInitExt() {
213
365
  const nodeProcess = globalThis.process;
214
366
  return typeof nodeProcess === "object" && Number.parseInt(nodeProcess?.versions?.node?.substring(0, 2) ?? "") >= 18 && nodeProcess?.versions?.undici;
@@ -221,15 +373,16 @@ function randomID() {
221
373
  * Create an openapi-fetch client.
222
374
  */
223
375
  function createClient(clientOptions) {
224
- let { baseUrl = "", Request: CustomRequest = globalThis.Request, fetch: baseFetch = globalThis.fetch, querySerializer: globalQuerySerializer, bodySerializer: globalBodySerializer, pathSerializer: globalPathSerializer, headers: baseHeaders, requestInitExt, ...baseOptions } = { ...clientOptions };
376
+ let { baseUrl = "", Request: CustomRequest = globalThis.Request, fetch: baseFetch = globalThis.fetch, querySerializer: globalQuerySerializer, bodySerializer: globalBodySerializer, pathSerializer: globalPathSerializer, caseMapping, headers: baseHeaders, requestInitExt, ...baseOptions } = { ...clientOptions };
225
377
  requestInitExt = supportsRequestInitExt() ? requestInitExt : void 0;
226
378
  baseUrl = removeTrailingSlash(baseUrl);
227
379
  const globalMiddlewares = [];
380
+ if (caseMapping !== void 0) globalMiddlewares.push(createCaseMappingMiddleware(caseMapping));
228
381
  /**
229
382
  * Per-request fetch (keeps settings created in createClient()
230
383
  */
231
384
  async function coreFetch(schemaPath, fetchOptions) {
232
- const { baseUrl: localBaseUrl, fetch = baseFetch, Request = CustomRequest, headers, params = {}, parseAs = "json", querySerializer: requestQuerySerializer, bodySerializer = globalBodySerializer ?? defaultBodySerializer, pathSerializer: requestPathSerializer, body, middleware: requestMiddlewares = [], ...init } = fetchOptions || {};
385
+ const { baseUrl: localBaseUrl, fetch = baseFetch, Request = CustomRequest, headers, params: initialParams = {}, parseAs = "json", querySerializer: requestQuerySerializer, bodySerializer = globalBodySerializer ?? defaultBodySerializer, pathSerializer: requestPathSerializer, body: initialBody, middleware: requestMiddlewares = [], ...init } = fetchOptions || {};
233
386
  let finalBaseUrl = baseUrl;
234
387
  if (localBaseUrl) finalBaseUrl = removeTrailingSlash(localBaseUrl) ?? baseUrl;
235
388
  let querySerializer = typeof globalQuerySerializer === "function" ? globalQuerySerializer : createQuerySerializer(globalQuerySerializer);
@@ -238,6 +391,41 @@ function createClient(clientOptions) {
238
391
  ...requestQuerySerializer
239
392
  });
240
393
  const pathSerializer = requestPathSerializer || globalPathSerializer || defaultPathSerializer;
394
+ const finalMiddlewares = [...globalMiddlewares, ...requestMiddlewares];
395
+ const method = String(init.method ?? "GET").toUpperCase();
396
+ let id;
397
+ let options;
398
+ let params = initialParams;
399
+ let body = initialBody;
400
+ if (finalMiddlewares.length) {
401
+ id = randomID();
402
+ options = Object.freeze({
403
+ baseUrl: finalBaseUrl,
404
+ fetch,
405
+ parseAs,
406
+ querySerializer,
407
+ bodySerializer,
408
+ pathSerializer
409
+ });
410
+ for (const m of finalMiddlewares) if (m && typeof m === "object" && typeof m.onRequestInit === "function") {
411
+ const result = await m.onRequestInit({
412
+ schemaPath,
413
+ method,
414
+ input: {
415
+ params,
416
+ body
417
+ },
418
+ headers: mergeHeaders(baseHeaders, headers, params.header),
419
+ options,
420
+ id
421
+ });
422
+ if (result !== void 0) {
423
+ if (!result || typeof result !== "object" || !("params" in result) || !("body" in result)) throw new Error("onRequestInit: must return request input when modifying it");
424
+ params = result.params;
425
+ body = result.body;
426
+ }
427
+ }
428
+ }
241
429
  const serializedBody = body === void 0 ? void 0 : bodySerializer(body, mergeHeaders(baseHeaders, headers, params.header));
242
430
  const finalHeaders = mergeHeaders(serializedBody === void 0 || serializedBody instanceof FormData ? {} : { "Content-Type": "application/json" }, baseHeaders, headers, params.header);
243
431
  const serializedCookies = serializeCookieParams(params.cookie);
@@ -245,7 +433,6 @@ function createClient(clientOptions) {
245
433
  const existingCookie = finalHeaders.get("Cookie");
246
434
  finalHeaders.set("Cookie", existingCookie ? `${existingCookie}; ${serializedCookies}` : serializedCookies);
247
435
  }
248
- const finalMiddlewares = [...globalMiddlewares, ...requestMiddlewares];
249
436
  const requestInit = {
250
437
  redirect: "follow",
251
438
  ...baseOptions,
@@ -253,8 +440,6 @@ function createClient(clientOptions) {
253
440
  body: serializedBody,
254
441
  headers: finalHeaders
255
442
  };
256
- let id;
257
- let options;
258
443
  let request = new Request(createFinalURL(schemaPath, {
259
444
  baseUrl: finalBaseUrl,
260
445
  params,
@@ -265,15 +450,6 @@ function createClient(clientOptions) {
265
450
  /** Add custom parameters to Request object */
266
451
  for (const key in init) if (!(key in request)) request[key] = init[key];
267
452
  if (finalMiddlewares.length) {
268
- id = randomID();
269
- options = Object.freeze({
270
- baseUrl: finalBaseUrl,
271
- fetch,
272
- parseAs,
273
- querySerializer,
274
- bodySerializer,
275
- pathSerializer
276
- });
277
453
  for (const m of finalMiddlewares) if (m && typeof m === "object" && typeof m.onRequest === "function") {
278
454
  const result = await m.onRequest({
279
455
  request,
@@ -342,14 +518,15 @@ function createClient(clientOptions) {
342
518
  }
343
519
  const finalResponse = response;
344
520
  const contentLength = finalResponse.headers.get("Content-Length");
345
- if (finalResponse.status === 204 || request.method === "HEAD" || contentLength === "0" && !finalResponse.headers.get("Transfer-Encoding")?.includes("chunked")) return finalResponse.ok ? {
521
+ let parsedResult;
522
+ if (finalResponse.status === 204 || request.method === "HEAD" || contentLength === "0" && !finalResponse.headers.get("Transfer-Encoding")?.includes("chunked")) parsedResult = finalResponse.ok ? {
346
523
  data: void 0,
347
524
  response: finalResponse
348
525
  } : {
349
526
  error: void 0,
350
527
  response: finalResponse
351
528
  };
352
- if (finalResponse.ok) {
529
+ else if (finalResponse.ok) {
353
530
  const getResponseData = async () => {
354
531
  if (parseAs === "stream") return finalResponse.body;
355
532
  if (parseAs === "json" && !contentLength) {
@@ -358,24 +535,50 @@ function createClient(clientOptions) {
358
535
  }
359
536
  return await finalResponse[parseAs]();
360
537
  };
361
- return {
538
+ parsedResult = {
362
539
  data: await getResponseData(),
363
540
  response: finalResponse
364
541
  };
542
+ } else {
543
+ const raw = await finalResponse.text();
544
+ if (!raw) parsedResult = {
545
+ error: void 0,
546
+ response: finalResponse
547
+ };
548
+ else {
549
+ let error = raw;
550
+ try {
551
+ error = JSON.parse(raw);
552
+ } catch {}
553
+ parsedResult = {
554
+ error,
555
+ response: finalResponse
556
+ };
557
+ }
365
558
  }
366
- const raw = await finalResponse.text();
367
- if (!raw) return {
368
- error: void 0,
369
- response: finalResponse
370
- };
371
- let error = raw;
372
- try {
373
- error = JSON.parse(raw);
374
- } catch {}
375
- return {
376
- error,
377
- response: finalResponse
378
- };
559
+ for (let index = finalMiddlewares.length - 1; index >= 0; index--) {
560
+ const m = finalMiddlewares[index];
561
+ if (m && typeof m === "object" && typeof m.onResult === "function") {
562
+ const update = await m.onResult({
563
+ request,
564
+ schemaPath,
565
+ method,
566
+ params,
567
+ options,
568
+ id,
569
+ result: parsedResult
570
+ });
571
+ if (update !== void 0) {
572
+ if (!update || typeof update !== "object" || Object.keys(update).some((key) => key !== "data" && key !== "error")) throw new Error("onResult: must return data and/or error when modifying the result");
573
+ parsedResult = {
574
+ ...parsedResult,
575
+ ...update,
576
+ response: finalResponse
577
+ };
578
+ }
579
+ }
580
+ }
581
+ return parsedResult;
379
582
  }
380
583
  return {
381
584
  request(method, url, init) {
@@ -444,7 +647,7 @@ function createClient(clientOptions) {
444
647
  use(...middleware) {
445
648
  for (const m of middleware) {
446
649
  if (!m) continue;
447
- if (typeof m !== "object" || !("onRequest" in m || "onResponse" in m || "onError" in m)) throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");
650
+ if (typeof m !== "object" || !("onRequestInit" in m || "onRequest" in m || "onResponse" in m || "onError" in m || "onResult" in m)) throw new Error("Middleware must be an object with a supported hook");
448
651
  globalMiddlewares.push(m);
449
652
  }
450
653
  },
@@ -457,8 +660,7 @@ function createClient(clientOptions) {
457
660
  }
458
661
  };
459
662
  }
460
- //#endregion
461
- //#region src/path-client.ts
663
+ // src/path-client.ts
462
664
  var PathCallForwarder = class {
463
665
  client;
464
666
  url;
@@ -518,5 +720,4 @@ function wrapAsPathBasedClient(coreClient) {
518
720
  function createPathBasedClient(clientOptions) {
519
721
  return wrapAsPathBasedClient(createClient(clientOptions));
520
722
  }
521
- //#endregion
522
723
  export { createClient, createClient as default, createFinalURL, createPathBasedClient, createQuerySerializer, defaultBodySerializer, defaultPathSerializer, mergeHeaders, removeTrailingSlash, serializeArrayParam, serializeObjectParam, serializePrimitiveParam, wrapAsPathBasedClient };