@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/cli.mjs +120 -26
- package/dist/index.cjs +122 -27
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +122 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
- package/src/generate.ts +226 -40
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
if (
|
|
460
|
-
|
|
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:
|
|
463
|
+
oneOf: distinctSchemas,
|
|
463
464
|
discriminator: { propertyName: discriminatorProp }
|
|
464
465
|
};
|
|
465
|
-
return { oneOf:
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
1025
|
+
var _meta$schemas, _meta$errorSchema, _options$title, _options$version, _options$servers;
|
|
927
1026
|
const paths = {};
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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 =
|
|
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
|
|
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,
|