@trpc/openapi 11.19.0-alpha → 11.21.1-canary.20.10e2ef98f

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.
package/dist/index.mjs CHANGED
@@ -453,16 +453,46 @@ function convertUnionType(type, ctx, depth) {
453
453
  const schemas = effective.map((m) => typeToJsonSchema(m, ctx, depth + 1)).filter(isNonEmptySchema);
454
454
  if (hasBoolPair) schemas.push({ type: "boolean" });
455
455
  if (hasNull) schemas.push({ type: "null" });
456
- if (schemas.length === 0) return {};
457
- const [firstSchema] = schemas;
458
- if (schemas.length === 1 && firstSchema !== void 0) return firstSchema;
459
- if (schemas.every(isSimpleTypeSchema)) return { type: schemas.map((s) => s.type) };
460
- const discriminatorProp = detectDiscriminatorProperty(schemas);
456
+ const distinctSchemas = dedupeSchemas(schemas, ctx.schemas);
457
+ if (distinctSchemas.length === 0) return {};
458
+ const [firstSchema] = distinctSchemas;
459
+ if (distinctSchemas.length === 1 && firstSchema !== void 0) return firstSchema;
460
+ if (distinctSchemas.every(isSimpleTypeSchema)) return { type: distinctSchemas.map((s) => s.type) };
461
+ const discriminatorProp = detectDiscriminatorProperty(distinctSchemas);
461
462
  if (discriminatorProp) return {
462
- oneOf: schemas,
463
+ oneOf: distinctSchemas,
463
464
  discriminator: { propertyName: discriminatorProp }
464
465
  };
465
- return { oneOf: schemas };
466
+ return { oneOf: distinctSchemas };
467
+ }
468
+ function dedupeSchemas(schemas, components) {
469
+ const seen = /* @__PURE__ */ new Set();
470
+ return schemas.filter((schema) => {
471
+ const key = canonicalize(resolveBareRef(schema, components));
472
+ if (seen.has(key)) return false;
473
+ seen.add(key);
474
+ return true;
475
+ });
476
+ }
477
+ /**
478
+ * A `$ref` on its own describes exactly the component it points at, so compare
479
+ * through the component - otherwise the same shape reached both by reference
480
+ * and inline looks like two distinct schemas.
481
+ */
482
+ function resolveBareRef(schema, components) {
483
+ if (!isBareRef(schema)) return schema;
484
+ const target = getReferencedSchema(schema, components);
485
+ return target && isNonEmptySchema(target) ? target : schema;
486
+ }
487
+ /** A `$ref` with no sibling keys, i.e. one that stands for its component. */
488
+ function isBareRef(schema) {
489
+ return schema.$ref !== void 0 && Object.keys(schema).length === 1;
490
+ }
491
+ function canonicalize(value) {
492
+ var _JSON$stringify;
493
+ if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
494
+ if (value && typeof value === "object") return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonicalize(v)}`).join(",")}}`;
495
+ return (_JSON$stringify = JSON.stringify(value)) !== null && _JSON$stringify !== void 0 ? _JSON$stringify : "undefined";
466
496
  }
467
497
  /**
468
498
  * If every schema in a oneOf is an object with a common required property
@@ -675,7 +705,11 @@ function getProcedureInputTypeName(type, path) {
675
705
  if (declarationName && !ANONYMOUS_NAMES.has(declarationName) && !declarationName.startsWith("__")) return declarationName;
676
706
  }
677
707
  }
678
- return `${path.split(".").filter(Boolean).map((segment) => segment.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")).join("") || "Procedure"}Input`;
708
+ return `${pascalCasePath(path) || "Procedure"}Input`;
709
+ }
710
+ /** `user.getById` → `UserGetById`, for naming components after a procedure. */
711
+ function pascalCasePath(path) {
712
+ return path.split(".").filter(Boolean).map((segment) => segment.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")).join("");
679
713
  }
680
714
  function isUnknownLikeType(type) {
681
715
  return hasFlag(type, ts.TypeFlags.Unknown | ts.TypeFlags.Any);
@@ -758,9 +792,28 @@ function extractProcedure(def, ctx) {
758
792
  type: def.typeName,
759
793
  inputSchema,
760
794
  outputSchema,
795
+ errorSchema: extractProcedureErrorSchema($typesType, ctx),
761
796
  description: def.description
762
797
  });
763
798
  }
799
+ function extractProcedureErrorSchema($typesType, ctx) {
800
+ const { schemaCtx } = ctx;
801
+ const errorSym = $typesType.getProperty("errorShape");
802
+ if (!errorSym) return null;
803
+ const { checker } = schemaCtx;
804
+ const errorType = checker.getTypeOfSymbol(errorSym);
805
+ if (hasFlag(errorType, ts.TypeFlags.Any) || isUnknownLikeType(errorType) || addsNothingToRouterErrorType(errorType, ctx.routerErrorType, checker)) return null;
806
+ const schema = typeToJsonSchema(errorType, schemaCtx);
807
+ return isNonEmptySchema(schema) ? schema : null;
808
+ }
809
+ function addsNothingToRouterErrorType(errorType, routerErrorType, checker) {
810
+ if (!routerErrorType) return false;
811
+ if (errorType === routerErrorType) return true;
812
+ const describe = (type) => checker.typeToString(type, void 0, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseFullyQualifiedType);
813
+ const unionMembers = (type) => type.isUnion() ? type.types : [type];
814
+ const routerMembers = new Set(unionMembers(routerErrorType).map(describe));
815
+ return unionMembers(errorType).every((member) => routerMembers.has(describe(member)));
816
+ }
764
817
  /** Extract the JSDoc comment text from a symbol, if any. */
765
818
  function getJsDocComment(sym, checker) {
766
819
  var _sym$declarations2;
@@ -848,7 +901,7 @@ function loadCompilerOptions(startDir) {
848
901
  * it to a JSON Schema. Returns `null` when the path cannot be resolved
849
902
  * (e.g. older tRPC versions or missing type info).
850
903
  */
851
- function extractErrorSchema(routerType, checker, schemaCtx) {
904
+ function getRouterErrorType(routerType, checker) {
852
905
  const walk = (type, keys) => {
853
906
  const [head, ...rest] = keys;
854
907
  if (!head) return type;
@@ -862,10 +915,21 @@ function extractErrorSchema(routerType, checker, schemaCtx) {
862
915
  "$types",
863
916
  "errorShape"
864
917
  ]);
918
+ if (!errorShapeType || hasFlag(errorShapeType, ts.TypeFlags.Any)) return null;
919
+ return errorShapeType;
920
+ }
921
+ function extractErrorSchema(routerType, checker, schemaCtx) {
922
+ const errorShapeType = getRouterErrorType(routerType, checker);
865
923
  if (!errorShapeType) return null;
866
- if (hasFlag(errorShapeType, ts.TypeFlags.Any)) return null;
867
924
  return typeToJsonSchema(errorShapeType, schemaCtx);
868
925
  }
926
+ function wrapInErrorEnvelope(errorSchema) {
927
+ return {
928
+ type: "object",
929
+ properties: { error: errorSchema },
930
+ required: ["error"]
931
+ };
932
+ }
869
933
  /** Fallback error schema when the router type doesn't expose an error shape. */
870
934
  const DEFAULT_ERROR_SCHEMA = {
871
935
  type: "object",
@@ -896,7 +960,7 @@ function wrapInSuccessEnvelope(outputSchema) {
896
960
  required: ["result"]
897
961
  };
898
962
  }
899
- function buildProcedureOperation(proc, method) {
963
+ function buildProcedureOperation(proc, method, errorResponseRef) {
900
964
  const [tag = proc.path] = proc.path.split(".");
901
965
  const operation = _objectSpread2(_objectSpread2({ operationId: proc.path }, proc.description ? { description: proc.description } : {}), {}, {
902
966
  tags: [tag],
@@ -905,7 +969,7 @@ function buildProcedureOperation(proc, method) {
905
969
  description: "Successful response",
906
970
  content: { "application/json": { schema: wrapInSuccessEnvelope(proc.outputSchema) } }
907
971
  },
908
- default: { $ref: "#/components/responses/Error" }
972
+ default: { $ref: errorResponseRef }
909
973
  }
910
974
  });
911
975
  if (proc.inputSchema === null) return operation;
@@ -922,19 +986,56 @@ function buildProcedureOperation(proc, method) {
922
986
  };
923
987
  return operation;
924
988
  }
989
+ function buildErrorResponse(schema) {
990
+ return {
991
+ description: "Error response",
992
+ content: { "application/json": { schema: wrapInErrorEnvelope(schema) } }
993
+ };
994
+ }
995
+ function buildErrorResponses(procedures, routerErrorSchema, schemas) {
996
+ const responses = { Error: buildErrorResponse(routerErrorSchema) };
997
+ const shapeKey = (schema) => canonicalize(resolveBareRef(schema, schemas));
998
+ const refByShape = /* @__PURE__ */ new Map([[shapeKey(routerErrorSchema), "#/components/responses/Error"]]);
999
+ const refByProcedurePath = /* @__PURE__ */ new Map();
1000
+ for (const proc of procedures) {
1001
+ if (!proc.errorSchema) continue;
1002
+ const key = shapeKey(proc.errorSchema);
1003
+ let ref = refByShape.get(key);
1004
+ if (!ref) {
1005
+ const name = `${pascalCasePath(proc.path) || "Procedure"}Error`;
1006
+ const shape = isBareRef(proc.errorSchema) ? proc.errorSchema : registerSchema(`${name}Shape`, proc.errorSchema, schemas);
1007
+ const responseName = ensureUniqueName(name, responses);
1008
+ responses[responseName] = buildErrorResponse(shape);
1009
+ ref = `#/components/responses/${responseName}`;
1010
+ refByShape.set(key, ref);
1011
+ }
1012
+ refByProcedurePath.set(proc.path, ref);
1013
+ }
1014
+ return {
1015
+ responses,
1016
+ refByProcedurePath
1017
+ };
1018
+ }
1019
+ function registerSchema(name, schema, schemas) {
1020
+ const refName = ensureUniqueName(name, schemas);
1021
+ schemas[refName] = schema;
1022
+ return { $ref: `#/components/schemas/${refName}` };
1023
+ }
925
1024
  function buildOpenAPIDocument(procedures, options, meta = { errorSchema: null }) {
926
- var _options$title, _options$version, _options$servers, _meta$errorSchema;
1025
+ var _meta$schemas, _meta$errorSchema, _options$title, _options$version, _options$servers;
927
1026
  const paths = {};
928
- for (const proc of procedures) {
929
- var _paths$opPath;
930
- if (!shouldIncludeProcedureInOpenAPI(proc.type)) continue;
1027
+ const included = procedures.filter((proc) => shouldIncludeProcedureInOpenAPI(proc.type));
1028
+ const schemas = (_meta$schemas = meta.schemas) !== null && _meta$schemas !== void 0 ? _meta$schemas : {};
1029
+ const { responses: errorResponses, refByProcedurePath } = buildErrorResponses(included, (_meta$errorSchema = meta.errorSchema) !== null && _meta$errorSchema !== void 0 ? _meta$errorSchema : DEFAULT_ERROR_SCHEMA, schemas);
1030
+ for (const proc of included) {
1031
+ var _paths$opPath, _refByProcedurePath$g;
931
1032
  const opPath = `/${proc.path}`;
932
1033
  const method = proc.type === "query" ? "get" : "post";
933
1034
  const pathItem = (_paths$opPath = paths[opPath]) !== null && _paths$opPath !== void 0 ? _paths$opPath : {};
934
1035
  paths[opPath] = pathItem;
935
- pathItem[method] = buildProcedureOperation(proc, method);
1036
+ pathItem[method] = buildProcedureOperation(proc, method, (_refByProcedurePath$g = refByProcedurePath.get(proc.path)) !== null && _refByProcedurePath$g !== void 0 ? _refByProcedurePath$g : "#/components/responses/Error");
936
1037
  }
937
- const hasNamedSchemas = meta.schemas !== void 0 && Object.keys(meta.schemas).length > 0;
1038
+ const hasNamedSchemas = Object.keys(schemas).length > 0;
938
1039
  return _objectSpread2(_objectSpread2({
939
1040
  openapi: "3.1.1",
940
1041
  jsonSchemaDialect: "https://spec.openapis.org/oas/3.1/dialect/base",
@@ -944,14 +1045,7 @@ function buildOpenAPIDocument(procedures, options, meta = { errorSchema: null })
944
1045
  }
945
1046
  }, ((_options$servers = options.servers) === null || _options$servers === void 0 ? void 0 : _options$servers.length) ? { servers: options.servers } : {}), {}, {
946
1047
  paths,
947
- components: _objectSpread2(_objectSpread2({}, hasNamedSchemas && meta.schemas ? { schemas: meta.schemas } : {}), {}, { responses: { Error: {
948
- description: "Error response",
949
- content: { "application/json": { schema: {
950
- type: "object",
951
- properties: { error: (_meta$errorSchema = meta.errorSchema) !== null && _meta$errorSchema !== void 0 ? _meta$errorSchema : DEFAULT_ERROR_SCHEMA },
952
- required: ["error"]
953
- } } }
954
- } } })
1048
+ components: _objectSpread2(_objectSpread2({}, hasNamedSchemas ? { schemas } : {}), {}, { responses: errorResponses })
955
1049
  });
956
1050
  }
957
1051
  /**
@@ -995,7 +1089,8 @@ async function generateOpenAPIDocument(routerFilePath, options = {}) {
995
1089
  procedures: [],
996
1090
  seen: /* @__PURE__ */ new Set(),
997
1091
  schemaCtx,
998
- runtimeDescriptions
1092
+ runtimeDescriptions,
1093
+ routerErrorType: getRouterErrorType(routerType, checker)
999
1094
  };
1000
1095
  walkType({
1001
1096
  type: routerType,