@orpc/openapi 2.0.0-beta.4 → 2.0.0-beta.41

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 (45) hide show
  1. package/README.md +97 -94
  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 +15 -5
  9. package/dist/adapters/fetch/index.d.ts +15 -5
  10. package/dist/adapters/fetch/index.mjs +6 -6
  11. package/dist/adapters/node/index.d.mts +9 -4
  12. package/dist/adapters/node/index.d.ts +9 -4
  13. package/dist/adapters/node/index.mjs +4 -4
  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 +6 -6
  17. package/dist/extensions/route.d.mts +5 -7
  18. package/dist/extensions/route.d.ts +5 -7
  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 +77 -45
  23. package/dist/index.d.ts +77 -45
  24. package/dist/index.mjs +614 -765
  25. package/dist/plugins/index.d.mts +35 -6
  26. package/dist/plugins/index.d.ts +35 -6
  27. package/dist/plugins/index.mjs +15 -10
  28. package/dist/shared/{openapi.B2SK0ZAr.mjs → openapi.8cM6P94D.mjs} +32 -36
  29. package/dist/shared/openapi.B2G-HeFn.mjs +123 -0
  30. package/dist/shared/{openapi.7vgmPxca.d.ts → openapi.BCNfESWr.d.ts} +12 -9
  31. package/dist/shared/{openapi.CX6Ri5dP.d.mts → openapi.BgpM--nz.d.mts} +12 -9
  32. package/dist/shared/{openapi.CTlpLuKN.mjs → openapi.Bi1qxGF9.mjs} +102 -54
  33. package/dist/shared/{openapi.DmAa7YPO.mjs → openapi.Bu_PfTtX.mjs} +140 -61
  34. package/dist/shared/{openapi.C7m7NAmH.d.mts → openapi.C2LayTNZ.d.mts} +16 -5
  35. package/dist/shared/{openapi.C7m7NAmH.d.ts → openapi.C2LayTNZ.d.ts} +16 -5
  36. package/dist/shared/openapi.C6FFC29v.d.mts +31 -0
  37. package/dist/shared/openapi.C6FFC29v.d.ts +31 -0
  38. package/dist/shared/{openapi.BQzzr4-4.d.ts → openapi.Cem81Zhm.d.mts} +41 -12
  39. package/dist/shared/{openapi.BcEtAxQj.d.mts → openapi.Cem81Zhm.d.ts} +41 -12
  40. package/dist/shared/openapi.Cg9h6jpm.d.mts +45 -0
  41. package/dist/shared/openapi.D0yWs-uW.d.ts +45 -0
  42. package/package.json +57 -17
  43. package/dist/shared/openapi.Bt87OzTt.mjs +0 -131
  44. package/dist/shared/openapi.CYgMBSUF.d.mts +0 -18
  45. package/dist/shared/openapi.CYgMBSUF.d.ts +0 -18
@@ -2,10 +2,10 @@ import { Context } from '@orpc/server';
2
2
  import { StandardHandlerPlugin, StandardHandlerRoutingInterceptorOptions, StandardHandlerOptions } from '@orpc/server/standard';
3
3
  import { Value, Promisable } from '@orpc/shared';
4
4
  import { ApiReferenceConfiguration } from '@scalar/api-reference';
5
- import { StandardUrl } from '@standardserver/core';
5
+ import { StandardUrl } from '@standard-server/core';
6
6
  import { SwaggerUIOptions } from 'swagger-ui';
7
- import { a as OpenAPIDocument } from '../shared/openapi.CYgMBSUF.mjs';
8
- import '@hey-api/spec-types';
7
+ import { OpenAPIDocument, OpenAPIVersion } from '../index.mjs';
8
+ import '@openapi-spec/types';
9
9
  import '@orpc/client';
10
10
 
11
11
  type OpenAPIReferenceHandlerPluginProvider = 'scalar' | 'swagger';
@@ -13,15 +13,34 @@ interface OpenAPIReferenceHandlerPluginScalarConfig extends Partial<ApiReference
13
13
  }
14
14
  interface OpenAPIReferenceHandlerPluginSwaggerConfig extends Partial<Omit<SwaggerUIOptions, 'dom_id' | 'presets' | 'plugins'>> {
15
15
  dom_id?: undefined | never;
16
- presets?: undefined | `SwaggerUIBundle.${string}`[];
17
- plugins?: undefined | `SwaggerUIBundle.${string}`[];
16
+ /**
17
+ * Paths to global variables holding the presets, resolved in the browser,
18
+ * e.g. `'SwaggerUIBundle.presets.apis'`. Load custom presets through `docsHead`.
19
+ *
20
+ * @default ['SwaggerUIBundle.presets.apis']
21
+ */
22
+ presets?: undefined | string[];
23
+ /**
24
+ * Paths to global variables holding the plugins, resolved in the browser,
25
+ * e.g. `'SwaggerUIBundle.plugins.DownloadUrl'`. Load custom plugins through `docsHead`.
26
+ *
27
+ * @default ['SwaggerUIBundle.plugins.DownloadUrl']
28
+ */
29
+ plugins?: undefined | string[];
18
30
  }
19
31
  interface OpenAPIReferenceHandlerPluginOptions<T extends Context, TProvider extends OpenAPIReferenceHandlerPluginProvider> {
20
32
  /**
21
33
  * A static or dynamic OpenAPI document to serve.
22
34
  * Receives routing interceptor options when provided as a function.
23
35
  */
24
- spec: Value<Promisable<OpenAPIDocument>, [StandardHandlerRoutingInterceptorOptions<T>]>;
36
+ spec: Value<Promisable<OpenAPIDocument<OpenAPIVersion>>, [StandardHandlerRoutingInterceptorOptions<T>]>;
37
+ /**
38
+ * Determines whether the docs UI and OpenAPI JSON are allowed to be served for a request.
39
+ * When it resolves to `false`, the request falls through as unmatched,
40
+ * as if the plugin were not installed. Useful for restricting access
41
+ * to authenticated users.
42
+ */
43
+ allow?: Value<Promisable<boolean>, [StandardHandlerRoutingInterceptorOptions<T>]>;
25
44
  /**
26
45
  * The URL path at which to serve the OpenAPI JSON.
27
46
  *
@@ -71,9 +90,19 @@ interface OpenAPIReferenceHandlerPluginOptions<T extends Context, TProvider exte
71
90
  */
72
91
  docsHead?: Value<Promisable<string>, [StandardHandlerRoutingInterceptorOptions<T>]>;
73
92
  }
93
+ /**
94
+ * Serves API reference documentation powered by Scalar or Swagger UI,
95
+ * and exposes the OpenAPI specification as JSON.
96
+ *
97
+ * @remarks
98
+ * **Note**: By default, the API reference UI is served from `/` and the OpenAPI specification from `/spec.json`.
99
+ *
100
+ * @see {@link https://orpc.dev/docs/plugins/openapi-reference | OpenAPI Reference Plugin (Swagger/Scalar)}
101
+ */
74
102
  declare class OpenAPIReferenceHandlerPlugin<T extends Context, TProvider extends OpenAPIReferenceHandlerPluginProvider> implements StandardHandlerPlugin<T> {
75
103
  name: string;
76
104
  private readonly spec;
105
+ private readonly allow;
77
106
  private readonly specPath;
78
107
  private readonly provider;
79
108
  private readonly providerConfig;
@@ -2,10 +2,10 @@ import { Context } from '@orpc/server';
2
2
  import { StandardHandlerPlugin, StandardHandlerRoutingInterceptorOptions, StandardHandlerOptions } from '@orpc/server/standard';
3
3
  import { Value, Promisable } from '@orpc/shared';
4
4
  import { ApiReferenceConfiguration } from '@scalar/api-reference';
5
- import { StandardUrl } from '@standardserver/core';
5
+ import { StandardUrl } from '@standard-server/core';
6
6
  import { SwaggerUIOptions } from 'swagger-ui';
7
- import { a as OpenAPIDocument } from '../shared/openapi.CYgMBSUF.js';
8
- import '@hey-api/spec-types';
7
+ import { OpenAPIDocument, OpenAPIVersion } from '../index.js';
8
+ import '@openapi-spec/types';
9
9
  import '@orpc/client';
10
10
 
11
11
  type OpenAPIReferenceHandlerPluginProvider = 'scalar' | 'swagger';
@@ -13,15 +13,34 @@ interface OpenAPIReferenceHandlerPluginScalarConfig extends Partial<ApiReference
13
13
  }
14
14
  interface OpenAPIReferenceHandlerPluginSwaggerConfig extends Partial<Omit<SwaggerUIOptions, 'dom_id' | 'presets' | 'plugins'>> {
15
15
  dom_id?: undefined | never;
16
- presets?: undefined | `SwaggerUIBundle.${string}`[];
17
- plugins?: undefined | `SwaggerUIBundle.${string}`[];
16
+ /**
17
+ * Paths to global variables holding the presets, resolved in the browser,
18
+ * e.g. `'SwaggerUIBundle.presets.apis'`. Load custom presets through `docsHead`.
19
+ *
20
+ * @default ['SwaggerUIBundle.presets.apis']
21
+ */
22
+ presets?: undefined | string[];
23
+ /**
24
+ * Paths to global variables holding the plugins, resolved in the browser,
25
+ * e.g. `'SwaggerUIBundle.plugins.DownloadUrl'`. Load custom plugins through `docsHead`.
26
+ *
27
+ * @default ['SwaggerUIBundle.plugins.DownloadUrl']
28
+ */
29
+ plugins?: undefined | string[];
18
30
  }
19
31
  interface OpenAPIReferenceHandlerPluginOptions<T extends Context, TProvider extends OpenAPIReferenceHandlerPluginProvider> {
20
32
  /**
21
33
  * A static or dynamic OpenAPI document to serve.
22
34
  * Receives routing interceptor options when provided as a function.
23
35
  */
24
- spec: Value<Promisable<OpenAPIDocument>, [StandardHandlerRoutingInterceptorOptions<T>]>;
36
+ spec: Value<Promisable<OpenAPIDocument<OpenAPIVersion>>, [StandardHandlerRoutingInterceptorOptions<T>]>;
37
+ /**
38
+ * Determines whether the docs UI and OpenAPI JSON are allowed to be served for a request.
39
+ * When it resolves to `false`, the request falls through as unmatched,
40
+ * as if the plugin were not installed. Useful for restricting access
41
+ * to authenticated users.
42
+ */
43
+ allow?: Value<Promisable<boolean>, [StandardHandlerRoutingInterceptorOptions<T>]>;
25
44
  /**
26
45
  * The URL path at which to serve the OpenAPI JSON.
27
46
  *
@@ -71,9 +90,19 @@ interface OpenAPIReferenceHandlerPluginOptions<T extends Context, TProvider exte
71
90
  */
72
91
  docsHead?: Value<Promisable<string>, [StandardHandlerRoutingInterceptorOptions<T>]>;
73
92
  }
93
+ /**
94
+ * Serves API reference documentation powered by Scalar or Swagger UI,
95
+ * and exposes the OpenAPI specification as JSON.
96
+ *
97
+ * @remarks
98
+ * **Note**: By default, the API reference UI is served from `/` and the OpenAPI specification from `/spec.json`.
99
+ *
100
+ * @see {@link https://orpc.dev/docs/plugins/openapi-reference | OpenAPI Reference Plugin (Swagger/Scalar)}
101
+ */
74
102
  declare class OpenAPIReferenceHandlerPlugin<T extends Context, TProvider extends OpenAPIReferenceHandlerPluginProvider> implements StandardHandlerPlugin<T> {
75
103
  name: string;
76
104
  private readonly spec;
105
+ private readonly allow;
77
106
  private readonly specPath;
78
107
  private readonly provider;
79
108
  private readonly providerConfig;
@@ -1,8 +1,9 @@
1
- import { toArray, matchesHttpPath, mergeHttpPath, getOpenTelemetryConfig, value, stringifyJSON } from '@orpc/shared';
1
+ import { toArray, matchesHttpPath, mergeHttpPath, value, getTracer, stringifyJSON } from '@orpc/shared';
2
2
 
3
3
  class OpenAPIReferenceHandlerPlugin {
4
4
  name = "~openapi-reference";
5
5
  spec;
6
+ allow;
6
7
  specPath;
7
8
  provider;
8
9
  providerConfig;
@@ -13,6 +14,7 @@ class OpenAPIReferenceHandlerPlugin {
13
14
  docsHead;
14
15
  constructor(options) {
15
16
  this.spec = options.spec;
17
+ this.allow = options.allow;
16
18
  this.specPath = options.specPath ?? "/spec.json";
17
19
  this.provider = options.provider ?? "scalar";
18
20
  this.providerConfig = options.providerConfig;
@@ -44,7 +46,10 @@ class OpenAPIReferenceHandlerPlugin {
44
46
  if (!isSpecPath && !isDocsPath) {
45
47
  return result;
46
48
  }
47
- const span = getOpenTelemetryConfig()?.trace.getActiveSpan();
49
+ if (await value(this.allow, routingInterceptorOptions) === false) {
50
+ return result;
51
+ }
52
+ const span = getTracer()?.getActiveSpan();
48
53
  const spec = await value(this.spec, routingInterceptorOptions);
49
54
  if (isSpecPath) {
50
55
  span?.updateName(`${routingInterceptorOptions.request.method} ${routingInterceptorOptions.request.url} (openapi spec)`);
@@ -64,13 +69,8 @@ class OpenAPIReferenceHandlerPlugin {
64
69
  dom_id: "#app",
65
70
  spec,
66
71
  deepLinking: true,
67
- presets: [
68
- "SwaggerUIBundle.presets.apis",
69
- "SwaggerUIBundle.presets.standalone"
70
- ],
71
- plugins: [
72
- "SwaggerUIBundle.plugins.DownloadUrl"
73
- ],
72
+ presets: ["SwaggerUIBundle.presets.apis"],
73
+ plugins: ["SwaggerUIBundle.plugins.DownloadUrl"],
74
74
  ...this.providerConfig
75
75
  };
76
76
  html = `
@@ -90,10 +90,15 @@ class OpenAPIReferenceHandlerPlugin {
90
90
 
91
91
  <!-- IMPORTANT: assign to a variable first to prevent ), ( in values breaking the call expression. -->
92
92
  <!-- IMPORTANT: escapeJsonForHtml ensures <, > cannot terminate the <\/script> tag prematurely. -->
93
+ <!-- IMPORTANT: resolve presets/plugins global paths at runtime, never unquote them in the JSON, which also holds the spec. -->
93
94
  <script>
94
- const swaggerConfig = ${escapeJsonForHtml(config).replace(/"(SwaggerUIBundle\.[.a-zA-Z0-9]+)"/g, "$1")}
95
+ const swaggerConfig = ${escapeJsonForHtml(config)}
95
96
 
96
97
  window.onload = () => {
98
+ for (const key of ['presets', 'plugins']) {
99
+ swaggerConfig[key] &&= swaggerConfig[key].map(path => path.split('.').reduce((value, name) => value?.[name], window))
100
+ }
101
+
97
102
  window.ui = SwaggerUIBundle(swaggerConfig)
98
103
  }
99
104
  <\/script>
@@ -1,15 +1,15 @@
1
- import { isORPCErrorJson, createORPCErrorFromJson, ORPCError } from '@orpc/client';
1
+ import { createORPCErrorFromMalformedResponse, isORPCErrorJson, createORPCErrorFromJson } from '@orpc/client';
2
2
  import { getRouterContract, ProcedureContract } from '@orpc/contract';
3
3
  import { unlazy } from '@orpc/server';
4
- import { value, pathToHttpPath, mergeHttpPath, isTypescriptObject, stringifyJSON } from '@orpc/shared';
5
- import { mergeStandardHeaders, parseStandardUrl, isStandardHeaders } from '@standardserver/core';
6
- import { toStandardHeaders } from '@standardserver/fetch';
7
- import { O as OpenAPISerializer, D as DEFAULT_OPENAPI_METHOD, a as DEFAULT_OPENAPI_INPUT_STRUCTURE, g as getDynamicPathParams, b as DEFAULT_OPENAPI_OUTPUT_STRUCTURE } from './openapi.DmAa7YPO.mjs';
4
+ import { value, pathToHttpPath, mergeHttpPath, isTypescriptObject, stringifyJSON, safeEncodeURIComponent } from '@orpc/shared';
5
+ import { mergeStandardHeaders, parseStandardUrl } from '@standard-server/core';
6
+ import { toStandardHeaders } from '@standard-server/fetch';
7
+ import { O as OpenAPISerializer, D as DEFAULT_OPENAPI_METHOD, a as DEFAULT_OPENAPI_INPUT_STRUCTURE, g as getDynamicPathParams, i as isBodylessMethod, b as DEFAULT_OPENAPI_OUTPUT_STRUCTURE } from './openapi.Bu_PfTtX.mjs';
8
8
  import { g as getOpenAPIMeta } from './openapi.B9PQzqBn.mjs';
9
+ import { s as serializeHeaders } from './openapi.Bi1qxGF9.mjs';
9
10
 
10
- class OpenAPILinkCodecError extends TypeError {
11
- }
12
11
  const END_SLASH_REGEX = /\/$/;
12
+ const DOT_SEGMENT_REGEX = /(?:^|\/)\.{1,2}(?:\/|$)/;
13
13
  class OpenAPILinkCodec {
14
14
  constructor(router, options = {}) {
15
15
  this.router = router;
@@ -42,7 +42,7 @@ class OpenAPILinkCodec {
42
42
  let data = input;
43
43
  if (dynamicParams?.length) {
44
44
  if (!isTypescriptObject(input)) {
45
- throw new OpenAPILinkCodecError(
45
+ throw new TypeError(
46
46
  `Input must be an object with "compact" input structure when the path has dynamic params (${dynamicParams.map((p) => p.parameterName).join(", ")}) in call to procedure (${path.join(".")}).`
47
47
  );
48
48
  }
@@ -56,7 +56,7 @@ class OpenAPILinkCodec {
56
56
  data = Object.keys(remaining).length > 0 ? remaining : void 0;
57
57
  }
58
58
  pathname = `${basePathname.replace(END_SLASH_REGEX, "")}${pathname}`;
59
- if (method === "GET") {
59
+ if (isBodylessMethod(method)) {
60
60
  const queryString2 = this.serializeQueryString(data, meta?.queryStyles);
61
61
  const search2 = combineSearch(baseSearch, queryString2);
62
62
  const url3 = `${pathname}${search2 ?? ""}${baseHash ?? ""}`;
@@ -78,12 +78,12 @@ class OpenAPILinkCodec {
78
78
  };
79
79
  }
80
80
  if (!isValidDetailedInput(input)) {
81
- throw new OpenAPILinkCodecError(`
81
+ throw new TypeError(`
82
82
  Invalid "detailed" input structure in call to procedure (${path.join(".")}):
83
83
  \u2022 Expected an object or undefined with optional properties:
84
84
  - params (object, required when the path has dynamic params)
85
85
  - query (object)
86
- - headers (Record<string, string | string[] | undefined>)
86
+ - headers (object)
87
87
  - body (any)
88
88
 
89
89
  Actual value:
@@ -92,7 +92,7 @@ class OpenAPILinkCodec {
92
92
  }
93
93
  if (dynamicParams?.length) {
94
94
  if (!input?.params) {
95
- throw new OpenAPILinkCodecError(
95
+ throw new TypeError(
96
96
  `The "params" property is required for "detailed" input when the path has dynamic params (${dynamicParams.map((p) => p.parameterName).join(", ")}) in call to procedure (${path.join(".")}).`
97
97
  );
98
98
  }
@@ -104,13 +104,13 @@ class OpenAPILinkCodec {
104
104
  }
105
105
  }
106
106
  if (input?.headers) {
107
- headers = mergeStandardHeaders(headers, input.headers);
107
+ headers = mergeStandardHeaders(headers, serializeHeaders(input.headers, this.serializer));
108
108
  }
109
109
  pathname = `${basePathname.replace(END_SLASH_REGEX, "")}${pathname}`;
110
110
  const queryString = this.serializeQueryString(input?.query, meta?.queryStyles);
111
111
  const search = combineSearch(baseSearch, queryString);
112
112
  const url = `${pathname}${search ?? ""}${baseHash ?? ""}`;
113
- if (method === "GET") {
113
+ if (isBodylessMethod(method)) {
114
114
  return {
115
115
  body: void 0,
116
116
  method,
@@ -130,21 +130,24 @@ class OpenAPILinkCodec {
130
130
  encodePathParam(val, param, style, path) {
131
131
  let encoded;
132
132
  if (style === "comma-delimited-array" && Array.isArray(val)) {
133
- encoded = val.map((val2) => this.serializer.serialize(val2)).filter((val2) => val2 !== void 0 && val2 !== null).map((val2) => encodeURIComponent(String(val2))).join(",");
133
+ encoded = val.map((val2) => this.serializer.serialize(val2)).filter((val2) => val2 !== void 0 && val2 !== null).map((val2) => safeEncodeURIComponent(String(val2))).join(",");
134
134
  } else if (style === "comma-delimited-object" && isTypescriptObject(val)) {
135
- encoded = Object.entries(val).map(([key, val2]) => [key, this.serializer.serialize(val2)]).filter(([, val2]) => val2 !== void 0 && val2 !== null).map(([key, val2]) => `${encodeURIComponent(String(key))},${encodeURIComponent(String(val2))}`).join(",");
135
+ encoded = Object.entries(val).map(([key, val2]) => [key, this.serializer.serialize(val2)]).filter(([, val2]) => val2 !== void 0 && val2 !== null).map(([key, val2]) => `${safeEncodeURIComponent(String(key))},${safeEncodeURIComponent(String(val2))}`).join(",");
136
136
  } else {
137
137
  const serialized = this.serializer.serialize(val);
138
138
  if (serialized !== void 0 && serialized !== null) {
139
139
  if (param.allowsSlash) {
140
- encoded = String(serialized).split("/").map(encodeURIComponent).join("/");
140
+ encoded = String(serialized).split("/").map(safeEncodeURIComponent).join("/");
141
141
  } else {
142
- encoded = encodeURIComponent(String(serialized));
142
+ encoded = safeEncodeURIComponent(String(serialized));
143
143
  }
144
144
  }
145
145
  }
146
146
  if (!encoded) {
147
- throw new OpenAPILinkCodecError(`Path param "${param.parameterName}" cannot be empty in call to procedure (${path.join(".")}).`);
147
+ throw new TypeError(`Path param "${param.parameterName}" cannot be empty in call to procedure (${path.join(".")}).`);
148
+ }
149
+ if (DOT_SEGMENT_REGEX.test(encoded)) {
150
+ throw new TypeError(`Path param "${param.parameterName}" cannot contain "." or ".." segments in call to procedure (${path.join(".")}).`);
148
151
  }
149
152
  return encoded;
150
153
  }
@@ -243,22 +246,17 @@ class OpenAPILinkCodec {
243
246
  return query || void 0;
244
247
  }
245
248
  async decodeResponse(response, path, _options) {
246
- const isOk = response.status >= 200 && response.status < 400;
249
+ const isOk = response.status < 400;
247
250
  const procedure = await this.resolveProcedure(path);
248
251
  const meta = getOpenAPIMeta(procedure);
252
+ const body = await response.resolveBody(meta?.responseBodyHint);
249
253
  const deserialized = await (async () => {
250
- let isBodyOk = false;
251
254
  try {
252
- const body = await response.resolveBody(meta?.responseBodyHint);
253
- isBodyOk = true;
254
255
  return this.serializer.deserialize(body);
255
256
  } catch (error) {
256
- if (!isBodyOk) {
257
- throw new Error("Cannot parse response body, please check the response body and content-type.", {
258
- cause: error
259
- });
260
- }
261
- throw new Error("Invalid OpenAPI response format.", {
257
+ throw createORPCErrorFromMalformedResponse({
258
+ message: "Invalid OpenAPI response format.",
259
+ response: { status: response.status, headers: response.headers, body },
262
260
  cause: error
263
261
  });
264
262
  }
@@ -273,9 +271,7 @@ class OpenAPILinkCodec {
273
271
  }
274
272
  return {
275
273
  kind: "error",
276
- error: new ORPCError("MALFORMED_ORPC_ERROR_RESPONSE", {
277
- data: { headers: response.headers, status: response.status, body: deserialized }
278
- })
274
+ error: createORPCErrorFromMalformedResponse({ response: { headers: response.headers, status: response.status, body } })
279
275
  };
280
276
  }
281
277
  const outputStructure = meta?.outputStructure ?? DEFAULT_OPENAPI_OUTPUT_STRUCTURE;
@@ -291,7 +287,7 @@ class OpenAPILinkCodec {
291
287
  async resolveProcedure(path) {
292
288
  const { default: maybeProcedure } = await unlazy(getRouterContract(this.router, path));
293
289
  if (!(maybeProcedure instanceof ProcedureContract)) {
294
- throw new OpenAPILinkCodecError(`Expected a procedure or contract at path (${path.join(".")})`);
290
+ throw new TypeError(`Expected a procedure or contract at path (${path.join(".")})`);
295
291
  }
296
292
  return maybeProcedure;
297
293
  }
@@ -324,7 +320,7 @@ function isValidDetailedInput(input) {
324
320
  if (input.query !== void 0 && !isTypescriptObject(input.query)) {
325
321
  return false;
326
322
  }
327
- if (input.headers !== void 0 && !isStandardHeaders(input.headers)) {
323
+ if (input.headers !== void 0 && !isTypescriptObject(input.headers)) {
328
324
  return false;
329
325
  }
330
326
  return true;
@@ -347,7 +343,7 @@ function encodeDelimitedArray(serializedValues, encodedDelimiter) {
347
343
  return strings.map(encodeURLSearchParamComponent).join(encodedDelimiter);
348
344
  }
349
345
  function encodeDelimitedObject(entries, encodedDelimiter) {
350
- const strings = entries.filter(([v]) => v !== null && v !== void 0).map(([k, v]) => [k, String(v)]);
346
+ const strings = entries.filter(([, v]) => v !== null && v !== void 0).map(([k, v]) => [k, String(v)]);
351
347
  if (!strings.length) {
352
348
  return void 0;
353
349
  }
@@ -356,4 +352,4 @@ function encodeDelimitedObject(entries, encodedDelimiter) {
356
352
  ).join(encodedDelimiter);
357
353
  }
358
354
 
359
- export { OpenAPILinkCodec as O, OpenAPILinkCodecError as a };
355
+ export { OpenAPILinkCodec as O };
@@ -0,0 +1,123 @@
1
+ import { isPlainObject, NullProtoObj, getOwn, setOwn } from '@orpc/shared';
2
+
3
+ class BracketNotationSerializer {
4
+ maxExplicitDeserializingArrayIndex;
5
+ constructor(options = {}) {
6
+ this.maxExplicitDeserializingArrayIndex = options.maxExplicitDeserializingArrayIndex ?? 999;
7
+ }
8
+ serialize(data) {
9
+ const result = [];
10
+ this.internalSerialize(data, "", true, result);
11
+ return result;
12
+ }
13
+ internalSerialize(data, path, isRoot, result) {
14
+ if (Array.isArray(data)) {
15
+ data.forEach((item, i) => {
16
+ this.internalSerialize(item, isRoot ? i.toString() : `${path}[${i}]`, false, result);
17
+ });
18
+ } else if (isPlainObject(data)) {
19
+ for (const key of Object.keys(data)) {
20
+ this.internalSerialize(data[key], isRoot ? key : `${path}[${key}]`, false, result);
21
+ }
22
+ } else {
23
+ result.push([path, data]);
24
+ }
25
+ }
26
+ deserialize(serialized) {
27
+ const arrayPushStyles = /* @__PURE__ */ new WeakSet();
28
+ const root = new NullProtoObj();
29
+ for (const [path, value] of serialized) {
30
+ const segments = this.parsePath(path);
31
+ let currentRef = root;
32
+ let nextSegment = segments[0];
33
+ for (let i = 1; i < segments.length; i++) {
34
+ const segment = segments[i];
35
+ const isLast = i === segments.length - 1;
36
+ const existing = getOwn(currentRef, nextSegment);
37
+ let child = existing;
38
+ if (!Array.isArray(child) && !isPlainObject(child)) {
39
+ child = [];
40
+ }
41
+ if (Array.isArray(child)) {
42
+ const isPushStyle = arrayPushStyles.has(child);
43
+ const canStayArray = segment === "" ? isLast && (isPushStyle || child.length === 0) : internalIsValidArrayIndex(segment, this.maxExplicitDeserializingArrayIndex) && !(isLast && isPushStyle);
44
+ if (!canStayArray) {
45
+ arrayPushStyles.delete(child);
46
+ child = isPushStyle ? internalPushStyleArrayToObject(child) : internalArrayToObject(child);
47
+ }
48
+ }
49
+ if (child !== existing) {
50
+ setOwn(currentRef, nextSegment, child);
51
+ }
52
+ currentRef = child;
53
+ nextSegment = segment;
54
+ }
55
+ if (Array.isArray(currentRef) && nextSegment === "") {
56
+ arrayPushStyles.add(currentRef);
57
+ currentRef.push(value);
58
+ } else if (Object.hasOwn(currentRef, nextSegment)) {
59
+ const current = currentRef[nextSegment];
60
+ if (Array.isArray(current)) {
61
+ current.push(value);
62
+ } else {
63
+ setOwn(currentRef, nextSegment, [current, value]);
64
+ }
65
+ } else {
66
+ setOwn(currentRef, nextSegment, value);
67
+ }
68
+ }
69
+ return root;
70
+ }
71
+ stringifyPath(segments) {
72
+ if (segments.length === 0) {
73
+ return "";
74
+ }
75
+ let result = segments[0].toString();
76
+ for (let i = 1; i < segments.length; i++) {
77
+ result += `[${segments[i]}]`;
78
+ }
79
+ return result;
80
+ }
81
+ parsePath(path) {
82
+ const segments = [];
83
+ let inBrackets = false;
84
+ let currentSegment = "";
85
+ for (let i = 0; i < path.length; i++) {
86
+ const char = path[i];
87
+ const nextChar = path[i + 1];
88
+ if (inBrackets && char === "]" && (nextChar === void 0 || nextChar === "[")) {
89
+ if (nextChar === void 0) {
90
+ inBrackets = false;
91
+ }
92
+ segments.push(currentSegment);
93
+ currentSegment = "";
94
+ i++;
95
+ } else if (segments.length === 0 && char === "[") {
96
+ inBrackets = true;
97
+ segments.push(currentSegment);
98
+ currentSegment = "";
99
+ } else {
100
+ currentSegment += char;
101
+ }
102
+ }
103
+ return inBrackets || segments.length === 0 ? [path] : segments;
104
+ }
105
+ }
106
+ const INTEGER_PATTERN = /^0$|^[1-9]\d*$/;
107
+ function internalIsValidArrayIndex(value, maxIndex) {
108
+ return INTEGER_PATTERN.test(value) && Number(value) <= maxIndex;
109
+ }
110
+ function internalArrayToObject(array) {
111
+ const obj = new NullProtoObj();
112
+ array.forEach((item, i) => {
113
+ obj[i] = item;
114
+ });
115
+ return obj;
116
+ }
117
+ function internalPushStyleArrayToObject(array) {
118
+ const obj = new NullProtoObj();
119
+ obj[""] = array.length === 1 ? array[0] : array;
120
+ return obj;
121
+ }
122
+
123
+ export { BracketNotationSerializer as B };
@@ -1,10 +1,10 @@
1
1
  import { AnyORPCError } from '@orpc/client';
2
2
  import { AnyProcedure, AnyRouter, Context } from '@orpc/server';
3
- import { StandardHandlerCodec, StandardHandlerHandleOptions, StandardHandlerCodecResolvedProcedure } from '@orpc/server/standard';
4
- import { Value, Promisable } from '@orpc/shared';
5
- import { StandardLazyRequest, StandardResponse } from '@standardserver/core';
3
+ import { StandardHandlerCodec, ResolvedStandardHandlerHandleOptions, StandardHandlerCodecResolvedProcedure } from '@orpc/server/standard';
4
+ import { Value, Public, Promisable } from '@orpc/shared';
5
+ import { StandardLazyRequest, StandardResponse } from '@standard-server/core';
6
6
  import { AnyProcedureContract } from '@orpc/contract';
7
- import { O as OpenAPISerializer } from './openapi.C7m7NAmH.js';
7
+ import { O as OpenAPISerializer } from './openapi.C2LayTNZ.js';
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
 
@@ -35,11 +37,12 @@ interface OpenAPIHandlerCodecCoreOptions<_T extends Context> {
35
37
  /**
36
38
  * Override the default OpenAPI serializer.
37
39
  */
38
- serializer?: Pick<OpenAPISerializer, keyof OpenAPISerializer>;
40
+ serializer?: Public<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
  /**
@@ -75,7 +78,7 @@ interface OpenAPIHandlerCodecOptions<T extends Context> extends OpenAPIHandlerCo
75
78
  declare class OpenAPIHandlerCodec<T extends Context> extends OpenAPIHandlerCodecCore<T> implements StandardHandlerCodec<T> {
76
79
  private readonly matcher;
77
80
  constructor(router: AnyRouter, options?: OpenAPIHandlerCodecOptions<T>);
78
- resolveProcedure(request: StandardLazyRequest, options: StandardHandlerHandleOptions<T>): Promise<StandardHandlerCodecResolvedProcedure | undefined>;
81
+ resolveProcedure(request: StandardLazyRequest, options: ResolvedStandardHandlerHandleOptions<T>): Promise<StandardHandlerCodecResolvedProcedure | undefined>;
79
82
  }
80
83
 
81
84
  export { OpenAPIHandlerCodec as a, OpenAPIHandlerCodecCore as b, OpenAPIMatcher as d };
@@ -1,10 +1,10 @@
1
1
  import { AnyORPCError } from '@orpc/client';
2
2
  import { AnyProcedure, AnyRouter, Context } from '@orpc/server';
3
- import { StandardHandlerCodec, StandardHandlerHandleOptions, StandardHandlerCodecResolvedProcedure } from '@orpc/server/standard';
4
- import { Value, Promisable } from '@orpc/shared';
5
- import { StandardLazyRequest, StandardResponse } from '@standardserver/core';
3
+ import { StandardHandlerCodec, ResolvedStandardHandlerHandleOptions, StandardHandlerCodecResolvedProcedure } from '@orpc/server/standard';
4
+ import { Value, Public, Promisable } from '@orpc/shared';
5
+ import { StandardLazyRequest, StandardResponse } from '@standard-server/core';
6
6
  import { AnyProcedureContract } from '@orpc/contract';
7
- import { O as OpenAPISerializer } from './openapi.C7m7NAmH.mjs';
7
+ import { O as OpenAPISerializer } from './openapi.C2LayTNZ.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
 
@@ -35,11 +37,12 @@ interface OpenAPIHandlerCodecCoreOptions<_T extends Context> {
35
37
  /**
36
38
  * Override the default OpenAPI serializer.
37
39
  */
38
- serializer?: Pick<OpenAPISerializer, keyof OpenAPISerializer>;
40
+ serializer?: Public<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
  /**
@@ -75,7 +78,7 @@ interface OpenAPIHandlerCodecOptions<T extends Context> extends OpenAPIHandlerCo
75
78
  declare class OpenAPIHandlerCodec<T extends Context> extends OpenAPIHandlerCodecCore<T> implements StandardHandlerCodec<T> {
76
79
  private readonly matcher;
77
80
  constructor(router: AnyRouter, options?: OpenAPIHandlerCodecOptions<T>);
78
- resolveProcedure(request: StandardLazyRequest, options: StandardHandlerHandleOptions<T>): Promise<StandardHandlerCodecResolvedProcedure | undefined>;
81
+ resolveProcedure(request: StandardLazyRequest, options: ResolvedStandardHandlerHandleOptions<T>): Promise<StandardHandlerCodecResolvedProcedure | undefined>;
79
82
  }
80
83
 
81
84
  export { OpenAPIHandlerCodec as a, OpenAPIHandlerCodecCore as b, OpenAPIMatcher as d };