adorn-api 1.0.10 → 1.0.12
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/README.md +318 -620
- package/dist/adapter/express/auth.d.ts +5 -0
- package/dist/adapter/express/auth.d.ts.map +1 -0
- package/dist/adapter/express/coercion.d.ts +22 -0
- package/dist/adapter/express/coercion.d.ts.map +1 -0
- package/dist/adapter/express/index.d.ts +3 -50
- package/dist/adapter/express/index.d.ts.map +1 -1
- package/dist/adapter/express/openapi.d.ts +11 -0
- package/dist/adapter/express/openapi.d.ts.map +1 -0
- package/dist/adapter/express/router.d.ts +4 -0
- package/dist/adapter/express/router.d.ts.map +1 -0
- package/dist/adapter/express/swagger.d.ts +4 -0
- package/dist/adapter/express/swagger.d.ts.map +1 -0
- package/dist/adapter/express/types.d.ts +64 -0
- package/dist/adapter/express/types.d.ts.map +1 -0
- package/dist/adapter/express/validation.d.ts +10 -0
- package/dist/adapter/express/validation.d.ts.map +1 -0
- package/dist/cli.cjs +330 -142
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +329 -141
- package/dist/cli.js.map +1 -1
- package/dist/compiler/manifest/emit.d.ts.map +1 -1
- package/dist/compiler/manifest/format.d.ts +1 -0
- package/dist/compiler/manifest/format.d.ts.map +1 -1
- package/dist/compiler/schema/openapi.d.ts.map +1 -1
- package/dist/compiler/schema/typeToJsonSchema.d.ts +7 -1
- package/dist/compiler/schema/typeToJsonSchema.d.ts.map +1 -1
- package/dist/express.cjs +618 -586
- package/dist/express.cjs.map +1 -1
- package/dist/express.js +615 -583
- package/dist/express.js.map +1 -1
- package/dist/http.d.ts +11 -9
- package/dist/http.d.ts.map +1 -1
- package/dist/index.cjs +2 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -9
- package/dist/index.js.map +1 -1
- package/dist/metal/applyListQuery.d.ts +27 -0
- package/dist/metal/applyListQuery.d.ts.map +1 -0
- package/dist/metal/index.cjs +59 -0
- package/dist/metal/index.cjs.map +1 -1
- package/dist/metal/index.d.ts +4 -0
- package/dist/metal/index.d.ts.map +1 -1
- package/dist/metal/index.js +55 -0
- package/dist/metal/index.js.map +1 -1
- package/dist/metal/listQuery.d.ts +7 -0
- package/dist/metal/listQuery.d.ts.map +1 -0
- package/dist/metal/queryOptions.d.ts +8 -0
- package/dist/metal/queryOptions.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/compiler/analyze/extractQueryStyle.d.ts +0 -8
- package/dist/compiler/analyze/extractQueryStyle.d.ts.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -263,13 +263,14 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
|
|
|
263
263
|
}
|
|
264
264
|
function isObjectType(type, checker) {
|
|
265
265
|
const objectFlags = (type.flags & import_typescript2.default.TypeFlags.Object) !== 0;
|
|
266
|
-
|
|
266
|
+
const intersectionFlags = (type.flags & import_typescript2.default.TypeFlags.Intersection) !== 0;
|
|
267
|
+
if (!objectFlags && !intersectionFlags) return false;
|
|
267
268
|
const symbol = type.getSymbol();
|
|
268
269
|
if (symbol?.getName() === "__object") return true;
|
|
269
270
|
const properties = checker.getPropertiesOfType(type);
|
|
270
271
|
if (properties.length > 0) return true;
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
272
|
+
const callSigs = type.getCallSignatures?.();
|
|
273
|
+
if (callSigs && callSigs.length > 0) return false;
|
|
273
274
|
return true;
|
|
274
275
|
}
|
|
275
276
|
function getTypeName(type) {
|
|
@@ -321,7 +322,7 @@ function unwrapPromiseTypeNode(typeNode) {
|
|
|
321
322
|
}
|
|
322
323
|
|
|
323
324
|
// src/compiler/schema/openapi.ts
|
|
324
|
-
var
|
|
325
|
+
var import_typescript5 = __toESM(require("typescript"), 1);
|
|
325
326
|
|
|
326
327
|
// src/compiler/schema/typeToJsonSchema.ts
|
|
327
328
|
var import_typescript3 = __toESM(require("typescript"), 1);
|
|
@@ -340,7 +341,7 @@ function typeToJsonSchema(type, ctx, typeNode) {
|
|
|
340
341
|
return { type: "string" };
|
|
341
342
|
}
|
|
342
343
|
if (type.flags & import_typescript3.default.TypeFlags.Number) {
|
|
343
|
-
return
|
|
344
|
+
return normalizeNumericType(type, checker, typeNode);
|
|
344
345
|
}
|
|
345
346
|
if (type.flags & import_typescript3.default.TypeFlags.Boolean) {
|
|
346
347
|
return { type: "boolean" };
|
|
@@ -602,9 +603,12 @@ function getBranchSchemaName(type, ctx) {
|
|
|
602
603
|
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
603
604
|
}
|
|
604
605
|
function handleObjectType(type, ctx, typeNode) {
|
|
605
|
-
const { checker, components, typeStack } = ctx;
|
|
606
|
+
const { checker, components, typeStack, mode } = ctx;
|
|
606
607
|
const symbol = type.getSymbol();
|
|
607
608
|
const typeName = symbol?.getName?.() ?? getTypeNameFromNode(typeNode, ctx);
|
|
609
|
+
if (isMetalOrmWrapperType(type, checker)) {
|
|
610
|
+
return handleMetalOrmWrapper(type, ctx);
|
|
611
|
+
}
|
|
608
612
|
if (typeName && typeName !== "__type") {
|
|
609
613
|
if (components.has(typeName)) {
|
|
610
614
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
@@ -617,7 +621,8 @@ function handleObjectType(type, ctx, typeNode) {
|
|
|
617
621
|
const schema = buildObjectSchema(type, ctx, typeNode);
|
|
618
622
|
if (typeName && typeName !== "__type") {
|
|
619
623
|
typeStack.delete(type);
|
|
620
|
-
|
|
624
|
+
const existing = components.get(typeName);
|
|
625
|
+
if (!existing) {
|
|
621
626
|
components.set(typeName, schema);
|
|
622
627
|
}
|
|
623
628
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
@@ -639,22 +644,44 @@ function getExplicitTypeNameFromNode(typeNode) {
|
|
|
639
644
|
}
|
|
640
645
|
return null;
|
|
641
646
|
}
|
|
647
|
+
function shouldBeIntegerType(typeName) {
|
|
648
|
+
if (!typeName) return false;
|
|
649
|
+
const lower = typeName.toLowerCase();
|
|
650
|
+
return lower === "id" || lower.endsWith("id") || lower === "primarykey" || lower === "pk";
|
|
651
|
+
}
|
|
652
|
+
function normalizeNumericType(type, checker, typeNode) {
|
|
653
|
+
const typeName = getExplicitTypeNameFromNode(typeNode) ?? null;
|
|
654
|
+
const symbol = getEffectiveSymbol(type, checker);
|
|
655
|
+
const symbolName = symbol?.getName() ?? null;
|
|
656
|
+
if (shouldBeIntegerType(typeName) || shouldBeIntegerType(symbolName)) {
|
|
657
|
+
return { type: "integer" };
|
|
658
|
+
}
|
|
659
|
+
return { type: "number" };
|
|
660
|
+
}
|
|
642
661
|
function getTypeNameFromNode(typeNode, ctx) {
|
|
643
662
|
const explicitName = getExplicitTypeNameFromNode(typeNode);
|
|
644
663
|
if (explicitName) return explicitName;
|
|
645
664
|
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
646
665
|
}
|
|
647
666
|
function buildObjectSchema(type, ctx, typeNode) {
|
|
648
|
-
const { checker } = ctx;
|
|
667
|
+
const { checker, mode } = ctx;
|
|
649
668
|
const properties = {};
|
|
650
669
|
const required = [];
|
|
651
670
|
const props = checker.getPropertiesOfType(type);
|
|
652
671
|
for (const prop of props) {
|
|
653
672
|
const propName = prop.getName();
|
|
673
|
+
if (isIteratorOrSymbolProperty(propName)) {
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
654
676
|
const propType = checker.getTypeOfSymbol(prop);
|
|
677
|
+
if (isMethodLike(propType)) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
655
680
|
const isOptional = !!(prop.flags & import_typescript3.default.SymbolFlags.Optional);
|
|
681
|
+
const isRelation = isMetalOrmWrapperType(propType, checker);
|
|
656
682
|
properties[propName] = typeToJsonSchema(propType, ctx);
|
|
657
|
-
|
|
683
|
+
const shouldRequire = mode === "response" ? !isRelation && !isOptional : !isOptional;
|
|
684
|
+
if (shouldRequire) {
|
|
658
685
|
required.push(propName);
|
|
659
686
|
}
|
|
660
687
|
}
|
|
@@ -693,6 +720,56 @@ function getRecordValueType(type, checker) {
|
|
|
693
720
|
}
|
|
694
721
|
return null;
|
|
695
722
|
}
|
|
723
|
+
var METAL_ORM_WRAPPER_NAMES = ["HasManyCollection", "ManyToManyCollection", "BelongsToReference", "HasOneReference"];
|
|
724
|
+
function isMetalOrmWrapperType(type, checker) {
|
|
725
|
+
const aliasSymbol = type.aliasSymbol || type.symbol;
|
|
726
|
+
if (!aliasSymbol) return false;
|
|
727
|
+
return METAL_ORM_WRAPPER_NAMES.includes(aliasSymbol.getName());
|
|
728
|
+
}
|
|
729
|
+
function getWrapperTypeName(type, checker) {
|
|
730
|
+
const symbol = getEffectiveSymbol(type, checker);
|
|
731
|
+
if (!symbol) return null;
|
|
732
|
+
const name = symbol.getName();
|
|
733
|
+
return METAL_ORM_WRAPPER_NAMES.includes(name) ? name : null;
|
|
734
|
+
}
|
|
735
|
+
function getEffectiveSymbol(type, checker) {
|
|
736
|
+
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
737
|
+
if (aliasSymbol && aliasSymbol.flags & import_typescript3.default.SymbolFlags.Alias) {
|
|
738
|
+
return checker.getAliasedSymbol(aliasSymbol);
|
|
739
|
+
}
|
|
740
|
+
return type.getSymbol() ?? null;
|
|
741
|
+
}
|
|
742
|
+
function isMethodLike(type) {
|
|
743
|
+
const callSigs = type.getCallSignatures?.();
|
|
744
|
+
return !!(callSigs && callSigs.length > 0);
|
|
745
|
+
}
|
|
746
|
+
function isIteratorOrSymbolProperty(propName) {
|
|
747
|
+
return propName.startsWith("__@") || propName.startsWith("[") || propName === Symbol.iterator.toString();
|
|
748
|
+
}
|
|
749
|
+
function handleMetalOrmWrapper(type, ctx) {
|
|
750
|
+
const typeRef = type;
|
|
751
|
+
const typeArgs = typeRef.typeArguments;
|
|
752
|
+
const targetType = typeArgs?.[0] ?? null;
|
|
753
|
+
const wrapperName = getWrapperTypeName(type, ctx.checker);
|
|
754
|
+
if (!wrapperName) return {};
|
|
755
|
+
const wrapperRel = { wrapper: wrapperName };
|
|
756
|
+
if (wrapperName === "HasManyCollection" || wrapperName === "ManyToManyCollection") {
|
|
757
|
+
const items = targetType ? typeToJsonSchema(targetType, ctx) : {};
|
|
758
|
+
if (wrapperName === "ManyToManyCollection" && typeArgs?.[1]) {
|
|
759
|
+
wrapperRel.pivot = typeArgs[1];
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
type: "array",
|
|
763
|
+
items,
|
|
764
|
+
"x-metal-orm-rel": wrapperRel
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
const targetSchema = targetType ? typeToJsonSchema(targetType, ctx) : {};
|
|
768
|
+
return {
|
|
769
|
+
...targetSchema,
|
|
770
|
+
"x-metal-orm-rel": wrapperRel
|
|
771
|
+
};
|
|
772
|
+
}
|
|
696
773
|
|
|
697
774
|
// src/compiler/schema/extractAnnotations.ts
|
|
698
775
|
var import_typescript4 = __toESM(require("typescript"), 1);
|
|
@@ -840,56 +917,6 @@ function mergeFragments(base, ...frags) {
|
|
|
840
917
|
return result;
|
|
841
918
|
}
|
|
842
919
|
|
|
843
|
-
// src/compiler/analyze/extractQueryStyle.ts
|
|
844
|
-
var import_typescript5 = __toESM(require("typescript"), 1);
|
|
845
|
-
function extractQueryStyleOptions(checker, method) {
|
|
846
|
-
if (!import_typescript5.default.canHaveDecorators(method)) return null;
|
|
847
|
-
const decorators = import_typescript5.default.getDecorators(method);
|
|
848
|
-
if (!decorators || decorators.length === 0) return null;
|
|
849
|
-
for (const decorator of decorators) {
|
|
850
|
-
const expr = decorator.expression;
|
|
851
|
-
const isCall = import_typescript5.default.isCallExpression(expr);
|
|
852
|
-
const callee = isCall ? expr.expression : expr;
|
|
853
|
-
const args = isCall ? expr.arguments : import_typescript5.default.factory.createNodeArray([]);
|
|
854
|
-
const sym = checker.getSymbolAtLocation(callee);
|
|
855
|
-
if (!sym) continue;
|
|
856
|
-
const resolved = sym.flags & import_typescript5.default.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
|
|
857
|
-
const name = resolved.getName();
|
|
858
|
-
if (name !== "QueryStyle") continue;
|
|
859
|
-
const optsNode = args[0];
|
|
860
|
-
if (!optsNode || !import_typescript5.default.isObjectLiteralExpression(optsNode)) {
|
|
861
|
-
return {};
|
|
862
|
-
}
|
|
863
|
-
return parseQueryStyleOptions(optsNode);
|
|
864
|
-
}
|
|
865
|
-
return null;
|
|
866
|
-
}
|
|
867
|
-
function parseQueryStyleOptions(node) {
|
|
868
|
-
const opts = {};
|
|
869
|
-
for (const prop of node.properties) {
|
|
870
|
-
if (!import_typescript5.default.isPropertyAssignment(prop)) continue;
|
|
871
|
-
const name = getPropName(prop.name);
|
|
872
|
-
if (!name) continue;
|
|
873
|
-
if (name === "style" && import_typescript5.default.isStringLiteral(prop.initializer)) {
|
|
874
|
-
const style = prop.initializer.text;
|
|
875
|
-
opts.style = style;
|
|
876
|
-
} else if (name === "explode" && isBooleanLiteral(prop.initializer)) {
|
|
877
|
-
opts.explode = prop.initializer.kind === import_typescript5.default.SyntaxKind.TrueKeyword;
|
|
878
|
-
} else if (name === "allowReserved" && isBooleanLiteral(prop.initializer)) {
|
|
879
|
-
opts.allowReserved = prop.initializer.kind === import_typescript5.default.SyntaxKind.TrueKeyword;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
return opts;
|
|
883
|
-
}
|
|
884
|
-
function getPropName(name) {
|
|
885
|
-
if (import_typescript5.default.isIdentifier(name)) return name.text;
|
|
886
|
-
if (import_typescript5.default.isStringLiteral(name)) return name.text;
|
|
887
|
-
return null;
|
|
888
|
-
}
|
|
889
|
-
function isBooleanLiteral(node) {
|
|
890
|
-
return node.kind === import_typescript5.default.SyntaxKind.TrueKeyword || node.kind === import_typescript5.default.SyntaxKind.FalseKeyword;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
920
|
// src/compiler/schema/openapi.ts
|
|
894
921
|
function generateOpenAPI(controllers, checker, options = {}) {
|
|
895
922
|
const components = /* @__PURE__ */ new Map();
|
|
@@ -897,7 +924,8 @@ function generateOpenAPI(controllers, checker, options = {}) {
|
|
|
897
924
|
checker,
|
|
898
925
|
components,
|
|
899
926
|
typeStack: /* @__PURE__ */ new Set(),
|
|
900
|
-
typeNameStack: []
|
|
927
|
+
typeNameStack: [],
|
|
928
|
+
mode: "response"
|
|
901
929
|
};
|
|
902
930
|
const paths = {};
|
|
903
931
|
for (const controller of controllers) {
|
|
@@ -925,7 +953,11 @@ function generateOpenAPI(controllers, checker, options = {}) {
|
|
|
925
953
|
function convertToOpenApiPath(basePath, path4) {
|
|
926
954
|
const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
927
955
|
const converted = path4.replace(/:([^/]+)/g, "{$1}");
|
|
928
|
-
|
|
956
|
+
let fullPath = base + converted || "/";
|
|
957
|
+
if (fullPath.endsWith("/") && fullPath !== "/") {
|
|
958
|
+
fullPath = fullPath.slice(0, -1);
|
|
959
|
+
}
|
|
960
|
+
return fullPath;
|
|
929
961
|
}
|
|
930
962
|
function buildOperation(operation, ctx, controllerConsumes) {
|
|
931
963
|
const op = {
|
|
@@ -940,7 +972,8 @@ function buildOperation(operation, ctx, controllerConsumes) {
|
|
|
940
972
|
if (parameters.length > 0) {
|
|
941
973
|
op.parameters = parameters;
|
|
942
974
|
}
|
|
943
|
-
const
|
|
975
|
+
const responseCtx = { ...ctx, mode: "response" };
|
|
976
|
+
const responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
|
|
944
977
|
const status = operation.httpMethod === "POST" ? 201 : 200;
|
|
945
978
|
op.responses[status] = {
|
|
946
979
|
description: status === 201 ? "Created" : "OK",
|
|
@@ -953,8 +986,9 @@ function buildOperation(operation, ctx, controllerConsumes) {
|
|
|
953
986
|
if (["POST", "PUT", "PATCH"].includes(operation.httpMethod) && operation.bodyParamIndex !== null) {
|
|
954
987
|
const bodyParam = operation.parameters[operation.bodyParamIndex];
|
|
955
988
|
if (bodyParam) {
|
|
956
|
-
|
|
957
|
-
bodySchema =
|
|
989
|
+
const requestCtx = { ...ctx, mode: "request" };
|
|
990
|
+
let bodySchema = typeToJsonSchema(bodyParam.type, requestCtx);
|
|
991
|
+
bodySchema = mergeBodySchemaAnnotations(bodyParam, requestCtx, bodySchema);
|
|
958
992
|
const contentType = operation.bodyContentType ?? controllerConsumes?.[0] ?? "application/json";
|
|
959
993
|
const requestBody = {
|
|
960
994
|
required: !bodyParam.isOptional,
|
|
@@ -981,12 +1015,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
|
981
1015
|
const declarations = typeSymbol.getDeclarations();
|
|
982
1016
|
if (!declarations || declarations.length === 0) return schema;
|
|
983
1017
|
const classDecl = declarations[0];
|
|
984
|
-
if (!
|
|
1018
|
+
if (!import_typescript5.default.isClassDeclaration(classDecl)) return schema;
|
|
985
1019
|
const result = { ...schema };
|
|
986
1020
|
const props = { ...result.properties };
|
|
987
1021
|
for (const member of classDecl.members) {
|
|
988
|
-
if (!
|
|
989
|
-
const propName =
|
|
1022
|
+
if (!import_typescript5.default.isPropertyDeclaration(member) || !member.name) continue;
|
|
1023
|
+
const propName = import_typescript5.default.isIdentifier(member.name) ? member.name.text : null;
|
|
990
1024
|
if (!propName) continue;
|
|
991
1025
|
if (!props[propName]) continue;
|
|
992
1026
|
const frags = extractPropertySchemaFragments(ctx.checker, member);
|
|
@@ -1008,48 +1042,124 @@ function buildPathParameters(operation, ctx, parameters) {
|
|
|
1008
1042
|
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
1009
1043
|
}
|
|
1010
1044
|
}
|
|
1045
|
+
const schema = paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema;
|
|
1011
1046
|
parameters.push({
|
|
1012
1047
|
name: param.name,
|
|
1013
1048
|
in: "path",
|
|
1014
1049
|
required: !param.isOptional,
|
|
1015
|
-
schema
|
|
1050
|
+
schema
|
|
1016
1051
|
});
|
|
1017
1052
|
}
|
|
1018
1053
|
}
|
|
1019
1054
|
}
|
|
1055
|
+
function isObjectLikeSchema(schema, ctx) {
|
|
1056
|
+
const resolved = resolveSchemaRef(schema, ctx.components);
|
|
1057
|
+
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1058
|
+
return true;
|
|
1059
|
+
}
|
|
1060
|
+
if (resolved.allOf) {
|
|
1061
|
+
for (const branch of resolved.allOf) {
|
|
1062
|
+
if (isObjectLikeSchema(branch, ctx)) {
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (resolved.type === "array" && resolved.items) {
|
|
1068
|
+
const itemsSchema = resolveSchemaRef(resolved.items, ctx.components);
|
|
1069
|
+
return isObjectLikeSchema(itemsSchema, ctx);
|
|
1070
|
+
}
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
function resolveSchemaRef(schema, components) {
|
|
1074
|
+
const ref = schema.$ref;
|
|
1075
|
+
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
1076
|
+
return schema;
|
|
1077
|
+
}
|
|
1078
|
+
const name = ref.replace("#/components/schemas/", "");
|
|
1079
|
+
const next = components.get(name);
|
|
1080
|
+
if (!next) return schema;
|
|
1081
|
+
return resolveSchemaRef(next, components);
|
|
1082
|
+
}
|
|
1083
|
+
function resolveAndCollectObjectProps(schema, components) {
|
|
1084
|
+
const resolved = resolveSchemaRef(schema, components);
|
|
1085
|
+
const properties = {};
|
|
1086
|
+
const required = [];
|
|
1087
|
+
const processSchema = (s) => {
|
|
1088
|
+
const current = resolveSchemaRef(s, components);
|
|
1089
|
+
if (current.properties) {
|
|
1090
|
+
for (const [key, val] of Object.entries(current.properties)) {
|
|
1091
|
+
if (!properties[key]) {
|
|
1092
|
+
properties[key] = val;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (current.required) {
|
|
1097
|
+
for (const req of current.required) {
|
|
1098
|
+
if (!required.includes(req)) {
|
|
1099
|
+
required.push(req);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
if (current.allOf) {
|
|
1104
|
+
for (const branch of current.allOf) {
|
|
1105
|
+
processSchema(branch);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
processSchema(resolved);
|
|
1110
|
+
return { properties, required };
|
|
1111
|
+
}
|
|
1020
1112
|
function buildQueryParameters(operation, ctx, parameters) {
|
|
1021
1113
|
if (operation.queryObjectParamIndex !== null) {
|
|
1022
1114
|
const queryParam = operation.parameters[operation.queryObjectParamIndex];
|
|
1023
1115
|
if (!queryParam) return;
|
|
1024
|
-
const queryStyle = extractQueryStyleOptions(ctx.checker, operation.methodDeclaration);
|
|
1025
1116
|
const querySchema = typeToJsonSchema(queryParam.type, ctx);
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
schema: querySchema.$ref ? { $ref: querySchema.$ref } : querySchema,
|
|
1033
|
-
style: "deepObject",
|
|
1034
|
-
explode
|
|
1035
|
-
};
|
|
1036
|
-
if (queryStyle.allowReserved !== void 0) {
|
|
1037
|
-
deepParam.allowReserved = queryStyle.allowReserved;
|
|
1038
|
-
}
|
|
1039
|
-
parameters.push(deepParam);
|
|
1040
|
-
} else {
|
|
1041
|
-
if (!querySchema.properties) return;
|
|
1042
|
-
const queryObjProps = querySchema.properties;
|
|
1043
|
-
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1044
|
-
const isRequired = querySchema.required?.includes(propName) ?? false;
|
|
1045
|
-
const serialization = determineQuerySerialization(propSchema.type);
|
|
1117
|
+
const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps(querySchema, ctx.components);
|
|
1118
|
+
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1119
|
+
const isRequired = queryRequired.includes(propName) ?? false;
|
|
1120
|
+
const isObjectLike = isObjectLikeSchema(propSchema, ctx);
|
|
1121
|
+
const serialization = determineQuerySerialization(propSchema.type);
|
|
1122
|
+
if (isObjectLike) {
|
|
1046
1123
|
parameters.push({
|
|
1047
1124
|
name: propName,
|
|
1048
1125
|
in: "query",
|
|
1049
1126
|
required: isRequired,
|
|
1050
|
-
|
|
1051
|
-
|
|
1127
|
+
content: {
|
|
1128
|
+
"application/json": {
|
|
1129
|
+
schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
1132
|
+
description: `URL-encoded JSON. Example: ${propName}=${encodeURIComponent(JSON.stringify({ example: "value" }))}`
|
|
1052
1133
|
});
|
|
1134
|
+
} else {
|
|
1135
|
+
const paramDef = {
|
|
1136
|
+
name: propName,
|
|
1137
|
+
in: "query",
|
|
1138
|
+
required: isRequired,
|
|
1139
|
+
schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
|
|
1140
|
+
};
|
|
1141
|
+
if (propName === "page") {
|
|
1142
|
+
paramDef.schema = { type: "integer", default: 1, minimum: 1 };
|
|
1143
|
+
} else if (propName === "pageSize") {
|
|
1144
|
+
paramDef.schema = { type: "integer", default: 10, minimum: 1 };
|
|
1145
|
+
} else if (propName === "totalItems") {
|
|
1146
|
+
paramDef.schema = { type: "integer", minimum: 0 };
|
|
1147
|
+
} else if (propName === "sort") {
|
|
1148
|
+
paramDef.schema = {
|
|
1149
|
+
oneOf: [
|
|
1150
|
+
{ type: "string" },
|
|
1151
|
+
{ type: "array", items: { type: "string" } }
|
|
1152
|
+
]
|
|
1153
|
+
};
|
|
1154
|
+
} else if (propName === "q") {
|
|
1155
|
+
paramDef.schema = { type: "string" };
|
|
1156
|
+
} else if (propName === "hasComments") {
|
|
1157
|
+
paramDef.schema = { type: "boolean" };
|
|
1158
|
+
}
|
|
1159
|
+
if (Object.keys(serialization).length > 0) {
|
|
1160
|
+
Object.assign(paramDef, serialization);
|
|
1161
|
+
}
|
|
1162
|
+
parameters.push(paramDef);
|
|
1053
1163
|
}
|
|
1054
1164
|
}
|
|
1055
1165
|
}
|
|
@@ -1063,14 +1173,28 @@ function buildQueryParameters(operation, ctx, parameters) {
|
|
|
1063
1173
|
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
1064
1174
|
}
|
|
1065
1175
|
}
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1176
|
+
const isObjectLike = isObjectLikeSchema(paramSchema, ctx);
|
|
1177
|
+
if (isObjectLike) {
|
|
1178
|
+
parameters.push({
|
|
1179
|
+
name: param.name,
|
|
1180
|
+
in: "query",
|
|
1181
|
+
required: !param.isOptional,
|
|
1182
|
+
content: {
|
|
1183
|
+
"application/json": {
|
|
1184
|
+
schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
} else {
|
|
1189
|
+
const serialization = determineQuerySerialization(paramSchema.type);
|
|
1190
|
+
parameters.push({
|
|
1191
|
+
name: param.name,
|
|
1192
|
+
in: "query",
|
|
1193
|
+
required: !param.isOptional,
|
|
1194
|
+
schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema,
|
|
1195
|
+
...Object.keys(serialization).length > 0 ? serialization : {}
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1074
1198
|
}
|
|
1075
1199
|
}
|
|
1076
1200
|
}
|
|
@@ -1120,14 +1244,15 @@ function buildCookieParameters(operation, ctx, parameters) {
|
|
|
1120
1244
|
}
|
|
1121
1245
|
|
|
1122
1246
|
// src/compiler/manifest/emit.ts
|
|
1123
|
-
var
|
|
1247
|
+
var import_typescript6 = __toESM(require("typescript"), 1);
|
|
1124
1248
|
function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
|
|
1125
1249
|
const components = /* @__PURE__ */ new Map();
|
|
1126
1250
|
const ctx = {
|
|
1127
1251
|
checker,
|
|
1128
1252
|
components,
|
|
1129
1253
|
typeStack: /* @__PURE__ */ new Set(),
|
|
1130
|
-
typeNameStack: []
|
|
1254
|
+
typeNameStack: [],
|
|
1255
|
+
mode: "request"
|
|
1131
1256
|
};
|
|
1132
1257
|
const controllerEntries = controllers.map((ctrl) => ({
|
|
1133
1258
|
controllerId: ctrl.className,
|
|
@@ -1141,7 +1266,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
|
|
|
1141
1266
|
generator: {
|
|
1142
1267
|
name: "adorn-api",
|
|
1143
1268
|
version,
|
|
1144
|
-
typescript:
|
|
1269
|
+
typescript: import_typescript6.default.version
|
|
1145
1270
|
},
|
|
1146
1271
|
schemas: {
|
|
1147
1272
|
kind: "openapi-3.1",
|
|
@@ -1152,6 +1277,63 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
|
|
|
1152
1277
|
controllers: controllerEntries
|
|
1153
1278
|
};
|
|
1154
1279
|
}
|
|
1280
|
+
function resolveSchemaRef2(schema, components) {
|
|
1281
|
+
const ref = schema.$ref;
|
|
1282
|
+
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
1283
|
+
return schema;
|
|
1284
|
+
}
|
|
1285
|
+
const name = ref.replace("#/components/schemas/", "");
|
|
1286
|
+
const next = components.get(name);
|
|
1287
|
+
if (!next) return schema;
|
|
1288
|
+
return resolveSchemaRef2(next, components);
|
|
1289
|
+
}
|
|
1290
|
+
function resolveAndCollectObjectProps2(schema, components) {
|
|
1291
|
+
const resolved = resolveSchemaRef2(schema, components);
|
|
1292
|
+
const properties = {};
|
|
1293
|
+
const required = [];
|
|
1294
|
+
const processSchema = (s) => {
|
|
1295
|
+
const current = resolveSchemaRef2(s, components);
|
|
1296
|
+
if (current.properties) {
|
|
1297
|
+
for (const [key, val] of Object.entries(current.properties)) {
|
|
1298
|
+
if (!properties[key]) {
|
|
1299
|
+
properties[key] = val;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (current.required) {
|
|
1304
|
+
for (const req of current.required) {
|
|
1305
|
+
if (!required.includes(req)) {
|
|
1306
|
+
required.push(req);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
if (current.allOf) {
|
|
1311
|
+
for (const branch of current.allOf) {
|
|
1312
|
+
processSchema(branch);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
processSchema(resolved);
|
|
1317
|
+
return { properties, required };
|
|
1318
|
+
}
|
|
1319
|
+
function isObjectLikeSchema2(schema, components) {
|
|
1320
|
+
const resolved = resolveSchemaRef2(schema, components);
|
|
1321
|
+
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1322
|
+
return true;
|
|
1323
|
+
}
|
|
1324
|
+
if (resolved.allOf) {
|
|
1325
|
+
for (const branch of resolved.allOf) {
|
|
1326
|
+
if (isObjectLikeSchema2(branch, components)) {
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
if (resolved.type === "array" && resolved.items) {
|
|
1332
|
+
const itemsSchema = resolveSchemaRef2(resolved.items, components);
|
|
1333
|
+
return isObjectLikeSchema2(itemsSchema, components);
|
|
1334
|
+
}
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1155
1337
|
function buildOperationEntry(op, ctx) {
|
|
1156
1338
|
const args = {
|
|
1157
1339
|
body: null,
|
|
@@ -1225,53 +1407,39 @@ function buildPathArgs(op, ctx, args) {
|
|
|
1225
1407
|
function buildQueryArgs(op, ctx, args) {
|
|
1226
1408
|
if (op.queryObjectParamIndex !== null) {
|
|
1227
1409
|
const queryParam = op.parameters[op.queryObjectParamIndex];
|
|
1228
|
-
if (queryParam)
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
schemaRef,
|
|
1238
|
-
schemaType: querySchema.type,
|
|
1239
|
-
serialization: {
|
|
1240
|
-
style: "deepObject",
|
|
1241
|
-
explode: queryStyle.explode ?? true,
|
|
1242
|
-
allowReserved: queryStyle.allowReserved
|
|
1243
|
-
}
|
|
1244
|
-
});
|
|
1245
|
-
} else {
|
|
1246
|
-
if (!querySchema.properties) return;
|
|
1247
|
-
for (const [propName, propSchema] of Object.entries(querySchema.properties)) {
|
|
1248
|
-
const isRequired = querySchema.required?.includes(propName) ?? false;
|
|
1249
|
-
let schemaRef = propSchema.$ref;
|
|
1250
|
-
if (!schemaRef) {
|
|
1251
|
-
schemaRef = "#/components/schemas/InlineQueryParam";
|
|
1252
|
-
}
|
|
1253
|
-
args.query.push({
|
|
1254
|
-
name: propName,
|
|
1255
|
-
index: queryParam.index,
|
|
1256
|
-
required: !isRequired,
|
|
1257
|
-
schemaRef,
|
|
1258
|
-
schemaType: propSchema.type
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1410
|
+
if (!queryParam) return;
|
|
1411
|
+
const querySchema = typeToJsonSchema(queryParam.type, ctx);
|
|
1412
|
+
const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps2(querySchema, ctx.components);
|
|
1413
|
+
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1414
|
+
const isRequired = queryRequired.includes(propName) ?? false;
|
|
1415
|
+
const isObjectLike = isObjectLikeSchema2(propSchema, ctx.components);
|
|
1416
|
+
let schemaRef = propSchema.$ref;
|
|
1417
|
+
if (!schemaRef) {
|
|
1418
|
+
schemaRef = "#/components/schemas/InlineQueryParam";
|
|
1261
1419
|
}
|
|
1420
|
+
args.query.push({
|
|
1421
|
+
name: propName,
|
|
1422
|
+
index: queryParam.index,
|
|
1423
|
+
required: !isRequired,
|
|
1424
|
+
schemaRef,
|
|
1425
|
+
schemaType: propSchema.type,
|
|
1426
|
+
content: isObjectLike ? "application/json" : void 0
|
|
1427
|
+
});
|
|
1262
1428
|
}
|
|
1263
1429
|
}
|
|
1264
1430
|
for (const paramIndex of op.queryParamIndices) {
|
|
1265
1431
|
const param = op.parameters[paramIndex];
|
|
1266
1432
|
if (param) {
|
|
1267
1433
|
const paramSchema = typeToJsonSchema(param.type, ctx);
|
|
1434
|
+
const isObjectLike = isObjectLikeSchema2(paramSchema, ctx.components);
|
|
1268
1435
|
const schemaRef = paramSchema.$ref ?? "#/components/schemas/InlineQueryParam";
|
|
1269
1436
|
args.query.push({
|
|
1270
1437
|
name: param.name,
|
|
1271
1438
|
index: param.index,
|
|
1272
1439
|
required: !param.isOptional,
|
|
1273
1440
|
schemaRef,
|
|
1274
|
-
schemaType: paramSchema.type
|
|
1441
|
+
schemaType: paramSchema.type,
|
|
1442
|
+
content: isObjectLike ? "application/json" : void 0
|
|
1275
1443
|
});
|
|
1276
1444
|
}
|
|
1277
1445
|
}
|
|
@@ -1460,7 +1628,7 @@ export function validateResponse(operationId, status, contentType, data) {
|
|
|
1460
1628
|
// src/compiler/cache/isStale.ts
|
|
1461
1629
|
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
1462
1630
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
1463
|
-
var
|
|
1631
|
+
var import_typescript7 = require("typescript");
|
|
1464
1632
|
var import_meta = {};
|
|
1465
1633
|
function readJson(p) {
|
|
1466
1634
|
try {
|
|
@@ -1567,7 +1735,7 @@ async function isStale(params) {
|
|
|
1567
1735
|
// src/compiler/cache/writeCache.ts
|
|
1568
1736
|
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
1569
1737
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
1570
|
-
var
|
|
1738
|
+
var import_typescript8 = __toESM(require("typescript"), 1);
|
|
1571
1739
|
function statMtimeMs2(p) {
|
|
1572
1740
|
return import_node_fs4.default.statSync(p).mtimeMs;
|
|
1573
1741
|
}
|
|
@@ -1600,7 +1768,7 @@ function writeCache(params) {
|
|
|
1600
1768
|
generator: {
|
|
1601
1769
|
name: "adorn-api",
|
|
1602
1770
|
version: params.adornVersion,
|
|
1603
|
-
typescript:
|
|
1771
|
+
typescript: import_typescript8.default.version
|
|
1604
1772
|
},
|
|
1605
1773
|
project: {
|
|
1606
1774
|
tsconfigPath: params.tsconfigAbs,
|
|
@@ -1613,7 +1781,7 @@ function writeCache(params) {
|
|
|
1613
1781
|
}
|
|
1614
1782
|
|
|
1615
1783
|
// src/cli.ts
|
|
1616
|
-
var
|
|
1784
|
+
var import_typescript9 = __toESM(require("typescript"), 1);
|
|
1617
1785
|
var import_node_process = __toESM(require("process"), 1);
|
|
1618
1786
|
var ADORN_VERSION = "0.1.0";
|
|
1619
1787
|
function log(msg) {
|
|
@@ -1624,6 +1792,26 @@ function debug(...args) {
|
|
|
1624
1792
|
console.error("[adorn-api]", ...args);
|
|
1625
1793
|
}
|
|
1626
1794
|
}
|
|
1795
|
+
function sanitizeForJson(obj) {
|
|
1796
|
+
if (obj === null || obj === void 0) return obj;
|
|
1797
|
+
if (typeof obj !== "object") return obj;
|
|
1798
|
+
if (Array.isArray(obj)) {
|
|
1799
|
+
return obj.map((item) => sanitizeForJson(item));
|
|
1800
|
+
}
|
|
1801
|
+
const result = {};
|
|
1802
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1803
|
+
if (key.startsWith("__@") || key.startsWith("[")) continue;
|
|
1804
|
+
if (typeof value === "function") continue;
|
|
1805
|
+
if (value !== null && typeof value === "object") {
|
|
1806
|
+
const typeName = value.constructor?.name;
|
|
1807
|
+
if (typeName && !["Object", "Array", "String", "Number", "Boolean", "Date", "RegExp"].includes(typeName)) {
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
result[key] = sanitizeForJson(value);
|
|
1812
|
+
}
|
|
1813
|
+
return result;
|
|
1814
|
+
}
|
|
1627
1815
|
async function buildCommand(args) {
|
|
1628
1816
|
const projectIndex = args.indexOf("-p");
|
|
1629
1817
|
const projectPath = projectIndex !== -1 ? args[projectIndex + 1] : "./tsconfig.json";
|
|
@@ -1641,7 +1829,7 @@ async function buildCommand(args) {
|
|
|
1641
1829
|
outDir: outputDir,
|
|
1642
1830
|
project: projectPath,
|
|
1643
1831
|
adornVersion: ADORN_VERSION,
|
|
1644
|
-
typescriptVersion:
|
|
1832
|
+
typescriptVersion: import_typescript9.default.version
|
|
1645
1833
|
});
|
|
1646
1834
|
if (!stale.stale) {
|
|
1647
1835
|
log("adorn-api: artifacts up-to-date");
|
|
@@ -1662,7 +1850,7 @@ async function buildCommand(args) {
|
|
|
1662
1850
|
const openapi = generateOpenAPI(controllers, checker, { title: "API", version: "1.0.0" });
|
|
1663
1851
|
const manifest = generateManifest(controllers, checker, ADORN_VERSION, validationMode);
|
|
1664
1852
|
(0, import_node_fs5.mkdirSync)(outputPath, { recursive: true });
|
|
1665
|
-
(0, import_node_fs5.writeFileSync)((0, import_node_path5.resolve)(outputPath, "openapi.json"), JSON.stringify(openapi, null, 2));
|
|
1853
|
+
(0, import_node_fs5.writeFileSync)((0, import_node_path5.resolve)(outputPath, "openapi.json"), JSON.stringify(sanitizeForJson(openapi), null, 2));
|
|
1666
1854
|
(0, import_node_fs5.writeFileSync)((0, import_node_path5.resolve)(outputPath, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
1667
1855
|
if (validationMode === "precompiled") {
|
|
1668
1856
|
log("Generating precompiled validators...");
|