@shipworthy/openapi-fetch 0.0.2 → 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.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 };
@@ -0,0 +1,161 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ // src/middleware/transform-casing.ts
3
+ function expandSchemas(schemas, mapping, visited = /* @__PURE__ */ new Set()) {
4
+ return schemas.flatMap((schema) => {
5
+ const nested = schema.variants ?? [];
6
+ if (!schema.ref || visited.has(schema.ref)) return [schema, ...expandSchemas(nested, mapping, visited)];
7
+ const referenced = mapping.schemas?.[schema.ref];
8
+ const next = new Set(visited).add(schema.ref);
9
+ return [schema, ...expandSchemas(referenced ? [referenced, ...nested] : nested, mapping, next)];
10
+ });
11
+ }
12
+ function createConverter(mapping) {
13
+ const schemaIds = /* @__PURE__ */ new WeakMap();
14
+ const plans = /* @__PURE__ */ new Map();
15
+ let nextId = 0;
16
+ function planFor(schemas, direction) {
17
+ if (schemas.length === 0) return void 0;
18
+ const key = `${direction}:${schemas.map((schema) => {
19
+ let id = schemaIds.get(schema);
20
+ if (id === void 0) {
21
+ id = ++nextId;
22
+ schemaIds.set(schema, id);
23
+ }
24
+ return id;
25
+ }).join(",")}`;
26
+ const existing = plans.get(key);
27
+ if (existing) return existing;
28
+ const expanded = expandSchemas(schemas, mapping);
29
+ const fields = /* @__PURE__ */ new Map();
30
+ for (const schema of expanded) for (const [wireName, field] of Object.entries(schema.properties ?? {})) {
31
+ const sourceName = direction === "toApi" ? field.name : wireName;
32
+ const targetName = direction === "toApi" ? wireName : field.name;
33
+ const current = fields.get(sourceName);
34
+ if (current) {
35
+ if (current.name !== targetName) throw new Error(`Conflicting case mapping for ${sourceName}`);
36
+ if (field.schema) current.schemas.push(field.schema);
37
+ } else fields.set(sourceName, {
38
+ name: targetName,
39
+ schemas: field.schema ? [field.schema] : []
40
+ });
41
+ }
42
+ const plan = {
43
+ expanded,
44
+ fields,
45
+ declared: new Set(expanded.flatMap((schema) => schema.declared ?? [])),
46
+ patterns: expanded.flatMap((schema) => Object.entries(schema.patternProperties ?? {}).map(([pattern, nested]) => ({
47
+ regex: new RegExp(pattern),
48
+ schema: nested
49
+ }))),
50
+ additional: expanded.flatMap((schema) => schema.additionalProperties ? [schema.additionalProperties] : []),
51
+ prefixLength: expanded.reduce((max, schema) => Math.max(max, schema.prefixItems?.length ?? 0), 0)
52
+ };
53
+ plans.set(key, plan);
54
+ return plan;
55
+ }
56
+ function itemPlan(plan, index, direction) {
57
+ if (index >= plan.prefixLength) {
58
+ if (plan.items === void 0) plan.items = planFor(plan.expanded.flatMap((schema) => schema.items ? [schema.items] : []), direction) ?? null;
59
+ return plan.items ?? void 0;
60
+ }
61
+ const prefixItems = plan.prefixItems ??= [];
62
+ if (prefixItems[index] === void 0) prefixItems[index] = planFor(plan.expanded.flatMap((schema) => {
63
+ const prefix = schema.prefixItems;
64
+ const item = prefix && index < prefix.length ? prefix[index] : schema.items;
65
+ return item ? [item] : [];
66
+ }), direction) ?? null;
67
+ return prefixItems[index] ?? void 0;
68
+ }
69
+ function convertWithPlan(value, plan, direction) {
70
+ if (!plan || value === null || value === void 0) return value;
71
+ if (Array.isArray(value)) return value.map((item, index) => convertWithPlan(item, itemPlan(plan, index, direction), direction));
72
+ if (typeof value !== "object") return value;
73
+ const prototype = Object.getPrototypeOf(value);
74
+ if (prototype !== Object.prototype && prototype !== null) return value;
75
+ const convertedNames = /* @__PURE__ */ new Set();
76
+ const entries = Object.entries(value).map(([name, item]) => {
77
+ const field = plan.fields.get(name);
78
+ const matched = [];
79
+ let hasPattern = false;
80
+ for (const pattern of plan.patterns) if (pattern.regex.test(name)) {
81
+ hasPattern = true;
82
+ if (pattern.schema) matched.push(pattern.schema);
83
+ }
84
+ let nested;
85
+ if (matched.length > 0) nested = planFor([...field?.schemas ?? [], ...matched], direction);
86
+ else if (field) {
87
+ if (field.nested === void 0) field.nested = planFor(field.schemas, direction) ?? null;
88
+ nested = field.nested ?? void 0;
89
+ } else if (!plan.declared.has(name) && !hasPattern) {
90
+ if (plan.additionalPlan === void 0) plan.additionalPlan = planFor(plan.additional, direction) ?? null;
91
+ nested = plan.additionalPlan ?? void 0;
92
+ }
93
+ const targetName = field?.name ?? name;
94
+ if (convertedNames.has(targetName)) throw new Error(`Conflicting case mapping for ${targetName}`);
95
+ convertedNames.add(targetName);
96
+ return [targetName, convertWithPlan(item, nested, direction)];
97
+ });
98
+ return Object.fromEntries(entries);
99
+ }
100
+ return (value, schemas, direction) => {
101
+ if (schemas.length === 0 || value === null || value === void 0) return value;
102
+ return convertWithPlan(value, planFor(schemas, direction), direction);
103
+ };
104
+ }
105
+ function isJsonMediaType(mediaType) {
106
+ return mediaType === "application/json" || mediaType.endsWith("+json");
107
+ }
108
+ function mediaSchema(media, contentType) {
109
+ if (!media) return void 0;
110
+ const type = contentType?.split(";", 1)[0]?.trim().toLowerCase();
111
+ if (type && !isJsonMediaType(type)) return void 0;
112
+ const entries = Object.entries(media).filter(([name]) => isJsonMediaType(name.toLowerCase()));
113
+ const exact = entries.find(([name]) => name.toLowerCase() === type);
114
+ if (exact) return exact[1] ?? void 0;
115
+ const json = entries.find(([name]) => name.toLowerCase() === "application/json");
116
+ if (json) return json[1] ?? void 0;
117
+ return entries.length === 1 ? entries[0]?.[1] ?? void 0 : void 0;
118
+ }
119
+ function responseMedia(operation, response) {
120
+ const responses = operation.responses;
121
+ if (!responses) return void 0;
122
+ const status = String(response.status);
123
+ const range = `${status[0]}XX`;
124
+ return mediaSchema(Object.hasOwn(responses, status) ? responses[status] : Object.hasOwn(responses, range) ? responses[range] : responses.default, response.headers.get("content-type") ?? void 0);
125
+ }
126
+ function convertRequest(input, operation, convertValue, headers) {
127
+ const parameters = operation.parameters;
128
+ const params = input.params;
129
+ const bodySchema = mediaSchema(operation.requestBodies, headers.get("content-type") ?? void 0);
130
+ return {
131
+ params: parameters ? {
132
+ ...params,
133
+ path: convertValue(params.path, parameters.path ? [parameters.path] : [], "toApi"),
134
+ query: convertValue(params.query, parameters.query ? [parameters.query] : [], "toApi"),
135
+ header: convertValue(params.header, parameters.header ? [parameters.header] : [], "toApi"),
136
+ cookie: convertValue(params.cookie, parameters.cookie ? [parameters.cookie] : [], "toApi")
137
+ } : params,
138
+ body: bodySchema && input.body !== void 0 ? convertValue(input.body, [bodySchema], "toApi") : input.body
139
+ };
140
+ }
141
+ /** Convert generated camelCase values at the openapi-fetch request and result stages. */
142
+ function createCaseMappingMiddleware(mapping) {
143
+ const convertValue = createConverter(mapping);
144
+ return {
145
+ onRequestInit({ method, schemaPath, input, headers }) {
146
+ const operation = mapping.operations?.[`${method} ${schemaPath}`];
147
+ return operation ? convertRequest(input, operation, convertValue, headers) : void 0;
148
+ },
149
+ onResult({ method, schemaPath, result, options }) {
150
+ const operation = mapping.operations?.[`${method} ${schemaPath}`];
151
+ if (!operation) return;
152
+ const schema = responseMedia(operation, result.response);
153
+ if (!schema) return;
154
+ return {
155
+ ...options.parseAs === "json" && result.data !== void 0 ? { data: convertValue(result.data, [schema], "fromApi") } : {},
156
+ ...result.error !== void 0 ? { error: convertValue(result.error, [schema], "fromApi") } : {}
157
+ };
158
+ }
159
+ };
160
+ }
161
+ exports.createCaseMappingMiddleware = createCaseMappingMiddleware;