@orpc/openapi 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/chunk-YXHH6XHB.js +642 -0
  2. package/dist/fetch.js +17 -565
  3. package/dist/index.js +229 -101
  4. package/dist/node.js +46 -0
  5. package/dist/src/{fetch → adapters/fetch}/index.d.ts +2 -2
  6. package/dist/src/adapters/fetch/input-structure-compact.d.ts +6 -0
  7. package/dist/src/{fetch/input-builder-full.d.ts → adapters/fetch/input-structure-detailed.d.ts} +3 -3
  8. package/dist/src/{fetch → adapters/fetch}/openapi-handler.d.ts +11 -8
  9. package/dist/src/{fetch → adapters/fetch}/openapi-payload-codec.d.ts +2 -2
  10. package/dist/src/adapters/node/index.d.ts +5 -0
  11. package/dist/src/adapters/node/openapi-handler-server.d.ts +7 -0
  12. package/dist/src/adapters/node/openapi-handler-serverless.d.ts +7 -0
  13. package/dist/src/adapters/node/openapi-handler.d.ts +12 -0
  14. package/dist/src/adapters/node/types.d.ts +2 -0
  15. package/dist/src/openapi-error.d.ts +3 -0
  16. package/dist/src/openapi-generator.d.ts +15 -6
  17. package/dist/src/openapi-input-structure-parser.d.ts +22 -0
  18. package/dist/src/openapi-output-structure-parser.d.ts +18 -0
  19. package/dist/src/openapi-parameters-builder.d.ts +3 -0
  20. package/package.json +12 -6
  21. package/dist/src/fetch/input-builder-simple.d.ts +0 -6
  22. /package/dist/src/{fetch → adapters/fetch}/bracket-notation.d.ts +0 -0
  23. /package/dist/src/{fetch → adapters/fetch}/openapi-handler-server.d.ts +0 -0
  24. /package/dist/src/{fetch → adapters/fetch}/openapi-handler-serverless.d.ts +0 -0
  25. /package/dist/src/{fetch → adapters/fetch}/openapi-procedure-matcher.d.ts +0 -0
  26. /package/dist/src/{fetch → adapters/fetch}/schema-coercer.d.ts +0 -0
package/dist/fetch.js CHANGED
@@ -1,567 +1,19 @@
1
1
  import {
2
- JSONSerializer,
3
- forEachContractProcedure,
4
- standardizeHTTPPath
5
- } from "./chunk-KNYXLM77.js";
6
-
7
- // src/fetch/bracket-notation.ts
8
- import { isPlainObject } from "@orpc/shared";
9
- function serialize(payload, parentKey = "") {
10
- if (!Array.isArray(payload) && !isPlainObject(payload))
11
- return [["", payload]];
12
- const result = [];
13
- function helper(value, path) {
14
- if (Array.isArray(value)) {
15
- value.forEach((item, index) => {
16
- helper(item, [...path, String(index)]);
17
- });
18
- } else if (isPlainObject(value)) {
19
- for (const [key, val] of Object.entries(value)) {
20
- helper(val, [...path, key]);
21
- }
22
- } else {
23
- result.push([stringifyPath(path), value]);
24
- }
25
- }
26
- helper(payload, parentKey ? [parentKey] : []);
27
- return result;
28
- }
29
- function deserialize(entities) {
30
- if (entities.length === 0) {
31
- return void 0;
32
- }
33
- const isRootArray = entities.every(([path]) => path === "");
34
- const result = isRootArray ? [] : {};
35
- const arrayPushPaths = /* @__PURE__ */ new Set();
36
- for (const [path, _] of entities) {
37
- const segments = parsePath(path);
38
- const base = segments.slice(0, -1).join(".");
39
- const last = segments[segments.length - 1];
40
- if (last === "") {
41
- arrayPushPaths.add(base);
42
- } else {
43
- arrayPushPaths.delete(base);
44
- }
45
- }
46
- function setValue(obj, segments, value, fullPath) {
47
- const [first, ...rest_] = segments;
48
- if (Array.isArray(obj) && first === "") {
49
- ;
50
- obj.push(value);
51
- return;
52
- }
53
- const objAsRecord = obj;
54
- if (rest_.length === 0) {
55
- objAsRecord[first] = value;
56
- return;
57
- }
58
- const rest = rest_;
59
- if (rest[0] === "") {
60
- const pathToCheck = segments.slice(0, -1).join(".");
61
- if (rest.length === 1 && arrayPushPaths.has(pathToCheck)) {
62
- if (!(first in objAsRecord)) {
63
- objAsRecord[first] = [];
64
- }
65
- if (Array.isArray(objAsRecord[first])) {
66
- ;
67
- objAsRecord[first].push(value);
68
- return;
69
- }
70
- }
71
- if (!(first in objAsRecord)) {
72
- objAsRecord[first] = {};
73
- }
74
- const target = objAsRecord[first];
75
- target[""] = value;
76
- return;
77
- }
78
- if (!(first in objAsRecord)) {
79
- objAsRecord[first] = {};
80
- }
81
- setValue(
82
- objAsRecord[first],
83
- rest,
84
- value,
85
- fullPath
86
- );
87
- }
88
- for (const [path, value] of entities) {
89
- const segments = parsePath(path);
90
- setValue(result, segments, value, path);
91
- }
92
- return result;
93
- }
94
- function escapeSegment(segment) {
95
- return segment.replace(/[\\[\]]/g, (match) => {
96
- switch (match) {
97
- case "\\":
98
- return "\\\\";
99
- case "[":
100
- return "\\[";
101
- case "]":
102
- return "\\]";
103
- default:
104
- return match;
105
- }
106
- });
107
- }
108
- function stringifyPath(path) {
109
- const [first, ...rest] = path;
110
- const firstSegment = escapeSegment(first);
111
- const base = first === "" ? "" : firstSegment;
112
- return rest.reduce(
113
- (result, segment) => `${result}[${escapeSegment(segment)}]`,
114
- base
115
- );
116
- }
117
- function parsePath(path) {
118
- if (path === "")
119
- return [""];
120
- const result = [];
121
- let currentSegment = "";
122
- let inBracket = false;
123
- let bracketContent = "";
124
- let backslashCount = 0;
125
- for (let i = 0; i < path.length; i++) {
126
- const char = path[i];
127
- if (char === "\\") {
128
- backslashCount++;
129
- continue;
130
- }
131
- if (backslashCount > 0) {
132
- const literalBackslashes = "\\".repeat(Math.floor(backslashCount / 2));
133
- if (char === "[" || char === "]") {
134
- if (backslashCount % 2 === 1) {
135
- if (inBracket) {
136
- bracketContent += literalBackslashes + char;
137
- } else {
138
- currentSegment += literalBackslashes + char;
139
- }
140
- } else {
141
- if (inBracket) {
142
- bracketContent += literalBackslashes;
143
- } else {
144
- currentSegment += literalBackslashes;
145
- }
146
- if (char === "[" && !inBracket) {
147
- if (currentSegment !== "" || result.length === 0) {
148
- result.push(currentSegment);
149
- }
150
- inBracket = true;
151
- bracketContent = "";
152
- currentSegment = "";
153
- } else if (char === "]" && inBracket) {
154
- result.push(bracketContent);
155
- inBracket = false;
156
- bracketContent = "";
157
- } else {
158
- if (inBracket) {
159
- bracketContent += char;
160
- } else {
161
- currentSegment += char;
162
- }
163
- }
164
- }
165
- } else {
166
- const allBackslashes = "\\".repeat(backslashCount);
167
- if (inBracket) {
168
- bracketContent += allBackslashes + char;
169
- } else {
170
- currentSegment += allBackslashes + char;
171
- }
172
- }
173
- backslashCount = 0;
174
- continue;
175
- }
176
- if (char === "[" && !inBracket) {
177
- if (currentSegment !== "" || result.length === 0) {
178
- result.push(currentSegment);
179
- }
180
- inBracket = true;
181
- bracketContent = "";
182
- currentSegment = "";
183
- continue;
184
- }
185
- if (char === "]" && inBracket) {
186
- result.push(bracketContent);
187
- inBracket = false;
188
- bracketContent = "";
189
- continue;
190
- }
191
- if (inBracket) {
192
- bracketContent += char;
193
- } else {
194
- currentSegment += char;
195
- }
196
- }
197
- if (backslashCount > 0) {
198
- const remainingBackslashes = "\\".repeat(backslashCount);
199
- if (inBracket) {
200
- bracketContent += remainingBackslashes;
201
- } else {
202
- currentSegment += remainingBackslashes;
203
- }
204
- }
205
- if (inBracket) {
206
- if (currentSegment !== "" || result.length === 0) {
207
- result.push(currentSegment);
208
- }
209
- result.push(`[${bracketContent}`);
210
- } else if (currentSegment !== "" || result.length === 0) {
211
- result.push(currentSegment);
212
- }
213
- return result;
214
- }
215
-
216
- // src/fetch/input-builder-full.ts
217
- var InputBuilderFull = class {
218
- build(params, query, headers, body) {
219
- return {
220
- params,
221
- query,
222
- headers,
223
- body
224
- };
225
- }
226
- };
227
-
228
- // src/fetch/input-builder-simple.ts
229
- import { isPlainObject as isPlainObject2 } from "@orpc/shared";
230
- var InputBuilderSimple = class {
231
- build(params, payload) {
232
- if (Object.keys(params).length === 0) {
233
- return payload;
234
- }
235
- if (!isPlainObject2(payload)) {
236
- return params;
237
- }
238
- return {
239
- ...params,
240
- ...payload
241
- };
242
- }
243
- };
244
-
245
- // src/fetch/openapi-handler.ts
246
- import { createProcedureClient, ORPCError as ORPCError2 } from "@orpc/server";
247
- import { executeWithHooks, ORPC_HANDLER_HEADER, trim } from "@orpc/shared";
248
-
249
- // src/fetch/openapi-payload-codec.ts
250
- import { ORPCError } from "@orpc/server";
251
- import { findDeepMatches } from "@orpc/shared";
252
- import cd from "content-disposition";
253
- import { safeParse } from "fast-content-type-parse";
254
- import wcmatch from "wildcard-match";
255
- var OpenAPIPayloadCodec = class {
256
- constructor(jsonSerializer) {
257
- this.jsonSerializer = jsonSerializer;
258
- }
259
- encode(payload, accept) {
260
- const typeMatchers = (accept?.split(",").map(safeParse) ?? [{ type: "*/*" }]).map(({ type }) => wcmatch(type));
261
- if (payload instanceof Blob) {
262
- const contentType = payload.type || "application/octet-stream";
263
- if (typeMatchers.some((isMatch) => isMatch(contentType))) {
264
- const headers = new Headers({
265
- "Content-Type": contentType
266
- });
267
- if (payload instanceof File && payload.name) {
268
- headers.append("Content-Disposition", cd(payload.name));
269
- }
270
- return {
271
- body: payload,
272
- headers
273
- };
274
- }
275
- }
276
- const handledPayload = this.jsonSerializer.serialize(payload);
277
- const hasBlobs = findDeepMatches((v) => v instanceof Blob, handledPayload).values.length > 0;
278
- const isExpectedMultipartFormData = typeMatchers.some(
279
- (isMatch) => isMatch("multipart/form-data")
280
- );
281
- if (hasBlobs && isExpectedMultipartFormData) {
282
- return this.encodeAsFormData(handledPayload);
283
- }
284
- if (typeMatchers.some((isMatch) => isMatch("application/json"))) {
285
- return this.encodeAsJSON(handledPayload);
286
- }
287
- if (typeMatchers.some(
288
- (isMatch) => isMatch("application/x-www-form-urlencoded")
289
- )) {
290
- return this.encodeAsURLSearchParams(handledPayload);
291
- }
292
- if (isExpectedMultipartFormData) {
293
- return this.encodeAsFormData(handledPayload);
294
- }
295
- throw new ORPCError({
296
- code: "NOT_ACCEPTABLE",
297
- message: `Unsupported content-type: ${accept}`
298
- });
299
- }
300
- encodeAsJSON(payload) {
301
- if (payload === void 0) {
302
- return {
303
- body: void 0,
304
- headers: new Headers({
305
- "content-type": "application/json"
306
- })
307
- };
308
- }
309
- return {
310
- body: JSON.stringify(payload),
311
- headers: new Headers({
312
- "content-type": "application/json"
313
- })
314
- };
315
- }
316
- encodeAsFormData(payload) {
317
- const form = new FormData();
318
- for (const [path, value] of serialize(payload)) {
319
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
320
- form.append(path, value.toString());
321
- } else if (value === null) {
322
- form.append(path, "null");
323
- } else if (value instanceof Date) {
324
- form.append(
325
- path,
326
- Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString()
327
- );
328
- } else if (value instanceof Blob) {
329
- form.append(path, value);
330
- }
331
- }
332
- return {
333
- body: form
334
- };
335
- }
336
- encodeAsURLSearchParams(payload) {
337
- const params = new URLSearchParams();
338
- for (const [path, value] of serialize(payload)) {
339
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
340
- params.append(path, value.toString());
341
- } else if (value === null) {
342
- params.append(path, "null");
343
- } else if (value instanceof Date) {
344
- params.append(
345
- path,
346
- Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString()
347
- );
348
- }
349
- }
350
- return {
351
- body: params.toString(),
352
- headers: new Headers({
353
- "content-type": "application/x-www-form-urlencoded"
354
- })
355
- };
356
- }
357
- async decode(re) {
358
- if (re instanceof Headers || re instanceof URLSearchParams || re instanceof FormData) {
359
- return deserialize([...re.entries()]);
360
- }
361
- const contentType = re.headers.get("content-type");
362
- const contentDisposition = re.headers.get("content-disposition");
363
- const fileName = contentDisposition ? cd.parse(contentDisposition).parameters.filename : void 0;
364
- if (fileName) {
365
- const blob2 = await re.blob();
366
- const file = new File([blob2], fileName, {
367
- type: blob2.type
368
- });
369
- return file;
370
- }
371
- if (!contentType || contentType.startsWith("application/json")) {
372
- if (!re.body) {
373
- return void 0;
374
- }
375
- return await re.json();
376
- }
377
- if (contentType.startsWith("application/x-www-form-urlencoded")) {
378
- const params = new URLSearchParams(await re.text());
379
- return this.decode(params);
380
- }
381
- if (contentType.startsWith("text/")) {
382
- const text = await re.text();
383
- return text;
384
- }
385
- if (contentType.startsWith("multipart/form-data")) {
386
- const form = await re.formData();
387
- return this.decode(form);
388
- }
389
- const blob = await re.blob();
390
- return new File([blob], "blob", {
391
- type: blob.type
392
- });
393
- }
394
- };
395
-
396
- // src/fetch/openapi-procedure-matcher.ts
397
- import { getLazyRouterPrefix, getRouterChild, isProcedure, unlazy } from "@orpc/server";
398
- import { mapValues } from "@orpc/shared";
399
- var OpenAPIProcedureMatcher = class {
400
- constructor(hono, router) {
401
- this.hono = hono;
402
- this.router = router;
403
- this.pendingRouters = [{ path: [], router }];
404
- }
405
- pendingRouters;
406
- async match(method, pathname) {
407
- await this.handlePendingRouters(pathname);
408
- const [matches, paramStash] = this.hono.match(method, pathname);
409
- const [match] = matches.sort((a, b) => {
410
- const slashCountA = a[0][0].split("/").length;
411
- const slashCountB = b[0][0].split("/").length;
412
- if (slashCountA !== slashCountB) {
413
- return slashCountB - slashCountA;
414
- }
415
- const paramsCountA = Object.keys(a[1]).length;
416
- const paramsCountB = Object.keys(b[1]).length;
417
- return paramsCountA - paramsCountB;
418
- });
419
- if (!match) {
420
- return void 0;
421
- }
422
- const path = match[0][1];
423
- const params = paramStash ? mapValues(
424
- match[1],
425
- // if paramStash is defined, then match[1] is ParamIndexMap
426
- (v) => paramStash[v]
427
- ) : match[1];
428
- const { default: maybeProcedure } = await unlazy(getRouterChild(this.router, ...path));
429
- if (!isProcedure(maybeProcedure)) {
430
- return void 0;
431
- }
432
- return {
433
- path,
434
- procedure: maybeProcedure,
435
- params: { ...params }
436
- // normalize params from hono
437
- };
438
- }
439
- add(path, router) {
440
- const lazies = forEachContractProcedure({ path, router }, ({ path: path2, contract }) => {
441
- const method = contract["~orpc"].route?.method ?? "POST";
442
- const httpPath = contract["~orpc"].route?.path ? this.convertOpenAPIPathToRouterPath(contract["~orpc"].route?.path) : `/${path2.map(encodeURIComponent).join("/")}`;
443
- this.hono.add(method, httpPath, [httpPath, path2]);
444
- });
445
- this.pendingRouters.push(...lazies);
446
- }
447
- async handlePendingRouters(pathname) {
448
- const newPendingLazyRouters = [];
449
- for (const item of this.pendingRouters) {
450
- const lazyPrefix = getLazyRouterPrefix(item.router);
451
- if (lazyPrefix && !pathname.startsWith(lazyPrefix) && !pathname.startsWith(`/${item.path.map(encodeURIComponent).join("/")}`)) {
452
- newPendingLazyRouters.push(item);
453
- continue;
454
- }
455
- const { default: router } = await unlazy(item.router);
456
- this.add(item.path, router);
457
- }
458
- this.pendingRouters = newPendingLazyRouters;
459
- }
460
- convertOpenAPIPathToRouterPath(path) {
461
- return standardizeHTTPPath(path).replace(/\{([^}]+)\}/g, ":$1");
462
- }
463
- };
464
-
465
- // src/fetch/schema-coercer.ts
466
- var CompositeSchemaCoercer = class {
467
- constructor(coercers) {
468
- this.coercers = coercers;
469
- }
470
- coerce(schema, value) {
471
- let current = value;
472
- for (const coercer of this.coercers) {
473
- current = coercer.coerce(schema, current);
474
- }
475
- return current;
476
- }
477
- };
478
-
479
- // src/fetch/openapi-handler.ts
480
- var OpenAPIHandler = class {
481
- constructor(hono, router, options) {
482
- this.options = options;
483
- const jsonSerializer = options?.jsonSerializer ?? new JSONSerializer();
484
- this.procedureMatcher = options?.procedureMatcher ?? new OpenAPIProcedureMatcher(hono, router);
485
- this.payloadCodec = options?.payloadCodec ?? new OpenAPIPayloadCodec(jsonSerializer);
486
- this.inputBuilderSimple = options?.inputBuilderSimple ?? new InputBuilderSimple();
487
- this.inputBuilderFull = options?.inputBuilderFull ?? new InputBuilderFull();
488
- this.compositeSchemaCoercer = new CompositeSchemaCoercer(options?.schemaCoercers ?? []);
489
- }
490
- procedureMatcher;
491
- payloadCodec;
492
- inputBuilderSimple;
493
- inputBuilderFull;
494
- compositeSchemaCoercer;
495
- condition(request) {
496
- return request.headers.get(ORPC_HANDLER_HEADER) === null;
497
- }
498
- async fetch(request, ...[options]) {
499
- const context = options?.context;
500
- const headers = request.headers;
501
- const accept = headers.get("Accept") || void 0;
502
- const execute = async () => {
503
- const url = new URL(request.url);
504
- const pathname = `/${trim(url.pathname.replace(options?.prefix ?? "", ""), "/")}`;
505
- const query = url.searchParams;
506
- const customMethod = request.method === "POST" ? query.get("method")?.toUpperCase() : void 0;
507
- const method = customMethod || request.method;
508
- const match = await this.procedureMatcher.match(method, pathname);
509
- if (!match) {
510
- throw new ORPCError2({ code: "NOT_FOUND", message: "Not found" });
511
- }
512
- const decodedPayload = request.method === "GET" ? await this.payloadCodec.decode(query) : await this.payloadCodec.decode(request);
513
- const input = this.inputBuilderSimple.build(match.params, decodedPayload);
514
- const coercedInput = this.compositeSchemaCoercer.coerce(match.procedure["~orpc"].contract["~orpc"].InputSchema, input);
515
- const client = createProcedureClient({
516
- context,
517
- procedure: match.procedure,
518
- path: match.path
519
- });
520
- const output = await client(coercedInput, { signal: options?.signal });
521
- const { body, headers: headers2 } = this.payloadCodec.encode(output);
522
- return new Response(body, {
523
- headers: headers2,
524
- status: match.procedure["~orpc"].contract["~orpc"].route?.successStatus ?? 200
525
- });
526
- };
527
- try {
528
- return await executeWithHooks({
529
- context,
530
- execute,
531
- input: request,
532
- hooks: this.options,
533
- meta: {
534
- signal: options?.signal
535
- }
536
- });
537
- } catch (e) {
538
- const error = this.convertToORPCError(e);
539
- try {
540
- const { body, headers: headers2 } = this.payloadCodec.encode(error.toJSON(), accept);
541
- return new Response(body, {
542
- status: error.status,
543
- headers: headers2
544
- });
545
- } catch (e2) {
546
- const error2 = this.convertToORPCError(e2);
547
- const { body, headers: headers2 } = this.payloadCodec.encode(error2.toJSON());
548
- return new Response(body, {
549
- status: error2.status,
550
- headers: headers2
551
- });
552
- }
553
- }
554
- }
555
- convertToORPCError(e) {
556
- return e instanceof ORPCError2 ? e : new ORPCError2({
557
- code: "INTERNAL_SERVER_ERROR",
558
- message: "Internal server error",
559
- cause: e
560
- });
561
- }
562
- };
2
+ CompositeSchemaCoercer,
3
+ InputStructureCompact,
4
+ InputStructureDetailed,
5
+ OpenAPIHandler,
6
+ OpenAPIPayloadCodec,
7
+ OpenAPIProcedureMatcher,
8
+ deserialize,
9
+ escapeSegment,
10
+ parsePath,
11
+ serialize,
12
+ stringifyPath
13
+ } from "./chunk-YXHH6XHB.js";
14
+ import "./chunk-KNYXLM77.js";
563
15
 
564
- // src/fetch/openapi-handler-server.ts
16
+ // src/adapters/fetch/openapi-handler-server.ts
565
17
  import { TrieRouter } from "hono/router/trie-router";
566
18
  var OpenAPIServerHandler = class extends OpenAPIHandler {
567
19
  constructor(router, options) {
@@ -569,7 +21,7 @@ var OpenAPIServerHandler = class extends OpenAPIHandler {
569
21
  }
570
22
  };
571
23
 
572
- // src/fetch/openapi-handler-serverless.ts
24
+ // src/adapters/fetch/openapi-handler-serverless.ts
573
25
  import { LinearRouter } from "hono/router/linear-router";
574
26
  var OpenAPIServerlessHandler = class extends OpenAPIHandler {
575
27
  constructor(router, options) {
@@ -578,8 +30,8 @@ var OpenAPIServerlessHandler = class extends OpenAPIHandler {
578
30
  };
579
31
  export {
580
32
  CompositeSchemaCoercer,
581
- InputBuilderFull,
582
- InputBuilderSimple,
33
+ InputStructureCompact,
34
+ InputStructureDetailed,
583
35
  OpenAPIHandler,
584
36
  OpenAPIPayloadCodec,
585
37
  OpenAPIProcedureMatcher,