@orpc/openapi 1.14.9 → 1.14.11

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 (48) hide show
  1. package/README.md +52 -71
  2. package/dist/adapters/fetch/index.d.mts +20 -16
  3. package/dist/adapters/fetch/index.d.ts +20 -16
  4. package/dist/adapters/fetch/index.mjs +24 -8
  5. package/dist/adapters/node/index.d.mts +8 -13
  6. package/dist/adapters/node/index.d.ts +8 -13
  7. package/dist/adapters/node/index.mjs +10 -7
  8. package/dist/adapters/standard/index.d.mts +46 -16
  9. package/dist/adapters/standard/index.d.ts +46 -16
  10. package/dist/adapters/standard/index.mjs +9 -6
  11. package/dist/extensions/route.d.mts +43 -0
  12. package/dist/extensions/route.d.ts +43 -0
  13. package/dist/extensions/route.mjs +14 -0
  14. package/dist/helpers/index.d.mts +51 -0
  15. package/dist/helpers/index.d.ts +51 -0
  16. package/dist/helpers/index.mjs +39 -0
  17. package/dist/index.d.mts +117 -105
  18. package/dist/index.d.ts +117 -105
  19. package/dist/index.mjs +850 -32
  20. package/dist/plugins/index.d.mts +55 -51
  21. package/dist/plugins/index.d.ts +55 -51
  22. package/dist/plugins/index.mjs +147 -142
  23. package/dist/shared/openapi.B6hEbRyF.d.ts +83 -0
  24. package/dist/shared/openapi.B9PQzqBn.mjs +49 -0
  25. package/dist/shared/openapi.BOOA-bde.d.mts +142 -0
  26. package/dist/shared/openapi.BOOA-bde.d.ts +142 -0
  27. package/dist/shared/openapi.BafbB3uM.d.mts +83 -0
  28. package/dist/shared/openapi.Bt87OzTt.mjs +131 -0
  29. package/dist/shared/openapi.ByT4oUeY.d.mts +18 -0
  30. package/dist/shared/openapi.ByT4oUeY.d.ts +18 -0
  31. package/dist/shared/openapi.C-p_Q2lb.mjs +359 -0
  32. package/dist/shared/openapi.CVgUshDP.mjs +318 -0
  33. package/dist/shared/openapi.DNNo0V-l.d.ts +313 -0
  34. package/dist/shared/openapi.hg_rhZ4x.d.mts +313 -0
  35. package/dist/shared/openapi.zZH_UksW.mjs +278 -0
  36. package/package.json +46 -24
  37. package/dist/adapters/aws-lambda/index.d.mts +0 -20
  38. package/dist/adapters/aws-lambda/index.d.ts +0 -20
  39. package/dist/adapters/aws-lambda/index.mjs +0 -18
  40. package/dist/adapters/fastify/index.d.mts +0 -23
  41. package/dist/adapters/fastify/index.d.ts +0 -23
  42. package/dist/adapters/fastify/index.mjs +0 -18
  43. package/dist/shared/openapi.BB-W-NKv.mjs +0 -204
  44. package/dist/shared/openapi.BGy4N6eR.d.mts +0 -120
  45. package/dist/shared/openapi.BGy4N6eR.d.ts +0 -120
  46. package/dist/shared/openapi.BwdtJjDu.mjs +0 -878
  47. package/dist/shared/openapi.DwaweYRb.d.mts +0 -54
  48. package/dist/shared/openapi.DwaweYRb.d.ts +0 -54
@@ -0,0 +1,359 @@
1
+ import { isORPCErrorJson, createORPCErrorFromJson, ORPCError } from '@orpc/client';
2
+ import { getRouterContract, ProcedureContract } from '@orpc/contract';
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, i as isBodylessMethod, b as DEFAULT_OPENAPI_OUTPUT_STRUCTURE } from './openapi.zZH_UksW.mjs';
8
+ import { g as getOpenAPIMeta } from './openapi.B9PQzqBn.mjs';
9
+
10
+ class OpenAPILinkCodecError extends TypeError {
11
+ }
12
+ const END_SLASH_REGEX = /\/$/;
13
+ class OpenAPILinkCodec {
14
+ constructor(router, options = {}) {
15
+ this.router = router;
16
+ this.baseUrl = options.url ?? "/";
17
+ this.headers = options.headers ?? {};
18
+ this.serializer = options.serializer ?? new OpenAPISerializer();
19
+ this.customErrorResponseBodyDecoder = options.customErrorResponseBodyDecoder;
20
+ }
21
+ baseUrl;
22
+ headers;
23
+ serializer;
24
+ customErrorResponseBodyDecoder;
25
+ async encodeInput(input, path, options) {
26
+ let headers = toResolvedStandardHeaders(await value(this.headers, options, path, input));
27
+ if (options.lastEventId !== void 0) {
28
+ headers = mergeStandardHeaders(headers, { "last-event-id": options.lastEventId });
29
+ }
30
+ const baseUrl = await value(this.baseUrl, options, path, input);
31
+ const procedure = await this.resolveProcedure(path);
32
+ const meta = getOpenAPIMeta(procedure);
33
+ const method = meta?.method ?? DEFAULT_OPENAPI_METHOD;
34
+ const inputStructure = meta?.inputStructure ?? DEFAULT_OPENAPI_INPUT_STRUCTURE;
35
+ let pathname = meta?.path ?? pathToHttpPath(path);
36
+ if (meta?.prefix) {
37
+ pathname = mergeHttpPath(meta.prefix, pathname);
38
+ }
39
+ const [basePathname, baseSearch, baseHash] = parseStandardUrl(baseUrl);
40
+ const dynamicParams = getDynamicPathParams(pathname);
41
+ if (inputStructure === "compact") {
42
+ let data = input;
43
+ if (dynamicParams?.length) {
44
+ if (!isTypescriptObject(input)) {
45
+ throw new OpenAPILinkCodecError(
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
+ );
48
+ }
49
+ const remaining = { ...input };
50
+ for (let i = dynamicParams.length - 1; i >= 0; i--) {
51
+ const param = dynamicParams[i];
52
+ const encoded = this.encodePathParam(input[param.parameterName], param, meta?.paramsStyles?.[param.parameterName], path);
53
+ pathname = `${pathname.slice(0, param.startIndex)}${encoded}${pathname.slice(param.startIndex + param.segment.length)}`;
54
+ delete remaining[param.parameterName];
55
+ }
56
+ data = Object.keys(remaining).length > 0 ? remaining : void 0;
57
+ }
58
+ pathname = `${basePathname.replace(END_SLASH_REGEX, "")}${pathname}`;
59
+ if (isBodylessMethod(method)) {
60
+ const queryString2 = this.serializeQueryString(data, meta?.queryStyles);
61
+ const search2 = combineSearch(baseSearch, queryString2);
62
+ const url3 = `${pathname}${search2 ?? ""}${baseHash ?? ""}`;
63
+ return {
64
+ body: void 0,
65
+ method,
66
+ headers,
67
+ url: url3,
68
+ signal: options.signal
69
+ };
70
+ }
71
+ const url2 = `${pathname}${baseSearch ?? ""}${baseHash ?? ""}`;
72
+ return {
73
+ url: url2,
74
+ method,
75
+ headers,
76
+ body: this.serializer.serialize(data),
77
+ signal: options.signal
78
+ };
79
+ }
80
+ if (!isValidDetailedInput(input)) {
81
+ throw new OpenAPILinkCodecError(`
82
+ Invalid "detailed" input structure in call to procedure (${path.join(".")}):
83
+ \u2022 Expected an object or undefined with optional properties:
84
+ - params (object, required when the path has dynamic params)
85
+ - query (object)
86
+ - headers (Record<string, string | string[] | undefined>)
87
+ - body (any)
88
+
89
+ Actual value:
90
+ ${stringifyJSON(input)}
91
+ `);
92
+ }
93
+ if (dynamicParams?.length) {
94
+ if (!input?.params) {
95
+ throw new OpenAPILinkCodecError(
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
+ );
98
+ }
99
+ for (let i = dynamicParams.length - 1; i >= 0; i--) {
100
+ const param = dynamicParams[i];
101
+ const val = input.params[param.parameterName];
102
+ const encoded = this.encodePathParam(val, param, meta?.paramsStyles?.[param.parameterName], path);
103
+ pathname = `${pathname.slice(0, param.startIndex)}${encoded}${pathname.slice(param.startIndex + param.segment.length)}`;
104
+ }
105
+ }
106
+ if (input?.headers) {
107
+ headers = mergeStandardHeaders(headers, input.headers);
108
+ }
109
+ pathname = `${basePathname.replace(END_SLASH_REGEX, "")}${pathname}`;
110
+ const queryString = this.serializeQueryString(input?.query, meta?.queryStyles);
111
+ const search = combineSearch(baseSearch, queryString);
112
+ const url = `${pathname}${search ?? ""}${baseHash ?? ""}`;
113
+ if (isBodylessMethod(method)) {
114
+ return {
115
+ body: void 0,
116
+ method,
117
+ headers,
118
+ url,
119
+ signal: options.signal
120
+ };
121
+ }
122
+ return {
123
+ url,
124
+ method,
125
+ headers,
126
+ body: this.serializer.serialize(input?.body),
127
+ signal: options.signal
128
+ };
129
+ }
130
+ encodePathParam(val, param, style, path) {
131
+ let encoded;
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(",");
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(",");
136
+ } else {
137
+ const serialized = this.serializer.serialize(val);
138
+ if (serialized !== void 0 && serialized !== null) {
139
+ if (param.allowsSlash) {
140
+ encoded = String(serialized).split("/").map(encodeURIComponent).join("/");
141
+ } else {
142
+ encoded = encodeURIComponent(String(serialized));
143
+ }
144
+ }
145
+ }
146
+ if (!encoded) {
147
+ throw new OpenAPILinkCodecError(`Path param "${param.parameterName}" cannot be empty in call to procedure (${path.join(".")}).`);
148
+ }
149
+ return encoded;
150
+ }
151
+ serializeQueryString(data, queryStyles) {
152
+ if (!queryStyles || !isTypescriptObject(data)) {
153
+ return toURLSearchParams(
154
+ this.serializer.serialize(data, { asFormData: true })
155
+ ).toString();
156
+ }
157
+ const remaining = { ...data };
158
+ let query = "";
159
+ Object.entries(queryStyles).forEach(([key, style]) => {
160
+ if (style === void 0) {
161
+ return;
162
+ }
163
+ const value2 = remaining[key];
164
+ delete remaining[key];
165
+ if (style === "primitive") {
166
+ const serialized = this.serializer.serialize(value2);
167
+ if (serialized !== void 0 && serialized !== null) {
168
+ query += `&${encodeURLSearchParamComponent(key)}=${encodeURLSearchParamComponent(String(serialized))}`;
169
+ }
170
+ } else if (style === "array" && Array.isArray(value2)) {
171
+ const encodedKey = encodeURLSearchParamComponent(key);
172
+ value2.forEach((v) => {
173
+ const s = this.serializer.serialize(v);
174
+ if (s !== void 0 && s !== null) {
175
+ query += `&${encodedKey}=${encodeURLSearchParamComponent(String(s))}`;
176
+ }
177
+ });
178
+ } else if (style === "json") {
179
+ const serialized = this.serializer.serialize(value2);
180
+ if (serialized !== void 0) {
181
+ query += `&${encodeURLSearchParamComponent(key)}=${encodeURLSearchParamComponent(stringifyJSON(serialized))}`;
182
+ }
183
+ } else if (style === "comma-delimited-array" && Array.isArray(value2)) {
184
+ const encodedValue = encodeDelimitedArray(
185
+ value2.map((v) => this.serializer.serialize(v)),
186
+ ","
187
+ );
188
+ if (encodedValue !== void 0) {
189
+ query += `&${encodeURLSearchParamComponent(key)}=${encodedValue}`;
190
+ }
191
+ } else if (style === "comma-delimited-object" && isTypescriptObject(value2)) {
192
+ const encodedValue = encodeDelimitedObject(
193
+ Object.entries(value2).map(([key2, value3]) => [key2, this.serializer.serialize(value3)]),
194
+ ","
195
+ );
196
+ if (encodedValue !== void 0) {
197
+ query += `&${encodeURLSearchParamComponent(key)}=${encodedValue}`;
198
+ }
199
+ } else if (style === "pipe-delimited-array" && Array.isArray(value2)) {
200
+ const encodedValue = encodeDelimitedArray(
201
+ value2.map((v) => this.serializer.serialize(v)),
202
+ "%7C"
203
+ );
204
+ if (encodedValue !== void 0) {
205
+ query += `&${encodeURLSearchParamComponent(key)}=${encodedValue}`;
206
+ }
207
+ } else if (style === "pipe-delimited-object" && isTypescriptObject(value2)) {
208
+ const encodedValue = encodeDelimitedObject(
209
+ Object.entries(value2).map(([key2, value3]) => [key2, this.serializer.serialize(value3)]),
210
+ "%7C"
211
+ );
212
+ if (encodedValue !== void 0) {
213
+ query += `&${encodeURLSearchParamComponent(key)}=${encodedValue}`;
214
+ }
215
+ } else if (style === "space-delimited-array" && Array.isArray(value2)) {
216
+ const encodedValue = encodeDelimitedArray(
217
+ value2.map((v) => this.serializer.serialize(v)),
218
+ "%20"
219
+ );
220
+ if (encodedValue !== void 0) {
221
+ query += `&${encodeURLSearchParamComponent(key)}=${encodedValue}`;
222
+ }
223
+ } else if (style === "space-delimited-object" && isTypescriptObject(value2)) {
224
+ const encodedValue = encodeDelimitedObject(
225
+ Object.entries(value2).map(([key2, value3]) => [key2, this.serializer.serialize(value3)]),
226
+ "%20"
227
+ );
228
+ if (encodedValue !== void 0) {
229
+ query += `&${encodeURLSearchParamComponent(key)}=${encodedValue}`;
230
+ }
231
+ } else {
232
+ const serialized = this.serializer.serialize(value2);
233
+ if (serialized !== void 0 && serialized !== null) {
234
+ query += `&${encodeURLSearchParamComponent(key)}=${encodeURLSearchParamComponent(String(serialized))}`;
235
+ }
236
+ }
237
+ });
238
+ const form = this.serializer.serialize(remaining, { asFormData: true });
239
+ query = `${toURLSearchParams(form).toString()}${query}`;
240
+ if (query.startsWith("&")) {
241
+ query = query.slice(1);
242
+ }
243
+ return query || void 0;
244
+ }
245
+ async decodeResponse(response, path, _options) {
246
+ const isOk = response.status < 400;
247
+ const procedure = await this.resolveProcedure(path);
248
+ const meta = getOpenAPIMeta(procedure);
249
+ const deserialized = await (async () => {
250
+ let isBodyOk = false;
251
+ try {
252
+ const body = await response.resolveBody(meta?.responseBodyHint);
253
+ isBodyOk = true;
254
+ return this.serializer.deserialize(body);
255
+ } 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.", {
262
+ cause: error
263
+ });
264
+ }
265
+ })();
266
+ if (!isOk) {
267
+ const customError = this.customErrorResponseBodyDecoder?.(deserialized, response);
268
+ if (customError !== void 0 && customError !== null) {
269
+ return { kind: "error", error: customError };
270
+ }
271
+ if (isORPCErrorJson(deserialized)) {
272
+ return { kind: "error", error: createORPCErrorFromJson(deserialized) };
273
+ }
274
+ return {
275
+ kind: "error",
276
+ error: new ORPCError("MALFORMED_ORPC_ERROR_RESPONSE", {
277
+ data: { headers: response.headers, status: response.status, body: deserialized }
278
+ })
279
+ };
280
+ }
281
+ const outputStructure = meta?.outputStructure ?? DEFAULT_OPENAPI_OUTPUT_STRUCTURE;
282
+ return outputStructure === "compact" ? { kind: "output", output: deserialized } : {
283
+ kind: "output",
284
+ output: {
285
+ status: response.status,
286
+ headers: response.headers,
287
+ body: deserialized
288
+ }
289
+ };
290
+ }
291
+ async resolveProcedure(path) {
292
+ const { default: maybeProcedure } = await unlazy(getRouterContract(this.router, path));
293
+ if (!(maybeProcedure instanceof ProcedureContract)) {
294
+ throw new OpenAPILinkCodecError(`Expected a procedure or contract at path (${path.join(".")})`);
295
+ }
296
+ return maybeProcedure;
297
+ }
298
+ }
299
+ function combineSearch(baseSearch, additionalSearch) {
300
+ if (!baseSearch && !additionalSearch) {
301
+ return void 0;
302
+ }
303
+ if (!additionalSearch) {
304
+ return baseSearch;
305
+ }
306
+ if (!baseSearch) {
307
+ return `?${additionalSearch}`;
308
+ }
309
+ return `${baseSearch}&${additionalSearch}`;
310
+ }
311
+ function toResolvedStandardHeaders(headers) {
312
+ if (typeof headers.forEach === "function") {
313
+ return toStandardHeaders(headers);
314
+ }
315
+ return headers;
316
+ }
317
+ function isValidDetailedInput(input) {
318
+ if (!isTypescriptObject(input)) {
319
+ return input === void 0;
320
+ }
321
+ if (input.params !== void 0 && !isTypescriptObject(input.params)) {
322
+ return false;
323
+ }
324
+ if (input.query !== void 0 && !isTypescriptObject(input.query)) {
325
+ return false;
326
+ }
327
+ if (input.headers !== void 0 && !isStandardHeaders(input.headers)) {
328
+ return false;
329
+ }
330
+ return true;
331
+ }
332
+ function encodeURLSearchParamComponent(value2) {
333
+ return new URLSearchParams({ "": value2 }).toString().slice(1);
334
+ }
335
+ function toURLSearchParams(form) {
336
+ const params = new URLSearchParams();
337
+ for (const [key, value2] of form) {
338
+ params.append(key, String(value2));
339
+ }
340
+ return params;
341
+ }
342
+ function encodeDelimitedArray(serializedValues, encodedDelimiter) {
343
+ const strings = serializedValues.filter((v) => v !== null && v !== void 0).map(String);
344
+ if (!strings.length) {
345
+ return void 0;
346
+ }
347
+ return strings.map(encodeURLSearchParamComponent).join(encodedDelimiter);
348
+ }
349
+ function encodeDelimitedObject(entries, encodedDelimiter) {
350
+ const strings = entries.filter(([v]) => v !== null && v !== void 0).map(([k, v]) => [k, String(v)]);
351
+ if (!strings.length) {
352
+ return void 0;
353
+ }
354
+ return strings.map(
355
+ ([key, value2]) => `${encodeURLSearchParamComponent(key)}${encodedDelimiter}${encodeURLSearchParamComponent(value2)}`
356
+ ).join(encodedDelimiter);
357
+ }
358
+
359
+ export { OpenAPILinkCodec as O, OpenAPILinkCodecError as a };
@@ -0,0 +1,318 @@
1
+ import { COMMON_ERROR_STATUS_MAP } from '@orpc/client';
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, i as isBodylessMethod, b as DEFAULT_OPENAPI_OUTPUT_STRUCTURE } from './openapi.zZH_UksW.mjs';
6
+ import { g as getOpenAPIMeta } from './openapi.B9PQzqBn.mjs';
7
+ import { createRouter, addRoute, findRoute, routeToRegExp } from 'rou3';
8
+
9
+ class OpenAPIMatcher {
10
+ filter;
11
+ rootRouter;
12
+ tree = createRouter();
13
+ pendingLazyRouters = [];
14
+ constructor(router, options = {}) {
15
+ this.filter = options.filter ?? true;
16
+ this.rootRouter = router;
17
+ this.index(router);
18
+ }
19
+ index(router, path = []) {
20
+ const lazyResults = walkProcedureContractsSync(router, (contract, path2) => {
21
+ if (!value(this.filter, contract, path2)) {
22
+ return;
23
+ }
24
+ const meta = getOpenAPIMeta(contract);
25
+ const method = meta?.method ?? DEFAULT_OPENAPI_METHOD;
26
+ const postHttpPath = meta?.path ?? pathToHttpPath(path2);
27
+ const openapiPath = meta?.prefix ? mergeHttpPath(meta.prefix, postHttpPath) : postHttpPath;
28
+ const rou3Path = toRou3Pattern(openapiPath);
29
+ addRoute(this.tree, method, rou3Path, {
30
+ path: path2,
31
+ contract,
32
+ procedure: contract instanceof Procedure ? contract : void 0
33
+ });
34
+ }, path);
35
+ this.pendingLazyRouters.push(...lazyResults.map((result) => {
36
+ const prefix = getOpenAPIMeta(result.router)?.prefix;
37
+ return {
38
+ ...result,
39
+ matcher: prefix ? toRou3PrefixMatcher(prefix) : void 0
40
+ };
41
+ }));
42
+ }
43
+ async match(method, pathname, prefix) {
44
+ if (prefix) {
45
+ if (!pathname.startsWith(prefix)) {
46
+ return void 0;
47
+ }
48
+ const charAfterPrefix = pathname[prefix.length];
49
+ if (charAfterPrefix === "/") {
50
+ pathname = pathname.slice(prefix.length);
51
+ } else if (charAfterPrefix === void 0) {
52
+ pathname = "/";
53
+ } else if (prefix[prefix.length - 1] === "/") {
54
+ pathname = pathname.slice(prefix.length - 1);
55
+ } else {
56
+ return void 0;
57
+ }
58
+ }
59
+ const result = await this.matchPathname(method, pathname);
60
+ if (!result && pathname.includes("%")) {
61
+ return this.matchPathname(method, normalizeHttpPath(pathname));
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) {
69
+ return void 0;
70
+ }
71
+ const procedure = await this.resolveProcedure(match.data);
72
+ return {
73
+ path: match.data.path,
74
+ procedure,
75
+ params: match.params ? decodeParams(match.params) : void 0
76
+ };
77
+ }
78
+ async resolvePendingLazyRouters(pathname) {
79
+ if (!this.pendingLazyRouters.length) {
80
+ return;
81
+ }
82
+ const stillPending = [];
83
+ 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);
89
+ }
90
+ }
91
+ this.pendingLazyRouters = stillPending;
92
+ }
93
+ async resolveProcedure(entry) {
94
+ if (entry.procedure) {
95
+ return entry.procedure;
96
+ }
97
+ const { default: maybeProcedure } = await unlazy(getRouter(this.rootRouter, entry.path));
98
+ if (!(maybeProcedure instanceof Procedure)) {
99
+ throw new TypeError(
100
+ `[Contract-First] Missing or invalid implementation for procedure at path: "${entry.path.join(".")}". Ensure the procedure is correctly implemented and matches its contract.`
101
+ );
102
+ }
103
+ entry.procedure = createContractProcedure(maybeProcedure, entry.contract);
104
+ return entry.procedure;
105
+ }
106
+ }
107
+ function toRou3Pattern(path) {
108
+ const params = getDynamicPathParams(path);
109
+ if (!params?.length) {
110
+ return path;
111
+ }
112
+ for (let i = params.length - 1; i >= 0; i--) {
113
+ const param = params[i];
114
+ const pattern = param.allowsSlash ? `**:${param.parameterName}` : `:${param.parameterName}`;
115
+ path = path.slice(0, param.startIndex) + pattern + path.slice(param.startIndex + param.segment.length);
116
+ }
117
+ return path;
118
+ }
119
+ function toRou3PrefixMatcher(path) {
120
+ const pattern = toRou3Pattern(path);
121
+ return routeToRegExp(pattern === "/" ? "/**" : `${pattern}/**`);
122
+ }
123
+ function decodeParams(params) {
124
+ return Object.fromEntries(Object.entries(params).map(([key, val]) => [key, tryDecodeURIComponent(val)]));
125
+ }
126
+
127
+ class OpenAPIHandlerCodecCore {
128
+ serializer;
129
+ errorStatusMap;
130
+ customErrorResponseBodySerializer;
131
+ constructor(options = {}) {
132
+ this.serializer = options.serializer ?? new OpenAPISerializer();
133
+ this.errorStatusMap = options.errorStatusMap ?? COMMON_ERROR_STATUS_MAP;
134
+ this.customErrorResponseBodySerializer = options.customErrorResponseBodyEncoder;
135
+ }
136
+ async decodeInput(matched, request) {
137
+ const [_, search] = parseStandardUrl(request.url);
138
+ const meta = getOpenAPIMeta(matched.procedure);
139
+ const inputStructure = meta?.inputStructure ?? DEFAULT_OPENAPI_INPUT_STRUCTURE;
140
+ const params = this.deserializeParams(matched.params, meta?.paramsStyles);
141
+ const query = this.deserializeQuery(search, meta?.queryStyles);
142
+ if (inputStructure === "compact") {
143
+ const data = isBodylessMethod(request.method) ? query : this.serializer.deserialize(await request.resolveBody(meta?.requestBodyHint));
144
+ if (data === void 0) {
145
+ return params;
146
+ }
147
+ if (!params || Object.keys(params).length < 1) {
148
+ return data;
149
+ }
150
+ if (isPlainObject(data)) {
151
+ return {
152
+ ...params,
153
+ ...data
154
+ };
155
+ }
156
+ return data;
157
+ }
158
+ return {
159
+ params,
160
+ query,
161
+ headers: request.headers,
162
+ body: this.serializer.deserialize(await request.resolveBody(meta?.requestBodyHint))
163
+ };
164
+ }
165
+ /**
166
+ * @throws {TypeError} If `outputStructure` is "detailed" and the output doesn't match the expected structure.
167
+ */
168
+ encodeOutput(output, procedure, path) {
169
+ const meta = getOpenAPIMeta(procedure);
170
+ const successStatus = meta?.successStatus ?? DEFAULT_SUCCESS_STATUS;
171
+ const outputStructure = meta?.outputStructure ?? DEFAULT_OPENAPI_OUTPUT_STRUCTURE;
172
+ if (outputStructure === "compact") {
173
+ return {
174
+ status: successStatus,
175
+ headers: {},
176
+ body: this.serializer.serialize(output)
177
+ };
178
+ }
179
+ if (!isValidDetailedOutput(output)) {
180
+ throw new TypeError(`
181
+ Invalid "detailed" output structure returned by procedure (${path.join(".")}):
182
+ \u2022 Expected an object with optional properties:
183
+ - status (number 200-399)
184
+ - headers (Record<string, string | string[] | undefined>)
185
+ - body (any)
186
+ \u2022 No extra keys allowed.
187
+
188
+ Actual value:
189
+ ${stringifyJSON(output)}
190
+ `);
191
+ }
192
+ return {
193
+ status: output.status ?? successStatus,
194
+ headers: output.headers ?? {},
195
+ body: this.serializer.serialize(output.body)
196
+ };
197
+ }
198
+ encodeError(error) {
199
+ const status = this.errorStatusMap[error.code] ?? DEFAULT_ERROR_STATUS;
200
+ return {
201
+ status,
202
+ headers: {},
203
+ body: this.serializer.serialize(this.customErrorResponseBodySerializer?.(error) ?? error.toJSON())
204
+ };
205
+ }
206
+ deserializeQuery(search, styles) {
207
+ const searchParams = new URLSearchParams(search);
208
+ const parsed = this.serializer.deserialize(searchParams);
209
+ if (!styles || !isPlainObject(parsed)) {
210
+ return parsed;
211
+ }
212
+ Object.entries(styles).forEach(([key, hint]) => {
213
+ if (hint === void 0) {
214
+ return;
215
+ }
216
+ const values = searchParams.getAll(key);
217
+ let parsedValue;
218
+ if (hint === "primitive") {
219
+ parsedValue = values.at(-1);
220
+ } else if (hint === "array") {
221
+ parsedValue = values;
222
+ } else if (hint === "comma-delimited-array") {
223
+ parsedValue = decodeDelimitedArray(values.at(-1), ",");
224
+ } else if (hint === "comma-delimited-object") {
225
+ parsedValue = decodeDelimitedObject(values.at(-1), ",");
226
+ } else if (hint === "space-delimited-array") {
227
+ parsedValue = decodeDelimitedArray(values.at(-1), " ");
228
+ } else if (hint === "space-delimited-object") {
229
+ parsedValue = decodeDelimitedObject(values.at(-1), " ");
230
+ } else if (hint === "pipe-delimited-array") {
231
+ parsedValue = decodeDelimitedArray(values.at(-1), "|");
232
+ } else if (hint === "pipe-delimited-object") {
233
+ parsedValue = decodeDelimitedObject(values.at(-1), "|");
234
+ } else {
235
+ const last = values.at(-1);
236
+ try {
237
+ parsedValue = parseEmptyableJSON(last);
238
+ } catch {
239
+ parsedValue = last;
240
+ }
241
+ }
242
+ parsed[key] = this.serializer.deserialize(parsedValue);
243
+ });
244
+ return parsed;
245
+ }
246
+ deserializeParams(params, styles) {
247
+ if (!params || !styles) {
248
+ return params;
249
+ }
250
+ const parsed = { ...params };
251
+ Object.entries(styles).forEach(([key, hint]) => {
252
+ if (hint === void 0 || hint === "primitive") {
253
+ return;
254
+ }
255
+ const value = params[key];
256
+ if (hint === "comma-delimited-array") {
257
+ parsed[key] = decodeDelimitedArray(value, ",");
258
+ } else {
259
+ parsed[key] = decodeDelimitedObject(value, ",");
260
+ }
261
+ });
262
+ return parsed;
263
+ }
264
+ }
265
+ class OpenAPIHandlerCodec extends OpenAPIHandlerCodecCore {
266
+ matcher;
267
+ constructor(router, options = {}) {
268
+ super(options);
269
+ this.matcher = new OpenAPIMatcher(router, options);
270
+ }
271
+ async resolveProcedure(request, options) {
272
+ const [pathname] = parseStandardUrl(request.url);
273
+ const matched = await this.matcher.match(request.method, pathname, options.prefix);
274
+ if (!matched) {
275
+ return void 0;
276
+ }
277
+ return {
278
+ procedure: matched.procedure,
279
+ path: matched.path,
280
+ decodeInput: () => this.decodeInput(matched, request)
281
+ };
282
+ }
283
+ }
284
+ function isValidDetailedOutput(output) {
285
+ if (!isTypescriptObject(output)) {
286
+ return false;
287
+ }
288
+ if (Object.keys(output).some((key) => key !== "status" && key !== "headers" && key !== "body")) {
289
+ return false;
290
+ }
291
+ if (output.status !== void 0 && (typeof output.status !== "number" || !Number.isInteger(output.status) || output.status < 200 || output.status > 399)) {
292
+ return false;
293
+ }
294
+ if (output.headers !== void 0 && !isStandardHeaders(output.headers)) {
295
+ return false;
296
+ }
297
+ return true;
298
+ }
299
+ function decodeDelimitedArray(value, delimiter) {
300
+ if (value === void 0) {
301
+ return void 0;
302
+ }
303
+ return value.split(delimiter);
304
+ }
305
+ function decodeDelimitedObject(value, delimiter) {
306
+ if (value === void 0) {
307
+ return void 0;
308
+ }
309
+ const obj = new NullProtoObj();
310
+ const parts = value.split(delimiter);
311
+ for (let i = 0; i < parts.length; i += 2) {
312
+ const key = parts[i];
313
+ obj[key] = parts[i + 1];
314
+ }
315
+ return obj;
316
+ }
317
+
318
+ export { OpenAPIHandlerCodec as O, OpenAPIHandlerCodecCore as a, OpenAPIMatcher as b };