@orpc/openapi 2.0.0-beta.3 → 2.0.0-beta.30

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.
Files changed (41) hide show
  1. package/README.md +71 -101
  2. package/dist/adapters/aws-lambda/index.d.mts +26 -0
  3. package/dist/adapters/aws-lambda/index.d.ts +26 -0
  4. package/dist/adapters/aws-lambda/index.mjs +21 -0
  5. package/dist/adapters/fastify/index.d.mts +23 -0
  6. package/dist/adapters/fastify/index.d.ts +23 -0
  7. package/dist/adapters/fastify/index.mjs +21 -0
  8. package/dist/adapters/fetch/index.d.mts +12 -2
  9. package/dist/adapters/fetch/index.d.ts +12 -2
  10. package/dist/adapters/fetch/index.mjs +4 -4
  11. package/dist/adapters/node/index.d.mts +7 -2
  12. package/dist/adapters/node/index.d.ts +7 -2
  13. package/dist/adapters/node/index.mjs +3 -3
  14. package/dist/adapters/standard/index.d.mts +10 -46
  15. package/dist/adapters/standard/index.d.ts +10 -46
  16. package/dist/adapters/standard/index.mjs +4 -4
  17. package/dist/extensions/route.d.mts +2 -2
  18. package/dist/extensions/route.d.ts +2 -2
  19. package/dist/helpers/index.d.mts +9 -1
  20. package/dist/helpers/index.d.ts +9 -1
  21. package/dist/helpers/index.mjs +1 -1
  22. package/dist/index.d.mts +70 -36
  23. package/dist/index.d.ts +70 -36
  24. package/dist/index.mjs +701 -738
  25. package/dist/plugins/index.d.mts +10 -1
  26. package/dist/plugins/index.d.ts +10 -1
  27. package/dist/shared/{openapi.DmAa7YPO.mjs → openapi.0yE-t1W-.mjs} +120 -42
  28. package/dist/shared/{openapi.DuDd2iQz.d.mts → openapi.9smDzQwj.d.mts} +10 -7
  29. package/dist/shared/{openapi.B3H7yHQa.mjs → openapi.BaqI61Xi.mjs} +91 -42
  30. package/dist/shared/openapi.Bd9icEUa.d.mts +45 -0
  31. package/dist/shared/{openapi.B2SK0ZAr.mjs → openapi.BlHXe8vI.mjs} +21 -29
  32. package/dist/shared/openapi.D1CqEIRy.d.ts +45 -0
  33. package/dist/shared/{openapi.BQzzr4-4.d.ts → openapi.DTYZZ6Ph.d.ts} +36 -10
  34. package/dist/shared/{openapi.CYgMBSUF.d.mts → openapi.DfTTLtn5.d.mts} +19 -5
  35. package/dist/shared/{openapi.CYgMBSUF.d.ts → openapi.DfTTLtn5.d.ts} +19 -5
  36. package/dist/shared/{openapi.BcEtAxQj.d.mts → openapi.Drcd0PuL.d.mts} +36 -10
  37. package/dist/shared/{openapi.C7m7NAmH.d.mts → openapi.Dz-JaXHo.d.mts} +13 -2
  38. package/dist/shared/{openapi.C7m7NAmH.d.ts → openapi.Dz-JaXHo.d.ts} +13 -2
  39. package/dist/shared/{openapi.DBYxUpK8.d.ts → openapi.YCiBHaJ-.d.ts} +10 -7
  40. package/dist/shared/{openapi.Bt87OzTt.mjs → openapi.s_p5sN-P.mjs} +20 -15
  41. package/package.json +54 -14
@@ -4,7 +4,7 @@ import { Value, Promisable } from '@orpc/shared';
4
4
  import { ApiReferenceConfiguration } from '@scalar/api-reference';
5
5
  import { StandardUrl } from '@standardserver/core';
6
6
  import { SwaggerUIOptions } from 'swagger-ui';
7
- import { a as OpenAPIDocument } from '../shared/openapi.CYgMBSUF.mjs';
7
+ import { OpenAPIDocument } from '../index.mjs';
8
8
  import '@hey-api/spec-types';
9
9
  import '@orpc/client';
10
10
 
@@ -71,6 +71,15 @@ interface OpenAPIReferenceHandlerPluginOptions<T extends Context, TProvider exte
71
71
  */
72
72
  docsHead?: Value<Promisable<string>, [StandardHandlerRoutingInterceptorOptions<T>]>;
73
73
  }
74
+ /**
75
+ * Serves API reference documentation powered by Scalar or Swagger UI,
76
+ * and exposes the OpenAPI specification as JSON.
77
+ *
78
+ * @remarks
79
+ * **Note**: By default, the API reference UI is served from `/` and the OpenAPI specification from `/spec.json`.
80
+ *
81
+ * @see {@link https://orpc.dev/docs/plugins/openapi-reference | OpenAPI Reference Plugin (Swagger/Scalar)}
82
+ */
74
83
  declare class OpenAPIReferenceHandlerPlugin<T extends Context, TProvider extends OpenAPIReferenceHandlerPluginProvider> implements StandardHandlerPlugin<T> {
75
84
  name: string;
76
85
  private readonly spec;
@@ -4,7 +4,7 @@ import { Value, Promisable } from '@orpc/shared';
4
4
  import { ApiReferenceConfiguration } from '@scalar/api-reference';
5
5
  import { StandardUrl } from '@standardserver/core';
6
6
  import { SwaggerUIOptions } from 'swagger-ui';
7
- import { a as OpenAPIDocument } from '../shared/openapi.CYgMBSUF.js';
7
+ import { OpenAPIDocument } from '../index.js';
8
8
  import '@hey-api/spec-types';
9
9
  import '@orpc/client';
10
10
 
@@ -71,6 +71,15 @@ interface OpenAPIReferenceHandlerPluginOptions<T extends Context, TProvider exte
71
71
  */
72
72
  docsHead?: Value<Promisable<string>, [StandardHandlerRoutingInterceptorOptions<T>]>;
73
73
  }
74
+ /**
75
+ * Serves API reference documentation powered by Scalar or Swagger UI,
76
+ * and exposes the OpenAPI specification as JSON.
77
+ *
78
+ * @remarks
79
+ * **Note**: By default, the API reference UI is served from `/` and the OpenAPI specification from `/spec.json`.
80
+ *
81
+ * @see {@link https://orpc.dev/docs/plugins/openapi-reference | OpenAPI Reference Plugin (Swagger/Scalar)}
82
+ */
74
83
  declare class OpenAPIReferenceHandlerPlugin<T extends Context, TProvider extends OpenAPIReferenceHandlerPluginProvider> implements StandardHandlerPlugin<T> {
75
84
  name: string;
76
85
  private readonly spec;
@@ -1,7 +1,7 @@
1
- import { wrapEventIteratorPreservingMeta, toORPCError, isORPCErrorJson, createORPCErrorFromJson } from '@orpc/client';
2
- import { isPlainObject, isAsyncIteratorObject } from '@orpc/shared';
1
+ import { wrapAsyncIteratorPreservingEventMeta, toORPCError, isORPCErrorJson, createORPCErrorFromJson } from '@orpc/client';
2
+ import { isPlainObject, NullProtoObj, isAsyncIteratorObject } from '@orpc/shared';
3
3
  import { ErrorEvent } from '@standardserver/core';
4
- import { B as BracketNotationSerializer } from './openapi.Bt87OzTt.mjs';
4
+ import { B as BracketNotationSerializer } from './openapi.s_p5sN-P.mjs';
5
5
 
6
6
  const DEFAULT_OPENAPI_METHOD = "POST";
7
7
  const DEFAULT_OPENAPI_SUCCESS_DESCRIPTION = "OK";
@@ -84,44 +84,116 @@ const DEFAULT_OPEN_API_JSON_SERIALIZER_HANDLERS = {
84
84
  }
85
85
  };
86
86
  class OpenAPIJsonSerializer {
87
- handlers;
87
+ inlineBuiltInHandlers;
88
+ handlerEntries;
88
89
  omitUndefinedProperties;
89
90
  constructor(options = {}) {
90
- this.handlers = {
91
- ...DEFAULT_OPEN_API_JSON_SERIALIZER_HANDLERS,
92
- ...options.handlers
93
- };
94
91
  this.omitUndefinedProperties = options.omitUndefinedProperties !== false;
92
+ const customHandlers = options.handlers;
93
+ if (customHandlers === void 0) {
94
+ this.inlineBuiltInHandlers = true;
95
+ return;
96
+ }
97
+ let inlineBuiltInHandlers = true;
98
+ let handlerEntries = [];
99
+ for (const key in customHandlers) {
100
+ const handler = customHandlers[key];
101
+ if (inlineBuiltInHandlers && key in DEFAULT_OPEN_API_JSON_SERIALIZER_HANDLERS) {
102
+ inlineBuiltInHandlers = false;
103
+ break;
104
+ }
105
+ if (handler !== void 0) {
106
+ handlerEntries.push(handler);
107
+ }
108
+ }
109
+ if (!inlineBuiltInHandlers) {
110
+ handlerEntries = [];
111
+ for (const handler of Object.values({ ...DEFAULT_OPEN_API_JSON_SERIALIZER_HANDLERS, ...customHandlers })) {
112
+ if (handler !== void 0) {
113
+ handlerEntries.push(handler);
114
+ }
115
+ }
116
+ }
117
+ this.inlineBuiltInHandlers = inlineBuiltInHandlers;
118
+ this.handlerEntries = handlerEntries;
95
119
  }
96
120
  serialize(data) {
97
- const [json, maps, blobs] = this.serializeValue(data, [], [], []);
121
+ const maps = [];
122
+ const blobs = [];
123
+ const json = this.serializeValue(data, [], maps, blobs);
98
124
  return { json, maps, blobs };
99
125
  }
126
+ /**
127
+ * `segments` is a shared mutable stack (push/pop while walking),
128
+ * so it must be copied before being stored in `maps`.
129
+ */
100
130
  serializeValue(data, segments, maps, blobs) {
101
- for (const key in this.handlers) {
102
- const handler = this.handlers[key];
103
- if (handler && handler.condition(data)) {
104
- const serialized = handler.serialize(data);
105
- if (handler.isTerminal) {
106
- return [serialized, maps, blobs];
131
+ if (this.inlineBuiltInHandlers) {
132
+ switch (typeof data) {
133
+ case "string":
134
+ case "boolean":
135
+ return data;
136
+ case "number":
137
+ return Number.isNaN(data) ? null : data;
138
+ case "undefined":
139
+ return null;
140
+ case "bigint":
141
+ return data.toString();
142
+ case "object": {
143
+ if (data === null) {
144
+ return data;
145
+ }
146
+ if (data instanceof Date) {
147
+ return Number.isNaN(data.getTime()) ? null : data.toISOString();
148
+ }
149
+ if (data instanceof URL) {
150
+ return data.toString();
151
+ }
152
+ if (data instanceof RegExp) {
153
+ return data.toString();
154
+ }
155
+ if (data instanceof Set) {
156
+ return this.serializeValue(Array.from(data), segments, maps, blobs);
157
+ }
158
+ if (data instanceof Map) {
159
+ return this.serializeValue(Array.from(data.entries()), segments, maps, blobs);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ const handlerEntries = this.handlerEntries;
165
+ if (handlerEntries) {
166
+ for (let i = 0; i < handlerEntries.length; i++) {
167
+ const handler = handlerEntries[i];
168
+ if (handler.condition(data)) {
169
+ const serialized = handler.serialize(data);
170
+ if (handler.isTerminal) {
171
+ if (serialized instanceof Blob) {
172
+ maps.push(segments.slice());
173
+ blobs.push(serialized);
174
+ }
175
+ return serialized;
176
+ }
177
+ return this.serializeValue(serialized, segments, maps, blobs);
107
178
  }
108
- const result = this.serializeValue(serialized, segments, maps, blobs);
109
- return result;
110
179
  }
111
180
  }
112
181
  if (data instanceof Blob) {
113
- maps.push(segments);
182
+ maps.push(segments.slice());
114
183
  blobs.push(data);
115
- return [data, maps, blobs];
184
+ return data;
116
185
  }
117
186
  if (Array.isArray(data)) {
118
- const json = data.map((v, i) => {
119
- return this.serializeValue(v, [...segments, i], maps, blobs)[0];
120
- });
121
- return [json, maps, blobs];
187
+ const json = [];
188
+ for (let i = 0; i < data.length; i++) {
189
+ segments.push(i);
190
+ json.push(this.serializeValue(data[i], segments, maps, blobs));
191
+ segments.pop();
192
+ }
193
+ return json;
122
194
  }
123
195
  if (isPlainObject(data)) {
124
- const json = {};
196
+ const json = new NullProtoObj();
125
197
  for (const k in data) {
126
198
  const v = data[k];
127
199
  if (k === "toJSON" && typeof v === "function") {
@@ -130,27 +202,30 @@ class OpenAPIJsonSerializer {
130
202
  if (v === void 0 && this.omitUndefinedProperties) {
131
203
  continue;
132
204
  }
133
- json[k] = this.serializeValue(v, [...segments, k], maps, blobs)[0];
205
+ segments.push(k);
206
+ json[k] = this.serializeValue(v, segments, maps, blobs);
207
+ segments.pop();
134
208
  }
135
- return [json, maps, blobs];
209
+ return json;
136
210
  }
137
- return [data, maps, blobs];
211
+ return data;
138
212
  }
139
213
  deserialize(serialized) {
140
214
  const ref = { data: serialized.json };
141
215
  if (serialized.blobs?.length) {
142
- serialized.maps.forEach((segments, i) => {
216
+ for (let i = 0; i < serialized.maps.length; i++) {
217
+ const segments = serialized.maps[i];
143
218
  let currentRef = ref;
144
219
  let preSegment = "data";
145
- segments.forEach((segment) => {
220
+ for (let j = 0; j < segments.length; j++) {
146
221
  currentRef = currentRef[preSegment];
147
- preSegment = segment;
222
+ preSegment = segments[j];
148
223
  if (!Object.hasOwn(currentRef, preSegment)) {
149
224
  throw new Error(`Security error: Invalid serialized data. Segment "${preSegment}" does not exist.`);
150
225
  }
151
- });
226
+ }
152
227
  currentRef[preSegment] = serialized.blobs[i];
153
- });
228
+ }
154
229
  }
155
230
  return ref.data;
156
231
  }
@@ -166,34 +241,34 @@ class OpenAPISerializer {
166
241
  this.defaultSerializeOptions = serialize;
167
242
  }
168
243
  serialize(data, options = {}) {
169
- const useFormDataForBlobFields = options.useFormDataForBlobFields ?? this.defaultSerializeOptions?.useFormDataForBlobFields ?? true;
170
- const asFormData = options.asFormData ?? this.defaultSerializeOptions?.asFormData ?? false;
171
244
  if (!options.asFormData) {
172
245
  if (data === void 0 || data instanceof ReadableStream || data instanceof Blob) {
173
246
  return data;
174
247
  }
175
248
  if (isAsyncIteratorObject(data)) {
176
- return wrapEventIteratorPreservingMeta(data, {
249
+ return wrapAsyncIteratorPreservingEventMeta(data, {
177
250
  mapResult: (result) => {
178
251
  if (result.value === void 0) {
179
252
  return result;
180
253
  }
181
- return { done: result.done, value: this.serializeValue(result.value, { asFormData: false, useFormDataForBlobFields: false }) };
254
+ return { done: result.done, value: this.serializeValue(result.value, false, false) };
182
255
  },
183
256
  mapError: (e) => {
184
257
  return new ErrorEvent({
185
- data: this.serializeValue(toORPCError(e).toJSON(), { asFormData: false, useFormDataForBlobFields: false }),
258
+ data: this.serializeValue(toORPCError(e).toJSON(), false, false),
186
259
  cause: e
187
260
  });
188
261
  }
189
262
  });
190
263
  }
191
264
  }
192
- return this.serializeValue(data, { useFormDataForBlobFields, asFormData });
265
+ const useFormDataForBlobFields = options.useFormDataForBlobFields ?? this.defaultSerializeOptions?.useFormDataForBlobFields ?? true;
266
+ const asFormData = options.asFormData ?? this.defaultSerializeOptions?.asFormData ?? false;
267
+ return this.serializeValue(data, useFormDataForBlobFields, asFormData);
193
268
  }
194
- serializeValue(value, options) {
269
+ serializeValue(value, useFormDataForBlobFields, asFormData) {
195
270
  const { json, blobs } = this.jsonSerializer.serialize(value);
196
- if (!options.asFormData && (json instanceof Blob || json === void 0 || !blobs?.length || !options.useFormDataForBlobFields)) {
271
+ if (!asFormData && (json instanceof Blob || json === void 0 || !blobs?.length || !useFormDataForBlobFields)) {
197
272
  return json;
198
273
  }
199
274
  const form = new FormData();
@@ -211,7 +286,7 @@ class OpenAPISerializer {
211
286
  return data;
212
287
  }
213
288
  if (isAsyncIteratorObject(data)) {
214
- return wrapEventIteratorPreservingMeta(data, {
289
+ return wrapAsyncIteratorPreservingEventMeta(data, {
215
290
  mapResult: (result) => {
216
291
  if (result.value === void 0) {
217
292
  return result;
@@ -236,6 +311,9 @@ class OpenAPISerializer {
236
311
  }
237
312
  }
238
313
 
314
+ function isBodylessMethod(method) {
315
+ return method === "GET" || method === "HEAD";
316
+ }
239
317
  const PARAMETER_NAME_REGEX = /^[\w-]+$/;
240
318
  function getDynamicPathParams(path) {
241
319
  if (!path.includes("{")) {
@@ -272,4 +350,4 @@ function getDynamicPathParams(path) {
272
350
  return params;
273
351
  }
274
352
 
275
- export { DEFAULT_OPENAPI_METHOD as D, OpenAPISerializer as O, DEFAULT_OPENAPI_INPUT_STRUCTURE as a, DEFAULT_OPENAPI_OUTPUT_STRUCTURE as b, DEFAULT_OPENAPI_SUCCESS_DESCRIPTION as c, OpenAPIJsonSerializer as d, getDynamicPathParams as g };
353
+ export { DEFAULT_OPENAPI_METHOD as D, OpenAPISerializer as O, DEFAULT_OPENAPI_INPUT_STRUCTURE as a, DEFAULT_OPENAPI_OUTPUT_STRUCTURE as b, DEFAULT_OPENAPI_SUCCESS_DESCRIPTION as c, OpenAPIJsonSerializer as d, getDynamicPathParams as g, isBodylessMethod as i };
@@ -1,10 +1,10 @@
1
1
  import { AnyORPCError } from '@orpc/client';
2
2
  import { AnyProcedure, AnyRouter, Context } from '@orpc/server';
3
- import { StandardHandlerHandleOptions, StandardHandlerCodec, StandardHandlerCodecResolvedProcedure } from '@orpc/server/standard';
3
+ import { StandardHandlerCodec, StandardHandlerHandleOptions, StandardHandlerCodecResolvedProcedure } from '@orpc/server/standard';
4
4
  import { Value, Promisable } from '@orpc/shared';
5
5
  import { StandardLazyRequest, StandardResponse } from '@standardserver/core';
6
6
  import { AnyProcedureContract } from '@orpc/contract';
7
- import { O as OpenAPISerializer } from './openapi.C7m7NAmH.mjs';
7
+ import { O as OpenAPISerializer } from './openapi.Dz-JaXHo.mjs';
8
8
 
9
9
  interface OpenAPIMatcherOptions {
10
10
  /**
@@ -18,7 +18,7 @@ declare class OpenAPIMatcher {
18
18
  private readonly filter;
19
19
  private readonly rootRouter;
20
20
  private readonly tree;
21
- private pendingLazyRouters;
21
+ private readonly pendingLazyRouters;
22
22
  constructor(router: AnyRouter, options?: OpenAPIMatcherOptions);
23
23
  private index;
24
24
  match(method: string, pathname: `/${string}`, prefix: `/${string}` | undefined): Promise<{
@@ -26,8 +26,10 @@ declare class OpenAPIMatcher {
26
26
  procedure: AnyProcedure;
27
27
  params?: Record<string, string> | undefined;
28
28
  } | undefined>;
29
- private matchPathname;
30
29
  private resolvePendingLazyRouters;
30
+ private loadPendingLazyRouters;
31
+ private loadPendingLazyRouter;
32
+ private indexPendingLazyRouter;
31
33
  private resolveProcedure;
32
34
  }
33
35
 
@@ -38,8 +40,9 @@ interface OpenAPIHandlerCodecCoreOptions<_T extends Context> {
38
40
  serializer?: Pick<OpenAPISerializer, keyof OpenAPISerializer>;
39
41
  /**
40
42
  * Mapping ORPCError Code -> HTTP Status Code
43
+ * The status code should be in the `4xx` or `5xx` range (must be greater than or equal to `400`).
41
44
  *
42
- * @default COMMON_ERROR_STATUS_MAP, DEFAULT_ERROR_STATUS
45
+ * @default COMMON_ERROR_STATUS_MAP
43
46
  */
44
47
  errorStatusMap?: Record<string, number> | undefined;
45
48
  /**
@@ -65,8 +68,8 @@ declare class OpenAPIHandlerCodecCore<T extends Context> {
65
68
  /**
66
69
  * @throws {TypeError} If `outputStructure` is "detailed" and the output doesn't match the expected structure.
67
70
  */
68
- encodeOutput(output: unknown, procedure: AnyProcedure, path: string[], _options: StandardHandlerHandleOptions<T>): Promisable<StandardResponse>;
69
- encodeError(error: AnyORPCError, _procedure: AnyProcedure, _path: string[], _options: StandardHandlerHandleOptions<T>): Promisable<StandardResponse>;
71
+ encodeOutput(output: unknown, procedure: AnyProcedure, path: string[]): Promisable<StandardResponse>;
72
+ encodeError(error: AnyORPCError): Promisable<StandardResponse>;
70
73
  private deserializeQuery;
71
74
  private deserializeParams;
72
75
  }
@@ -1,8 +1,8 @@
1
1
  import { COMMON_ERROR_STATUS_MAP } from '@orpc/client';
2
2
  import { walkProcedureContractsSync, Procedure, unlazy, getRouter, createContractProcedure, DEFAULT_SUCCESS_STATUS, DEFAULT_ERROR_STATUS } from '@orpc/server';
3
- import { value, pathToHttpPath, mergeHttpPath, normalizeHttpPath, tryDecodeURIComponent, isPlainObject, stringifyJSON, parseEmptyableJSON, isTypescriptObject, NullProtoObj } from '@orpc/shared';
4
- import { parseStandardUrl, isStandardHeaders } from '@standardserver/core';
5
- import { D as DEFAULT_OPENAPI_METHOD, g as getDynamicPathParams, O as OpenAPISerializer, a as DEFAULT_OPENAPI_INPUT_STRUCTURE, b as DEFAULT_OPENAPI_OUTPUT_STRUCTURE } from './openapi.DmAa7YPO.mjs';
3
+ import { value, pathToHttpPath, mergeHttpPath, normalizeHttpPath, tryDecodeURIComponent, NullProtoObj, isTypescriptObject, isPlainObject, stringifyJSON, parseEmptyableJSON } from '@orpc/shared';
4
+ import { parseStandardUrl } from '@standardserver/core';
5
+ import { D as DEFAULT_OPENAPI_METHOD, g as getDynamicPathParams, O as OpenAPISerializer, a as DEFAULT_OPENAPI_INPUT_STRUCTURE, i as isBodylessMethod, b as DEFAULT_OPENAPI_OUTPUT_STRUCTURE } from './openapi.0yE-t1W-.mjs';
6
6
  import { g as getOpenAPIMeta } from './openapi.B9PQzqBn.mjs';
7
7
  import { createRouter, addRoute, findRoute, routeToRegExp } from 'rou3';
8
8
 
@@ -10,7 +10,7 @@ class OpenAPIMatcher {
10
10
  filter;
11
11
  rootRouter;
12
12
  tree = createRouter();
13
- pendingLazyRouters = [];
13
+ pendingLazyRouters = /* @__PURE__ */ new Set();
14
14
  constructor(router, options = {}) {
15
15
  this.filter = options.filter ?? true;
16
16
  this.rootRouter = router;
@@ -32,13 +32,13 @@ class OpenAPIMatcher {
32
32
  procedure: contract instanceof Procedure ? contract : void 0
33
33
  });
34
34
  }, path);
35
- this.pendingLazyRouters.push(...lazyResults.map((result) => {
35
+ for (const result of lazyResults) {
36
36
  const prefix = getOpenAPIMeta(result.router)?.prefix;
37
- return {
37
+ this.pendingLazyRouters.add({
38
38
  ...result,
39
39
  matcher: prefix ? toRou3PrefixMatcher(prefix) : void 0
40
- };
41
- }));
40
+ });
41
+ }
42
42
  }
43
43
  async match(method, pathname, prefix) {
44
44
  if (prefix) {
@@ -56,44 +56,58 @@ class OpenAPIMatcher {
56
56
  return void 0;
57
57
  }
58
58
  }
59
- const result = await this.matchPathname(method, pathname);
60
- if (!result && pathname.includes("%")) {
61
- return this.matchPathname(method, normalizeHttpPath(pathname));
59
+ const loading = this.resolvePendingLazyRouters(pathname);
60
+ if (loading !== void 0) {
61
+ await loading;
62
62
  }
63
- return result;
64
- }
65
- async matchPathname(method, pathname) {
66
- await this.resolvePendingLazyRouters(pathname);
67
- const match = findRoute(this.tree, method, pathname);
68
- if (!match) {
63
+ let match = findRoute(this.tree, method, pathname);
64
+ if (match === void 0 && pathname.includes("%")) {
65
+ const normalizedPathname = normalizeHttpPath(pathname);
66
+ const normalizedLoading = this.resolvePendingLazyRouters(normalizedPathname);
67
+ if (normalizedLoading !== void 0) {
68
+ await normalizedLoading;
69
+ }
70
+ match = findRoute(this.tree, method, normalizedPathname);
71
+ }
72
+ if (match === void 0) {
69
73
  return void 0;
70
74
  }
71
- const procedure = await this.resolveProcedure(match.data);
75
+ const entry = match.data;
72
76
  return {
73
- path: match.data.path,
74
- procedure,
77
+ path: entry.path,
78
+ procedure: entry.procedure ?? await this.resolveProcedure(entry),
75
79
  params: match.params ? decodeParams(match.params) : void 0
76
80
  };
77
81
  }
78
- async resolvePendingLazyRouters(pathname) {
79
- if (!this.pendingLazyRouters.length) {
80
- return;
82
+ resolvePendingLazyRouters(pathname) {
83
+ for (const pending of this.pendingLazyRouters) {
84
+ if (pending.matcher === void 0 || pending.matcher.test(pathname)) {
85
+ return this.loadPendingLazyRouters(pathname);
86
+ }
81
87
  }
82
- const stillPending = [];
88
+ }
89
+ async loadPendingLazyRouters(pathname) {
83
90
  for (const pending of this.pendingLazyRouters) {
84
- if (!pending.matcher || pending.matcher.test(pathname)) {
85
- const { default: router } = await unlazy(pending.router);
86
- this.index(router, pending.path);
87
- } else {
88
- stillPending.push(pending);
91
+ if (pending.matcher === void 0 || pending.matcher.test(pathname)) {
92
+ await this.loadPendingLazyRouter(pending);
89
93
  }
90
94
  }
91
- this.pendingLazyRouters = stillPending;
92
95
  }
93
- async resolveProcedure(entry) {
94
- if (entry.procedure) {
95
- return entry.procedure;
96
+ loadPendingLazyRouter(pending) {
97
+ if (pending.loading === void 0) {
98
+ pending.loading = this.indexPendingLazyRouter(pending).catch((error) => {
99
+ pending.loading = void 0;
100
+ throw error;
101
+ });
96
102
  }
103
+ return pending.loading;
104
+ }
105
+ async indexPendingLazyRouter(pending) {
106
+ const { default: router } = await unlazy(pending.router);
107
+ this.index(router, pending.path);
108
+ this.pendingLazyRouters.delete(pending);
109
+ }
110
+ async resolveProcedure(entry) {
97
111
  const { default: maybeProcedure } = await unlazy(getRouter(this.rootRouter, entry.path));
98
112
  if (!(maybeProcedure instanceof Procedure)) {
99
113
  throw new TypeError(
@@ -124,6 +138,41 @@ function decodeParams(params) {
124
138
  return Object.fromEntries(Object.entries(params).map(([key, val]) => [key, tryDecodeURIComponent(val)]));
125
139
  }
126
140
 
141
+ function serializeHeaders(headers, serializer) {
142
+ const result = new NullProtoObj();
143
+ for (const [key, value] of Object.entries(headers)) {
144
+ if (Array.isArray(value)) {
145
+ const lines = [];
146
+ for (const item of value) {
147
+ const line2 = serializeHeaderValue(item, serializer);
148
+ if (line2 !== void 0) {
149
+ lines.push(line2);
150
+ }
151
+ }
152
+ result[key] = lines;
153
+ continue;
154
+ }
155
+ const line = serializeHeaderValue(value, serializer);
156
+ if (line !== void 0) {
157
+ result[key] = line;
158
+ }
159
+ }
160
+ return result;
161
+ }
162
+ function serializeHeaderValue(value, serializer) {
163
+ const serialized = serializer.serialize(value);
164
+ if (Array.isArray(serialized)) {
165
+ return serialized.filter((item) => item !== void 0 && item !== null).map(String).join(",");
166
+ }
167
+ if (isTypescriptObject(serialized)) {
168
+ return Object.entries(serialized).filter(([, val]) => val !== void 0 && val !== null).map(([key, val]) => `${String(key)},${String(val)}`).join(",");
169
+ }
170
+ if (serialized !== void 0 && serialized !== null) {
171
+ return String(serialized);
172
+ }
173
+ return void 0;
174
+ }
175
+
127
176
  class OpenAPIHandlerCodecCore {
128
177
  serializer;
129
178
  errorStatusMap;
@@ -140,7 +189,7 @@ class OpenAPIHandlerCodecCore {
140
189
  const params = this.deserializeParams(matched.params, meta?.paramsStyles);
141
190
  const query = this.deserializeQuery(search, meta?.queryStyles);
142
191
  if (inputStructure === "compact") {
143
- const data = request.method === "GET" ? query : this.serializer.deserialize(await request.resolveBody(meta?.requestBodyHint));
192
+ const data = isBodylessMethod(request.method) ? query : this.serializer.deserialize(await request.resolveBody(meta?.requestBodyHint));
144
193
  if (data === void 0) {
145
194
  return params;
146
195
  }
@@ -165,7 +214,7 @@ class OpenAPIHandlerCodecCore {
165
214
  /**
166
215
  * @throws {TypeError} If `outputStructure` is "detailed" and the output doesn't match the expected structure.
167
216
  */
168
- encodeOutput(output, procedure, path, _options) {
217
+ encodeOutput(output, procedure, path) {
169
218
  const meta = getOpenAPIMeta(procedure);
170
219
  const successStatus = meta?.successStatus ?? DEFAULT_SUCCESS_STATUS;
171
220
  const outputStructure = meta?.outputStructure ?? DEFAULT_OPENAPI_OUTPUT_STRUCTURE;
@@ -180,8 +229,8 @@ class OpenAPIHandlerCodecCore {
180
229
  throw new TypeError(`
181
230
  Invalid "detailed" output structure returned by procedure (${path.join(".")}):
182
231
  \u2022 Expected an object with optional properties:
183
- - status (number 200-399)
184
- - headers (Record<string, string | string[] | undefined>)
232
+ - status (number <400)
233
+ - headers (object)
185
234
  - body (any)
186
235
  \u2022 No extra keys allowed.
187
236
 
@@ -191,11 +240,11 @@ class OpenAPIHandlerCodecCore {
191
240
  }
192
241
  return {
193
242
  status: output.status ?? successStatus,
194
- headers: output.headers ?? {},
243
+ headers: output.headers !== void 0 ? serializeHeaders(output.headers, this.serializer) : {},
195
244
  body: this.serializer.serialize(output.body)
196
245
  };
197
246
  }
198
- encodeError(error, _procedure, _path, _options) {
247
+ encodeError(error) {
199
248
  const status = this.errorStatusMap[error.code] ?? DEFAULT_ERROR_STATUS;
200
249
  return {
201
250
  status,
@@ -288,10 +337,10 @@ function isValidDetailedOutput(output) {
288
337
  if (Object.keys(output).some((key) => key !== "status" && key !== "headers" && key !== "body")) {
289
338
  return false;
290
339
  }
291
- if (output.status !== void 0 && (typeof output.status !== "number" || !Number.isInteger(output.status) || output.status < 200 || output.status > 399)) {
340
+ if (output.status !== void 0 && (typeof output.status !== "number" || !Number.isInteger(output.status) || output.status > 399)) {
292
341
  return false;
293
342
  }
294
- if (output.headers !== void 0 && !isStandardHeaders(output.headers)) {
343
+ if (output.headers !== void 0 && !isTypescriptObject(output.headers)) {
295
344
  return false;
296
345
  }
297
346
  return true;
@@ -315,4 +364,4 @@ function decodeDelimitedObject(value, delimiter) {
315
364
  return obj;
316
365
  }
317
366
 
318
- export { OpenAPIHandlerCodec as O, OpenAPIHandlerCodecCore as a, OpenAPIMatcher as b };
367
+ export { OpenAPIHandlerCodec as O, OpenAPIHandlerCodecCore as a, OpenAPIMatcher as b, serializeHeaders as s };
@@ -0,0 +1,45 @@
1
+ import { ClientContext, ClientOptions, AnyORPCError } from '@orpc/client';
2
+ import { StandardLinkCodec, StandardLinkCodecDecodedResponse } from '@orpc/client/standard';
3
+ import { RouterContract } from '@orpc/contract';
4
+ import { Value, Promisable } from '@orpc/shared';
5
+ import { StandardUrl, StandardHeaders, StandardLazyResponse, StandardRequest } from '@standardserver/core';
6
+ import { O as OpenAPISerializer } from './openapi.Dz-JaXHo.mjs';
7
+
8
+ interface OpenAPILinkCodecOptions<T extends ClientContext> {
9
+ /**
10
+ * Base URL for all requests, without origin. Should match the OpenAPI handler mount path.
11
+ *
12
+ * @example '/api'
13
+ * @default '/'
14
+ */
15
+ url?: Value<Promisable<StandardUrl>, [options: ClientOptions<T>, path: string[], input: unknown]>;
16
+ /**
17
+ * Inject headers into the request.
18
+ */
19
+ headers?: Value<Promisable<StandardHeaders | Headers>, [options: ClientOptions<T>, path: string[], input: unknown]>;
20
+ /**
21
+ * Override the default OpenAPI serializer.
22
+ */
23
+ serializer?: Pick<OpenAPISerializer, keyof OpenAPISerializer>;
24
+ /**
25
+ * Customize how an error response body is converted into an ORPC error.
26
+ * Return `null` or `undefined` to fall back to the default decoding behavior.
27
+ */
28
+ customErrorResponseBodyDecoder?: (deserializedBody: unknown, response: StandardLazyResponse) => AnyORPCError | null | undefined;
29
+ }
30
+ declare class OpenAPILinkCodec<T extends ClientContext> implements StandardLinkCodec<T> {
31
+ private readonly router;
32
+ private readonly baseUrl;
33
+ private readonly headers;
34
+ private readonly serializer;
35
+ private readonly customErrorResponseBodyDecoder;
36
+ constructor(router: RouterContract, options?: OpenAPILinkCodecOptions<T>);
37
+ encodeInput(input: unknown, path: string[], options: ClientOptions<T>): Promise<StandardRequest>;
38
+ private encodePathParam;
39
+ private serializeQueryString;
40
+ decodeResponse(response: StandardLazyResponse, path: string[], _options: ClientOptions<T>): Promise<StandardLinkCodecDecodedResponse>;
41
+ private resolveProcedure;
42
+ }
43
+
44
+ export { OpenAPILinkCodec as a };
45
+ export type { OpenAPILinkCodecOptions as O };