@orpc/openapi 0.0.0-next.1b95eee → 0.0.0-next.1bc0bef

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 (31) hide show
  1. package/README.md +123 -14
  2. package/dist/adapters/aws-lambda/index.d.mts +8 -5
  3. package/dist/adapters/aws-lambda/index.d.ts +8 -5
  4. package/dist/adapters/aws-lambda/index.mjs +3 -3
  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 +18 -0
  8. package/dist/adapters/fetch/index.d.mts +11 -5
  9. package/dist/adapters/fetch/index.d.ts +11 -5
  10. package/dist/adapters/fetch/index.mjs +1 -1
  11. package/dist/adapters/node/index.d.mts +11 -5
  12. package/dist/adapters/node/index.d.ts +11 -5
  13. package/dist/adapters/node/index.mjs +1 -1
  14. package/dist/adapters/standard/index.d.mts +8 -23
  15. package/dist/adapters/standard/index.d.ts +8 -23
  16. package/dist/adapters/standard/index.mjs +1 -1
  17. package/dist/index.d.mts +9 -3
  18. package/dist/index.d.ts +9 -3
  19. package/dist/index.mjs +2 -2
  20. package/dist/plugins/index.d.mts +20 -3
  21. package/dist/plugins/index.d.ts +20 -3
  22. package/dist/plugins/index.mjs +69 -20
  23. package/dist/shared/{openapi.DYi1fARS.d.mts → openapi.BGy4N6eR.d.mts} +28 -6
  24. package/dist/shared/{openapi.DYi1fARS.d.ts → openapi.BGy4N6eR.d.ts} +28 -6
  25. package/dist/shared/{openapi.C_3bk7bB.mjs → openapi.CoREqFh3.mjs} +192 -64
  26. package/dist/shared/{openapi.C_UtQ8Us.mjs → openapi.DIt-Z9W1.mjs} +19 -8
  27. package/dist/shared/openapi.DwaweYRb.d.mts +54 -0
  28. package/dist/shared/openapi.DwaweYRb.d.ts +54 -0
  29. package/package.json +19 -13
  30. package/dist/shared/openapi.D3j94c9n.d.mts +0 -12
  31. package/dist/shared/openapi.D3j94c9n.d.ts +0 -12
@@ -3,7 +3,7 @@ import { toHttpPath } from '@orpc/client/standard';
3
3
  import { fallbackContractConfig, getEventIteratorSchemaDetails } from '@orpc/contract';
4
4
  import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, getDynamicParams } from '@orpc/openapi-client/standard';
5
5
  import { isProcedure, resolveContractProcedures } from '@orpc/server';
6
- import { isObject, stringifyJSON, findDeepMatches, toArray, clone } from '@orpc/shared';
6
+ import { isObject, stringifyJSON, findDeepMatches, toArray, clone, value } from '@orpc/shared';
7
7
  import { TypeName } from 'json-schema-typed/draft-2020-12';
8
8
 
9
9
  const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
@@ -114,13 +114,18 @@ function isAnySchema(schema) {
114
114
  return false;
115
115
  }
116
116
  function separateObjectSchema(schema, separatedProperties) {
117
- if (Object.keys(schema).some((k) => k !== "type" && k !== "properties" && k !== "required" && LOGIC_KEYWORDS.includes(k))) {
117
+ if (Object.keys(schema).some(
118
+ (k) => !["type", "properties", "required", "additionalProperties"].includes(k) && LOGIC_KEYWORDS.includes(k) && schema[k] !== void 0
119
+ )) {
118
120
  return [{ type: "object" }, schema];
119
121
  }
120
122
  const matched = { ...schema };
121
123
  const rest = { ...schema };
122
- matched.properties = schema.properties && Object.entries(schema.properties).filter(([key]) => separatedProperties.includes(key)).reduce((acc, [key, value]) => {
123
- acc[key] = value;
124
+ matched.properties = separatedProperties.reduce((acc, key) => {
125
+ const keySchema = schema.properties?.[key] ?? schema.additionalProperties;
126
+ if (keySchema !== void 0) {
127
+ acc[key] = keySchema;
128
+ }
124
129
  return acc;
125
130
  }, {});
126
131
  matched.required = schema.required?.filter((key) => separatedProperties.includes(key));
@@ -354,6 +359,107 @@ function resolveOpenAPIJsonSchemaRef(doc, schema) {
354
359
  const resolved = doc.components?.schemas?.[name];
355
360
  return resolved ?? schema;
356
361
  }
362
+ function simplifyComposedObjectJsonSchemasAndRefs(schema, doc) {
363
+ if (doc) {
364
+ schema = resolveOpenAPIJsonSchemaRef(doc, schema);
365
+ }
366
+ if (typeof schema !== "object" || !schema.anyOf && !schema.oneOf && !schema.allOf) {
367
+ return schema;
368
+ }
369
+ const unionSchemas = [
370
+ ...toArray(schema.anyOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc))),
371
+ ...toArray(schema.oneOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc)))
372
+ ];
373
+ const objectUnionSchemas = [];
374
+ for (const u of unionSchemas) {
375
+ if (!isObjectSchema(u)) {
376
+ return schema;
377
+ }
378
+ objectUnionSchemas.push(u);
379
+ }
380
+ const mergedUnionPropertyMap = /* @__PURE__ */ new Map();
381
+ for (const u of objectUnionSchemas) {
382
+ if (u.properties) {
383
+ for (const [key, value] of Object.entries(u.properties)) {
384
+ let entry = mergedUnionPropertyMap.get(key);
385
+ if (!entry) {
386
+ const required = objectUnionSchemas.every((s) => s.required?.includes(key));
387
+ entry = { required, schemas: [] };
388
+ mergedUnionPropertyMap.set(key, entry);
389
+ }
390
+ entry.schemas.push(value);
391
+ }
392
+ }
393
+ }
394
+ const intersectionSchemas = toArray(schema.allOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc)));
395
+ const objectIntersectionSchemas = [];
396
+ for (const u of intersectionSchemas) {
397
+ if (!isObjectSchema(u)) {
398
+ return schema;
399
+ }
400
+ objectIntersectionSchemas.push(u);
401
+ }
402
+ if (isObjectSchema(schema)) {
403
+ objectIntersectionSchemas.push(schema);
404
+ }
405
+ const mergedInteractionPropertyMap = /* @__PURE__ */ new Map();
406
+ for (const u of objectIntersectionSchemas) {
407
+ if (u.properties) {
408
+ for (const [key, value] of Object.entries(u.properties)) {
409
+ let entry = mergedInteractionPropertyMap.get(key);
410
+ if (!entry) {
411
+ const required = objectIntersectionSchemas.some((s) => s.required?.includes(key));
412
+ entry = { required, schemas: [] };
413
+ mergedInteractionPropertyMap.set(key, entry);
414
+ }
415
+ entry.schemas.push(value);
416
+ }
417
+ }
418
+ }
419
+ const resultObjectSchema = { type: "object", properties: {}, required: [] };
420
+ const keys = /* @__PURE__ */ new Set([
421
+ ...mergedUnionPropertyMap.keys(),
422
+ ...mergedInteractionPropertyMap.keys()
423
+ ]);
424
+ if (keys.size === 0) {
425
+ return schema;
426
+ }
427
+ const deduplicateSchemas = (schemas) => {
428
+ const seen = /* @__PURE__ */ new Set();
429
+ const result = [];
430
+ for (const schema2 of schemas) {
431
+ const key = stringifyJSON(schema2);
432
+ if (!seen.has(key)) {
433
+ seen.add(key);
434
+ result.push(schema2);
435
+ }
436
+ }
437
+ return result;
438
+ };
439
+ for (const key of keys) {
440
+ const unionEntry = mergedUnionPropertyMap.get(key);
441
+ const intersectionEntry = mergedInteractionPropertyMap.get(key);
442
+ resultObjectSchema.properties[key] = (() => {
443
+ const dedupedUnionSchemas = unionEntry ? deduplicateSchemas(unionEntry.schemas) : [];
444
+ const dedupedIntersectionSchemas = intersectionEntry ? deduplicateSchemas(intersectionEntry.schemas) : [];
445
+ if (!dedupedUnionSchemas.length) {
446
+ return dedupedIntersectionSchemas.length === 1 ? dedupedIntersectionSchemas[0] : { allOf: dedupedIntersectionSchemas };
447
+ }
448
+ if (!dedupedIntersectionSchemas.length) {
449
+ return dedupedUnionSchemas.length === 1 ? dedupedUnionSchemas[0] : { anyOf: dedupedUnionSchemas };
450
+ }
451
+ const allOf = deduplicateSchemas([
452
+ ...dedupedIntersectionSchemas,
453
+ dedupedUnionSchemas.length === 1 ? dedupedUnionSchemas[0] : { anyOf: dedupedUnionSchemas }
454
+ ]);
455
+ return allOf.length === 1 ? allOf[0] : { allOf };
456
+ })();
457
+ if (unionEntry?.required || intersectionEntry?.required) {
458
+ resultObjectSchema.required.push(key);
459
+ }
460
+ }
461
+ return resultObjectSchema;
462
+ }
357
463
 
358
464
  class CompositeSchemaConverter {
359
465
  converters;
@@ -382,37 +488,38 @@ class OpenAPIGenerator {
382
488
  /**
383
489
  * Generates OpenAPI specifications from oRPC routers/contracts.
384
490
  *
385
- * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
491
+ * @see {@link https://orpc.dev/docs/openapi/openapi-specification OpenAPI Specification Docs}
386
492
  */
387
- async generate(router, options = {}) {
388
- const exclude = options.exclude ?? (() => false);
493
+ async generate(router, { customErrorResponseBodySchema, commonSchemas, filter: baseFilter, exclude, ...baseDoc } = {}) {
494
+ const filter = baseFilter ?? (({ contract, path }) => {
495
+ return !(exclude?.(contract, path) ?? false);
496
+ });
389
497
  const doc = {
390
- ...clone(options),
391
- info: options.info ?? { title: "API Reference", version: "0.0.0" },
392
- openapi: "3.1.1",
393
- exclude: void 0,
394
- commonSchemas: void 0
498
+ ...clone(baseDoc),
499
+ info: baseDoc.info ?? { title: "API Reference", version: "0.0.0" },
500
+ openapi: "3.1.1"
395
501
  };
396
- const baseSchemaConvertOptions = await this.#resolveCommonSchemas(doc, options.commonSchemas);
502
+ const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, commonSchemas);
397
503
  const contracts = [];
398
- await resolveContractProcedures({ path: [], router }, ({ contract, path }) => {
399
- if (!exclude(contract, path)) {
400
- contracts.push({ contract, path });
504
+ await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
505
+ if (!value(filter, traverseOptions)) {
506
+ return;
401
507
  }
508
+ contracts.push(traverseOptions);
402
509
  });
403
510
  const errors = [];
404
511
  for (const { contract, path } of contracts) {
405
- const operationId = path.join(".");
512
+ const stringPath = path.join(".");
406
513
  try {
407
514
  const def = contract["~orpc"];
408
515
  const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
409
516
  const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
410
517
  let operationObjectRef;
411
- if (def.route.spec !== void 0) {
518
+ if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
412
519
  operationObjectRef = def.route.spec;
413
520
  } else {
414
521
  operationObjectRef = {
415
- operationId,
522
+ operationId: def.route.operationId ?? stringPath,
416
523
  summary: def.route.summary,
417
524
  description: def.route.description,
418
525
  deprecated: def.route.deprecated,
@@ -420,7 +527,10 @@ class OpenAPIGenerator {
420
527
  };
421
528
  await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
422
529
  await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
423
- await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions);
530
+ await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema, customErrorResponseBodySchema);
531
+ }
532
+ if (typeof def.route.spec === "function") {
533
+ operationObjectRef = def.route.spec(operationObjectRef);
424
534
  }
425
535
  doc.paths ??= {};
426
536
  doc.paths[httpPath] ??= {};
@@ -430,7 +540,7 @@ class OpenAPIGenerator {
430
540
  throw e;
431
541
  }
432
542
  errors.push(
433
- `[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${operationId}
543
+ `[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
434
544
  ${e.message}`
435
545
  );
436
546
  }
@@ -445,11 +555,26 @@ ${errors.join("\n\n")}`
445
555
  return this.serializer.serialize(doc)[0];
446
556
  }
447
557
  async #resolveCommonSchemas(doc, commonSchemas) {
448
- const baseOptions = {};
558
+ let undefinedErrorJsonSchema = {
559
+ type: "object",
560
+ properties: {
561
+ defined: { const: false },
562
+ code: { type: "string" },
563
+ status: { type: "number" },
564
+ message: { type: "string" },
565
+ data: {}
566
+ },
567
+ required: ["defined", "code", "status", "message"]
568
+ };
569
+ const baseSchemaConvertOptions = {};
449
570
  if (commonSchemas) {
450
- baseOptions.components = [];
571
+ baseSchemaConvertOptions.components = [];
451
572
  for (const key in commonSchemas) {
452
- const { schema, strategy = "input" } = commonSchemas[key];
573
+ const options = commonSchemas[key];
574
+ if (options.schema === void 0) {
575
+ continue;
576
+ }
577
+ const { schema, strategy = "input" } = options;
453
578
  const [required, json] = await this.converter.convert(schema, { strategy });
454
579
  const allowedStrategies = [strategy];
455
580
  if (strategy === "input") {
@@ -463,7 +588,7 @@ ${errors.join("\n\n")}`
463
588
  allowedStrategies.push("input");
464
589
  }
465
590
  }
466
- baseOptions.components.push({
591
+ baseSchemaConvertOptions.components.push({
467
592
  schema,
468
593
  required,
469
594
  ref: `#/components/schemas/${key}`,
@@ -473,11 +598,19 @@ ${errors.join("\n\n")}`
473
598
  doc.components ??= {};
474
599
  doc.components.schemas ??= {};
475
600
  for (const key in commonSchemas) {
476
- const { schema, strategy = "input" } = commonSchemas[key];
601
+ const options = commonSchemas[key];
602
+ if (options.schema === void 0) {
603
+ if (options.error === "UndefinedError") {
604
+ doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
605
+ undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
606
+ }
607
+ continue;
608
+ }
609
+ const { schema, strategy = "input" } = options;
477
610
  const [, json] = await this.converter.convert(
478
611
  schema,
479
612
  {
480
- ...baseOptions,
613
+ ...baseSchemaConvertOptions,
481
614
  strategy,
482
615
  minStructureDepthForRef: 1
483
616
  // not allow use $ref for root schemas
@@ -486,7 +619,7 @@ ${errors.join("\n\n")}`
486
619
  doc.components.schemas[key] = toOpenAPISchema(json);
487
620
  }
488
621
  }
489
- return baseOptions;
622
+ return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
490
623
  }
491
624
  async #request(doc, ref, def, baseSchemaConvertOptions) {
492
625
  const method = fallbackContractConfig("defaultMethod", def.route.method);
@@ -507,13 +640,15 @@ ${errors.join("\n\n")}`
507
640
  def.inputSchema,
508
641
  {
509
642
  ...baseSchemaConvertOptions,
510
- strategy: "input",
511
- minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
643
+ strategy: "input"
512
644
  }
513
645
  );
514
646
  if (isAnySchema(schema) && !dynamicParams?.length) {
515
647
  return;
516
648
  }
649
+ if (inputStructure === "detailed" || inputStructure === "compact" && (dynamicParams?.length || method === "GET")) {
650
+ schema = simplifyComposedObjectJsonSchemasAndRefs(schema, doc);
651
+ }
517
652
  if (inputStructure === "compact") {
518
653
  if (dynamicParams?.length) {
519
654
  const error2 = new OpenAPIGeneratorError(
@@ -553,7 +688,7 @@ ${errors.join("\n\n")}`
553
688
  if (!isObjectSchema(schema)) {
554
689
  throw error;
555
690
  }
556
- const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
691
+ const resolvedParamSchema = schema.properties?.params !== void 0 ? simplifyComposedObjectJsonSchemasAndRefs(schema.properties.params, doc) : void 0;
557
692
  if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
558
693
  throw new OpenAPIGeneratorError(
559
694
  'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
@@ -562,7 +697,7 @@ ${errors.join("\n\n")}`
562
697
  for (const from of ["params", "query", "headers"]) {
563
698
  const fromSchema = schema.properties?.[from];
564
699
  if (fromSchema !== void 0) {
565
- const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
700
+ const resolvedSchema = simplifyComposedObjectJsonSchemasAndRefs(fromSchema, doc);
566
701
  if (!isObjectSchema(resolvedSchema)) {
567
702
  throw error;
568
703
  }
@@ -623,13 +758,14 @@ ${errors.join("\n\n")}`
623
758
 
624
759
  But got: ${stringifyJSON(item)}
625
760
  `);
626
- if (!isObjectSchema(item)) {
761
+ const simplifiedItem = simplifyComposedObjectJsonSchemasAndRefs(item, doc);
762
+ if (!isObjectSchema(simplifiedItem)) {
627
763
  throw error;
628
764
  }
629
765
  let schemaStatus;
630
766
  let schemaDescription;
631
- if (item.properties?.status !== void 0) {
632
- const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
767
+ if (simplifiedItem.properties?.status !== void 0) {
768
+ const statusSchema = resolveOpenAPIJsonSchemaRef(doc, simplifiedItem.properties.status);
633
769
  if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
634
770
  throw error;
635
771
  }
@@ -649,8 +785,8 @@ ${errors.join("\n\n")}`
649
785
  ref.responses[itemStatus] = {
650
786
  description: itemDescription
651
787
  };
652
- if (item.properties?.headers !== void 0) {
653
- const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
788
+ if (simplifiedItem.properties?.headers !== void 0) {
789
+ const headersSchema = simplifyComposedObjectJsonSchemasAndRefs(simplifiedItem.properties.headers, doc);
654
790
  if (!isObjectSchema(headersSchema)) {
655
791
  throw error;
656
792
  }
@@ -660,61 +796,53 @@ ${errors.join("\n\n")}`
660
796
  ref.responses[itemStatus].headers ??= {};
661
797
  ref.responses[itemStatus].headers[key] = {
662
798
  schema: toOpenAPISchema(headerSchema),
663
- required: item.required?.includes("headers") && headersSchema.required?.includes(key)
799
+ required: simplifiedItem.required?.includes("headers") && headersSchema.required?.includes(key)
664
800
  };
665
801
  }
666
802
  }
667
803
  }
668
- if (item.properties?.body !== void 0) {
804
+ if (simplifiedItem.properties?.body !== void 0) {
669
805
  ref.responses[itemStatus].content = toOpenAPIContent(
670
- applySchemaOptionality(item.required?.includes("body") ?? false, item.properties.body)
806
+ applySchemaOptionality(simplifiedItem.required?.includes("body") ?? false, simplifiedItem.properties.body)
671
807
  );
672
808
  }
673
809
  }
674
810
  }
675
- async #errorResponse(ref, def, baseSchemaConvertOptions) {
811
+ async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema, customErrorResponseBodySchema) {
676
812
  const errorMap = def.errorMap;
677
- const errors = {};
813
+ const errorResponsesByStatus = {};
678
814
  for (const code in errorMap) {
679
815
  const config = errorMap[code];
680
816
  if (!config) {
681
817
  continue;
682
818
  }
683
819
  const status = fallbackORPCErrorStatus(code, config.status);
684
- const message = fallbackORPCErrorMessage(code, config.message);
820
+ const defaultMessage = fallbackORPCErrorMessage(code, config.message);
821
+ errorResponsesByStatus[status] ??= { status, definedErrorDefinitions: [], errorSchemaVariants: [] };
685
822
  const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
686
- errors[status] ??= [];
687
- errors[status].push({
823
+ errorResponsesByStatus[status].definedErrorDefinitions.push([code, defaultMessage, dataRequired, dataSchema]);
824
+ errorResponsesByStatus[status].errorSchemaVariants.push({
688
825
  type: "object",
689
826
  properties: {
690
827
  defined: { const: true },
691
828
  code: { const: code },
692
829
  status: { const: status },
693
- message: { type: "string", default: message },
830
+ message: { type: "string", default: defaultMessage },
694
831
  data: dataSchema
695
832
  },
696
833
  required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
697
834
  });
698
835
  }
699
836
  ref.responses ??= {};
700
- for (const status in errors) {
701
- const schemas = errors[status];
702
- ref.responses[status] = {
703
- description: status,
704
- content: toOpenAPIContent({
837
+ for (const statusString in errorResponsesByStatus) {
838
+ const errorResponse = errorResponsesByStatus[statusString];
839
+ const customBodySchema = value(customErrorResponseBodySchema, errorResponse.definedErrorDefinitions, errorResponse.status);
840
+ ref.responses[statusString] = {
841
+ description: statusString,
842
+ content: toOpenAPIContent(customBodySchema ?? {
705
843
  oneOf: [
706
- ...schemas,
707
- {
708
- type: "object",
709
- properties: {
710
- defined: { const: false },
711
- code: { type: "string" },
712
- status: { type: "number" },
713
- message: { type: "string" },
714
- data: {}
715
- },
716
- required: ["defined", "code", "status", "message"]
717
- }
844
+ ...errorResponse.errorSchemaVariants,
845
+ undefinedErrorSchema
718
846
  ]
719
847
  })
720
848
  };
@@ -722,4 +850,4 @@ ${errors.join("\n\n")}`
722
850
  }
723
851
  }
724
852
 
725
- export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, filterSchemaBranches as m, applySchemaOptionality as n, expandUnionSchema as o, expandArrayableSchema as p, isPrimitiveSchema as q, resolveOpenAPIJsonSchemaRef as r, separateObjectSchema as s, toOpenAPIPath as t };
853
+ export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, separateObjectSchema as m, filterSchemaBranches as n, applySchemaOptionality as o, expandUnionSchema as p, expandArrayableSchema as q, resolveOpenAPIJsonSchemaRef as r, simplifyComposedObjectJsonSchemasAndRefs as s, toOpenAPIPath as t, isPrimitiveSchema as u };
@@ -2,15 +2,17 @@ import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, StandardBracketNota
2
2
  import { StandardHandler } from '@orpc/server/standard';
3
3
  import { isORPCErrorStatus } from '@orpc/client';
4
4
  import { fallbackContractConfig } from '@orpc/contract';
5
- import { isObject, stringifyJSON } from '@orpc/shared';
5
+ import { isObject, stringifyJSON, tryDecodeURIComponent, value } from '@orpc/shared';
6
6
  import { toHttpPath } from '@orpc/client/standard';
7
7
  import { traverseContractProcedures, isProcedure, getLazyMeta, unlazy, getRouter, createContractedProcedure } from '@orpc/server';
8
8
  import { createRouter, addRoute, findRoute } from 'rou3';
9
9
 
10
10
  class StandardOpenAPICodec {
11
- constructor(serializer) {
11
+ constructor(serializer, options = {}) {
12
12
  this.serializer = serializer;
13
+ this.customErrorResponseBodyEncoder = options.customErrorResponseBodyEncoder;
13
14
  }
15
+ customErrorResponseBodyEncoder;
14
16
  async decode(request, params, procedure) {
15
17
  const inputStructure = fallbackContractConfig("defaultInputStructure", procedure["~orpc"].route.inputStructure);
16
18
  if (inputStructure === "compact") {
@@ -73,10 +75,11 @@ class StandardOpenAPICodec {
73
75
  };
74
76
  }
75
77
  encodeError(error) {
78
+ const body = this.customErrorResponseBodyEncoder?.(error) ?? error.toJSON();
76
79
  return {
77
80
  status: error.status,
78
81
  headers: {},
79
- body: this.serializer.serialize(error.toJSON(), { outputFormat: "plain" })
82
+ body: this.serializer.serialize(body, { outputFormat: "plain" })
80
83
  };
81
84
  }
82
85
  #isDetailedOutput(output) {
@@ -97,14 +100,22 @@ function toRou3Pattern(path) {
97
100
  return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/**:$1").replace(/\/\{([^}]+)\}/g, "/:$1");
98
101
  }
99
102
  function decodeParams(params) {
100
- return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, decodeURIComponent(value)]));
103
+ return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, tryDecodeURIComponent(value)]));
101
104
  }
102
105
 
103
106
  class StandardOpenAPIMatcher {
107
+ filter;
104
108
  tree = createRouter();
105
109
  pendingRouters = [];
110
+ constructor(options = {}) {
111
+ this.filter = options.filter ?? true;
112
+ }
106
113
  init(router, path = []) {
107
- const laziedOptions = traverseContractProcedures({ router, path }, ({ path: path2, contract }) => {
114
+ const laziedOptions = traverseContractProcedures({ router, path }, (traverseOptions) => {
115
+ if (!value(this.filter, traverseOptions)) {
116
+ return;
117
+ }
118
+ const { path: path2, contract } = traverseOptions;
108
119
  const method = fallbackContractConfig("defaultMethod", contract["~orpc"].route.method);
109
120
  const httpPath = toRou3Pattern(contract["~orpc"].route.path ?? toHttpPath(path2));
110
121
  if (isProcedure(contract)) {
@@ -168,10 +179,10 @@ class StandardOpenAPIMatcher {
168
179
  class StandardOpenAPIHandler extends StandardHandler {
169
180
  constructor(router, options) {
170
181
  const jsonSerializer = new StandardOpenAPIJsonSerializer(options);
171
- const bracketNotationSerializer = new StandardBracketNotationSerializer();
182
+ const bracketNotationSerializer = new StandardBracketNotationSerializer(options);
172
183
  const serializer = new StandardOpenAPISerializer(jsonSerializer, bracketNotationSerializer);
173
- const matcher = new StandardOpenAPIMatcher();
174
- const codec = new StandardOpenAPICodec(serializer);
184
+ const matcher = new StandardOpenAPIMatcher(options);
185
+ const codec = new StandardOpenAPICodec(serializer, options);
175
186
  super(router, matcher, codec, options);
176
187
  }
177
188
  }
@@ -0,0 +1,54 @@
1
+ import { StandardOpenAPISerializer, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
2
+ import { AnyProcedure, TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
3
+ import { StandardCodec, StandardParams, StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
4
+ import { ORPCError, HTTPPath } from '@orpc/client';
5
+ import { StandardLazyRequest, StandardResponse } from '@orpc/standard-server';
6
+ import { Value } from '@orpc/shared';
7
+
8
+ interface StandardOpenAPICodecOptions {
9
+ /**
10
+ * Customize how an ORPC error is encoded into a response body.
11
+ * Use this if your API needs a different error output structure.
12
+ *
13
+ * @remarks
14
+ * - Return `null | undefined` to fallback to default behavior
15
+ *
16
+ * @default ((e) => e.toJSON())
17
+ */
18
+ customErrorResponseBodyEncoder?: (error: ORPCError<any, any>) => unknown;
19
+ }
20
+ declare class StandardOpenAPICodec implements StandardCodec {
21
+ #private;
22
+ private readonly serializer;
23
+ private readonly customErrorResponseBodyEncoder;
24
+ constructor(serializer: StandardOpenAPISerializer, options?: StandardOpenAPICodecOptions);
25
+ decode(request: StandardLazyRequest, params: StandardParams | undefined, procedure: AnyProcedure): Promise<unknown>;
26
+ encode(output: unknown, procedure: AnyProcedure): StandardResponse;
27
+ encodeError(error: ORPCError<any, any>): StandardResponse;
28
+ }
29
+
30
+ interface StandardOpenAPIMatcherOptions {
31
+ /**
32
+ * Filter procedures. Return `false` to exclude a procedure from matching.
33
+ *
34
+ * @default true
35
+ */
36
+ filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
37
+ }
38
+ declare class StandardOpenAPIMatcher implements StandardMatcher {
39
+ private readonly filter;
40
+ private readonly tree;
41
+ private pendingRouters;
42
+ constructor(options?: StandardOpenAPIMatcherOptions);
43
+ init(router: AnyRouter, path?: readonly string[]): void;
44
+ match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
45
+ }
46
+
47
+ interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions, StandardOpenAPICodecOptions {
48
+ }
49
+ declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
50
+ constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
51
+ }
52
+
53
+ export { StandardOpenAPICodec as a, StandardOpenAPIHandler as c, StandardOpenAPIMatcher as e };
54
+ export type { StandardOpenAPICodecOptions as S, StandardOpenAPIHandlerOptions as b, StandardOpenAPIMatcherOptions as d };
@@ -0,0 +1,54 @@
1
+ import { StandardOpenAPISerializer, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
2
+ import { AnyProcedure, TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
3
+ import { StandardCodec, StandardParams, StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
4
+ import { ORPCError, HTTPPath } from '@orpc/client';
5
+ import { StandardLazyRequest, StandardResponse } from '@orpc/standard-server';
6
+ import { Value } from '@orpc/shared';
7
+
8
+ interface StandardOpenAPICodecOptions {
9
+ /**
10
+ * Customize how an ORPC error is encoded into a response body.
11
+ * Use this if your API needs a different error output structure.
12
+ *
13
+ * @remarks
14
+ * - Return `null | undefined` to fallback to default behavior
15
+ *
16
+ * @default ((e) => e.toJSON())
17
+ */
18
+ customErrorResponseBodyEncoder?: (error: ORPCError<any, any>) => unknown;
19
+ }
20
+ declare class StandardOpenAPICodec implements StandardCodec {
21
+ #private;
22
+ private readonly serializer;
23
+ private readonly customErrorResponseBodyEncoder;
24
+ constructor(serializer: StandardOpenAPISerializer, options?: StandardOpenAPICodecOptions);
25
+ decode(request: StandardLazyRequest, params: StandardParams | undefined, procedure: AnyProcedure): Promise<unknown>;
26
+ encode(output: unknown, procedure: AnyProcedure): StandardResponse;
27
+ encodeError(error: ORPCError<any, any>): StandardResponse;
28
+ }
29
+
30
+ interface StandardOpenAPIMatcherOptions {
31
+ /**
32
+ * Filter procedures. Return `false` to exclude a procedure from matching.
33
+ *
34
+ * @default true
35
+ */
36
+ filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
37
+ }
38
+ declare class StandardOpenAPIMatcher implements StandardMatcher {
39
+ private readonly filter;
40
+ private readonly tree;
41
+ private pendingRouters;
42
+ constructor(options?: StandardOpenAPIMatcherOptions);
43
+ init(router: AnyRouter, path?: readonly string[]): void;
44
+ match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
45
+ }
46
+
47
+ interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions, StandardOpenAPICodecOptions {
48
+ }
49
+ declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
50
+ constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
51
+ }
52
+
53
+ export { StandardOpenAPICodec as a, StandardOpenAPIHandler as c, StandardOpenAPIMatcher as e };
54
+ export type { StandardOpenAPICodecOptions as S, StandardOpenAPIHandlerOptions as b, StandardOpenAPIMatcherOptions as d };