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.js
CHANGED
|
@@ -246,13 +246,14 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
|
|
|
246
246
|
}
|
|
247
247
|
function isObjectType(type, checker) {
|
|
248
248
|
const objectFlags = (type.flags & ts2.TypeFlags.Object) !== 0;
|
|
249
|
-
|
|
249
|
+
const intersectionFlags = (type.flags & ts2.TypeFlags.Intersection) !== 0;
|
|
250
|
+
if (!objectFlags && !intersectionFlags) return false;
|
|
250
251
|
const symbol = type.getSymbol();
|
|
251
252
|
if (symbol?.getName() === "__object") return true;
|
|
252
253
|
const properties = checker.getPropertiesOfType(type);
|
|
253
254
|
if (properties.length > 0) return true;
|
|
254
|
-
const
|
|
255
|
-
if (
|
|
255
|
+
const callSigs = type.getCallSignatures?.();
|
|
256
|
+
if (callSigs && callSigs.length > 0) return false;
|
|
256
257
|
return true;
|
|
257
258
|
}
|
|
258
259
|
function getTypeName(type) {
|
|
@@ -304,7 +305,7 @@ function unwrapPromiseTypeNode(typeNode) {
|
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
// src/compiler/schema/openapi.ts
|
|
307
|
-
import
|
|
308
|
+
import ts5 from "typescript";
|
|
308
309
|
|
|
309
310
|
// src/compiler/schema/typeToJsonSchema.ts
|
|
310
311
|
import ts3 from "typescript";
|
|
@@ -323,7 +324,7 @@ function typeToJsonSchema(type, ctx, typeNode) {
|
|
|
323
324
|
return { type: "string" };
|
|
324
325
|
}
|
|
325
326
|
if (type.flags & ts3.TypeFlags.Number) {
|
|
326
|
-
return
|
|
327
|
+
return normalizeNumericType(type, checker, typeNode);
|
|
327
328
|
}
|
|
328
329
|
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
329
330
|
return { type: "boolean" };
|
|
@@ -585,9 +586,12 @@ function getBranchSchemaName(type, ctx) {
|
|
|
585
586
|
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
586
587
|
}
|
|
587
588
|
function handleObjectType(type, ctx, typeNode) {
|
|
588
|
-
const { checker, components, typeStack } = ctx;
|
|
589
|
+
const { checker, components, typeStack, mode } = ctx;
|
|
589
590
|
const symbol = type.getSymbol();
|
|
590
591
|
const typeName = symbol?.getName?.() ?? getTypeNameFromNode(typeNode, ctx);
|
|
592
|
+
if (isMetalOrmWrapperType(type, checker)) {
|
|
593
|
+
return handleMetalOrmWrapper(type, ctx);
|
|
594
|
+
}
|
|
591
595
|
if (typeName && typeName !== "__type") {
|
|
592
596
|
if (components.has(typeName)) {
|
|
593
597
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
@@ -600,7 +604,8 @@ function handleObjectType(type, ctx, typeNode) {
|
|
|
600
604
|
const schema = buildObjectSchema(type, ctx, typeNode);
|
|
601
605
|
if (typeName && typeName !== "__type") {
|
|
602
606
|
typeStack.delete(type);
|
|
603
|
-
|
|
607
|
+
const existing = components.get(typeName);
|
|
608
|
+
if (!existing) {
|
|
604
609
|
components.set(typeName, schema);
|
|
605
610
|
}
|
|
606
611
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
@@ -622,22 +627,44 @@ function getExplicitTypeNameFromNode(typeNode) {
|
|
|
622
627
|
}
|
|
623
628
|
return null;
|
|
624
629
|
}
|
|
630
|
+
function shouldBeIntegerType(typeName) {
|
|
631
|
+
if (!typeName) return false;
|
|
632
|
+
const lower = typeName.toLowerCase();
|
|
633
|
+
return lower === "id" || lower.endsWith("id") || lower === "primarykey" || lower === "pk";
|
|
634
|
+
}
|
|
635
|
+
function normalizeNumericType(type, checker, typeNode) {
|
|
636
|
+
const typeName = getExplicitTypeNameFromNode(typeNode) ?? null;
|
|
637
|
+
const symbol = getEffectiveSymbol(type, checker);
|
|
638
|
+
const symbolName = symbol?.getName() ?? null;
|
|
639
|
+
if (shouldBeIntegerType(typeName) || shouldBeIntegerType(symbolName)) {
|
|
640
|
+
return { type: "integer" };
|
|
641
|
+
}
|
|
642
|
+
return { type: "number" };
|
|
643
|
+
}
|
|
625
644
|
function getTypeNameFromNode(typeNode, ctx) {
|
|
626
645
|
const explicitName = getExplicitTypeNameFromNode(typeNode);
|
|
627
646
|
if (explicitName) return explicitName;
|
|
628
647
|
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
629
648
|
}
|
|
630
649
|
function buildObjectSchema(type, ctx, typeNode) {
|
|
631
|
-
const { checker } = ctx;
|
|
650
|
+
const { checker, mode } = ctx;
|
|
632
651
|
const properties = {};
|
|
633
652
|
const required = [];
|
|
634
653
|
const props = checker.getPropertiesOfType(type);
|
|
635
654
|
for (const prop of props) {
|
|
636
655
|
const propName = prop.getName();
|
|
656
|
+
if (isIteratorOrSymbolProperty(propName)) {
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
637
659
|
const propType = checker.getTypeOfSymbol(prop);
|
|
660
|
+
if (isMethodLike(propType)) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
638
663
|
const isOptional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
664
|
+
const isRelation = isMetalOrmWrapperType(propType, checker);
|
|
639
665
|
properties[propName] = typeToJsonSchema(propType, ctx);
|
|
640
|
-
|
|
666
|
+
const shouldRequire = mode === "response" ? !isRelation && !isOptional : !isOptional;
|
|
667
|
+
if (shouldRequire) {
|
|
641
668
|
required.push(propName);
|
|
642
669
|
}
|
|
643
670
|
}
|
|
@@ -676,6 +703,56 @@ function getRecordValueType(type, checker) {
|
|
|
676
703
|
}
|
|
677
704
|
return null;
|
|
678
705
|
}
|
|
706
|
+
var METAL_ORM_WRAPPER_NAMES = ["HasManyCollection", "ManyToManyCollection", "BelongsToReference", "HasOneReference"];
|
|
707
|
+
function isMetalOrmWrapperType(type, checker) {
|
|
708
|
+
const aliasSymbol = type.aliasSymbol || type.symbol;
|
|
709
|
+
if (!aliasSymbol) return false;
|
|
710
|
+
return METAL_ORM_WRAPPER_NAMES.includes(aliasSymbol.getName());
|
|
711
|
+
}
|
|
712
|
+
function getWrapperTypeName(type, checker) {
|
|
713
|
+
const symbol = getEffectiveSymbol(type, checker);
|
|
714
|
+
if (!symbol) return null;
|
|
715
|
+
const name = symbol.getName();
|
|
716
|
+
return METAL_ORM_WRAPPER_NAMES.includes(name) ? name : null;
|
|
717
|
+
}
|
|
718
|
+
function getEffectiveSymbol(type, checker) {
|
|
719
|
+
const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
|
|
720
|
+
if (aliasSymbol && aliasSymbol.flags & ts3.SymbolFlags.Alias) {
|
|
721
|
+
return checker.getAliasedSymbol(aliasSymbol);
|
|
722
|
+
}
|
|
723
|
+
return type.getSymbol() ?? null;
|
|
724
|
+
}
|
|
725
|
+
function isMethodLike(type) {
|
|
726
|
+
const callSigs = type.getCallSignatures?.();
|
|
727
|
+
return !!(callSigs && callSigs.length > 0);
|
|
728
|
+
}
|
|
729
|
+
function isIteratorOrSymbolProperty(propName) {
|
|
730
|
+
return propName.startsWith("__@") || propName.startsWith("[") || propName === Symbol.iterator.toString();
|
|
731
|
+
}
|
|
732
|
+
function handleMetalOrmWrapper(type, ctx) {
|
|
733
|
+
const typeRef = type;
|
|
734
|
+
const typeArgs = typeRef.typeArguments;
|
|
735
|
+
const targetType = typeArgs?.[0] ?? null;
|
|
736
|
+
const wrapperName = getWrapperTypeName(type, ctx.checker);
|
|
737
|
+
if (!wrapperName) return {};
|
|
738
|
+
const wrapperRel = { wrapper: wrapperName };
|
|
739
|
+
if (wrapperName === "HasManyCollection" || wrapperName === "ManyToManyCollection") {
|
|
740
|
+
const items = targetType ? typeToJsonSchema(targetType, ctx) : {};
|
|
741
|
+
if (wrapperName === "ManyToManyCollection" && typeArgs?.[1]) {
|
|
742
|
+
wrapperRel.pivot = typeArgs[1];
|
|
743
|
+
}
|
|
744
|
+
return {
|
|
745
|
+
type: "array",
|
|
746
|
+
items,
|
|
747
|
+
"x-metal-orm-rel": wrapperRel
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
const targetSchema = targetType ? typeToJsonSchema(targetType, ctx) : {};
|
|
751
|
+
return {
|
|
752
|
+
...targetSchema,
|
|
753
|
+
"x-metal-orm-rel": wrapperRel
|
|
754
|
+
};
|
|
755
|
+
}
|
|
679
756
|
|
|
680
757
|
// src/compiler/schema/extractAnnotations.ts
|
|
681
758
|
import ts4 from "typescript";
|
|
@@ -823,56 +900,6 @@ function mergeFragments(base, ...frags) {
|
|
|
823
900
|
return result;
|
|
824
901
|
}
|
|
825
902
|
|
|
826
|
-
// src/compiler/analyze/extractQueryStyle.ts
|
|
827
|
-
import ts5 from "typescript";
|
|
828
|
-
function extractQueryStyleOptions(checker, method) {
|
|
829
|
-
if (!ts5.canHaveDecorators(method)) return null;
|
|
830
|
-
const decorators = ts5.getDecorators(method);
|
|
831
|
-
if (!decorators || decorators.length === 0) return null;
|
|
832
|
-
for (const decorator of decorators) {
|
|
833
|
-
const expr = decorator.expression;
|
|
834
|
-
const isCall = ts5.isCallExpression(expr);
|
|
835
|
-
const callee = isCall ? expr.expression : expr;
|
|
836
|
-
const args = isCall ? expr.arguments : ts5.factory.createNodeArray([]);
|
|
837
|
-
const sym = checker.getSymbolAtLocation(callee);
|
|
838
|
-
if (!sym) continue;
|
|
839
|
-
const resolved = sym.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
|
|
840
|
-
const name = resolved.getName();
|
|
841
|
-
if (name !== "QueryStyle") continue;
|
|
842
|
-
const optsNode = args[0];
|
|
843
|
-
if (!optsNode || !ts5.isObjectLiteralExpression(optsNode)) {
|
|
844
|
-
return {};
|
|
845
|
-
}
|
|
846
|
-
return parseQueryStyleOptions(optsNode);
|
|
847
|
-
}
|
|
848
|
-
return null;
|
|
849
|
-
}
|
|
850
|
-
function parseQueryStyleOptions(node) {
|
|
851
|
-
const opts = {};
|
|
852
|
-
for (const prop of node.properties) {
|
|
853
|
-
if (!ts5.isPropertyAssignment(prop)) continue;
|
|
854
|
-
const name = getPropName(prop.name);
|
|
855
|
-
if (!name) continue;
|
|
856
|
-
if (name === "style" && ts5.isStringLiteral(prop.initializer)) {
|
|
857
|
-
const style = prop.initializer.text;
|
|
858
|
-
opts.style = style;
|
|
859
|
-
} else if (name === "explode" && isBooleanLiteral(prop.initializer)) {
|
|
860
|
-
opts.explode = prop.initializer.kind === ts5.SyntaxKind.TrueKeyword;
|
|
861
|
-
} else if (name === "allowReserved" && isBooleanLiteral(prop.initializer)) {
|
|
862
|
-
opts.allowReserved = prop.initializer.kind === ts5.SyntaxKind.TrueKeyword;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
return opts;
|
|
866
|
-
}
|
|
867
|
-
function getPropName(name) {
|
|
868
|
-
if (ts5.isIdentifier(name)) return name.text;
|
|
869
|
-
if (ts5.isStringLiteral(name)) return name.text;
|
|
870
|
-
return null;
|
|
871
|
-
}
|
|
872
|
-
function isBooleanLiteral(node) {
|
|
873
|
-
return node.kind === ts5.SyntaxKind.TrueKeyword || node.kind === ts5.SyntaxKind.FalseKeyword;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
903
|
// src/compiler/schema/openapi.ts
|
|
877
904
|
function generateOpenAPI(controllers, checker, options = {}) {
|
|
878
905
|
const components = /* @__PURE__ */ new Map();
|
|
@@ -880,7 +907,8 @@ function generateOpenAPI(controllers, checker, options = {}) {
|
|
|
880
907
|
checker,
|
|
881
908
|
components,
|
|
882
909
|
typeStack: /* @__PURE__ */ new Set(),
|
|
883
|
-
typeNameStack: []
|
|
910
|
+
typeNameStack: [],
|
|
911
|
+
mode: "response"
|
|
884
912
|
};
|
|
885
913
|
const paths = {};
|
|
886
914
|
for (const controller of controllers) {
|
|
@@ -908,7 +936,11 @@ function generateOpenAPI(controllers, checker, options = {}) {
|
|
|
908
936
|
function convertToOpenApiPath(basePath, path4) {
|
|
909
937
|
const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
910
938
|
const converted = path4.replace(/:([^/]+)/g, "{$1}");
|
|
911
|
-
|
|
939
|
+
let fullPath = base + converted || "/";
|
|
940
|
+
if (fullPath.endsWith("/") && fullPath !== "/") {
|
|
941
|
+
fullPath = fullPath.slice(0, -1);
|
|
942
|
+
}
|
|
943
|
+
return fullPath;
|
|
912
944
|
}
|
|
913
945
|
function buildOperation(operation, ctx, controllerConsumes) {
|
|
914
946
|
const op = {
|
|
@@ -923,7 +955,8 @@ function buildOperation(operation, ctx, controllerConsumes) {
|
|
|
923
955
|
if (parameters.length > 0) {
|
|
924
956
|
op.parameters = parameters;
|
|
925
957
|
}
|
|
926
|
-
const
|
|
958
|
+
const responseCtx = { ...ctx, mode: "response" };
|
|
959
|
+
const responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
|
|
927
960
|
const status = operation.httpMethod === "POST" ? 201 : 200;
|
|
928
961
|
op.responses[status] = {
|
|
929
962
|
description: status === 201 ? "Created" : "OK",
|
|
@@ -936,8 +969,9 @@ function buildOperation(operation, ctx, controllerConsumes) {
|
|
|
936
969
|
if (["POST", "PUT", "PATCH"].includes(operation.httpMethod) && operation.bodyParamIndex !== null) {
|
|
937
970
|
const bodyParam = operation.parameters[operation.bodyParamIndex];
|
|
938
971
|
if (bodyParam) {
|
|
939
|
-
|
|
940
|
-
bodySchema =
|
|
972
|
+
const requestCtx = { ...ctx, mode: "request" };
|
|
973
|
+
let bodySchema = typeToJsonSchema(bodyParam.type, requestCtx);
|
|
974
|
+
bodySchema = mergeBodySchemaAnnotations(bodyParam, requestCtx, bodySchema);
|
|
941
975
|
const contentType = operation.bodyContentType ?? controllerConsumes?.[0] ?? "application/json";
|
|
942
976
|
const requestBody = {
|
|
943
977
|
required: !bodyParam.isOptional,
|
|
@@ -964,12 +998,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
|
964
998
|
const declarations = typeSymbol.getDeclarations();
|
|
965
999
|
if (!declarations || declarations.length === 0) return schema;
|
|
966
1000
|
const classDecl = declarations[0];
|
|
967
|
-
if (!
|
|
1001
|
+
if (!ts5.isClassDeclaration(classDecl)) return schema;
|
|
968
1002
|
const result = { ...schema };
|
|
969
1003
|
const props = { ...result.properties };
|
|
970
1004
|
for (const member of classDecl.members) {
|
|
971
|
-
if (!
|
|
972
|
-
const propName =
|
|
1005
|
+
if (!ts5.isPropertyDeclaration(member) || !member.name) continue;
|
|
1006
|
+
const propName = ts5.isIdentifier(member.name) ? member.name.text : null;
|
|
973
1007
|
if (!propName) continue;
|
|
974
1008
|
if (!props[propName]) continue;
|
|
975
1009
|
const frags = extractPropertySchemaFragments(ctx.checker, member);
|
|
@@ -991,48 +1025,124 @@ function buildPathParameters(operation, ctx, parameters) {
|
|
|
991
1025
|
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
992
1026
|
}
|
|
993
1027
|
}
|
|
1028
|
+
const schema = paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema;
|
|
994
1029
|
parameters.push({
|
|
995
1030
|
name: param.name,
|
|
996
1031
|
in: "path",
|
|
997
1032
|
required: !param.isOptional,
|
|
998
|
-
schema
|
|
1033
|
+
schema
|
|
999
1034
|
});
|
|
1000
1035
|
}
|
|
1001
1036
|
}
|
|
1002
1037
|
}
|
|
1038
|
+
function isObjectLikeSchema(schema, ctx) {
|
|
1039
|
+
const resolved = resolveSchemaRef(schema, ctx.components);
|
|
1040
|
+
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1041
|
+
return true;
|
|
1042
|
+
}
|
|
1043
|
+
if (resolved.allOf) {
|
|
1044
|
+
for (const branch of resolved.allOf) {
|
|
1045
|
+
if (isObjectLikeSchema(branch, ctx)) {
|
|
1046
|
+
return true;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (resolved.type === "array" && resolved.items) {
|
|
1051
|
+
const itemsSchema = resolveSchemaRef(resolved.items, ctx.components);
|
|
1052
|
+
return isObjectLikeSchema(itemsSchema, ctx);
|
|
1053
|
+
}
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
function resolveSchemaRef(schema, components) {
|
|
1057
|
+
const ref = schema.$ref;
|
|
1058
|
+
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
1059
|
+
return schema;
|
|
1060
|
+
}
|
|
1061
|
+
const name = ref.replace("#/components/schemas/", "");
|
|
1062
|
+
const next = components.get(name);
|
|
1063
|
+
if (!next) return schema;
|
|
1064
|
+
return resolveSchemaRef(next, components);
|
|
1065
|
+
}
|
|
1066
|
+
function resolveAndCollectObjectProps(schema, components) {
|
|
1067
|
+
const resolved = resolveSchemaRef(schema, components);
|
|
1068
|
+
const properties = {};
|
|
1069
|
+
const required = [];
|
|
1070
|
+
const processSchema = (s) => {
|
|
1071
|
+
const current = resolveSchemaRef(s, components);
|
|
1072
|
+
if (current.properties) {
|
|
1073
|
+
for (const [key, val] of Object.entries(current.properties)) {
|
|
1074
|
+
if (!properties[key]) {
|
|
1075
|
+
properties[key] = val;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
if (current.required) {
|
|
1080
|
+
for (const req of current.required) {
|
|
1081
|
+
if (!required.includes(req)) {
|
|
1082
|
+
required.push(req);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (current.allOf) {
|
|
1087
|
+
for (const branch of current.allOf) {
|
|
1088
|
+
processSchema(branch);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
processSchema(resolved);
|
|
1093
|
+
return { properties, required };
|
|
1094
|
+
}
|
|
1003
1095
|
function buildQueryParameters(operation, ctx, parameters) {
|
|
1004
1096
|
if (operation.queryObjectParamIndex !== null) {
|
|
1005
1097
|
const queryParam = operation.parameters[operation.queryObjectParamIndex];
|
|
1006
1098
|
if (!queryParam) return;
|
|
1007
|
-
const queryStyle = extractQueryStyleOptions(ctx.checker, operation.methodDeclaration);
|
|
1008
1099
|
const querySchema = typeToJsonSchema(queryParam.type, ctx);
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
schema: querySchema.$ref ? { $ref: querySchema.$ref } : querySchema,
|
|
1016
|
-
style: "deepObject",
|
|
1017
|
-
explode
|
|
1018
|
-
};
|
|
1019
|
-
if (queryStyle.allowReserved !== void 0) {
|
|
1020
|
-
deepParam.allowReserved = queryStyle.allowReserved;
|
|
1021
|
-
}
|
|
1022
|
-
parameters.push(deepParam);
|
|
1023
|
-
} else {
|
|
1024
|
-
if (!querySchema.properties) return;
|
|
1025
|
-
const queryObjProps = querySchema.properties;
|
|
1026
|
-
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1027
|
-
const isRequired = querySchema.required?.includes(propName) ?? false;
|
|
1028
|
-
const serialization = determineQuerySerialization(propSchema.type);
|
|
1100
|
+
const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps(querySchema, ctx.components);
|
|
1101
|
+
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1102
|
+
const isRequired = queryRequired.includes(propName) ?? false;
|
|
1103
|
+
const isObjectLike = isObjectLikeSchema(propSchema, ctx);
|
|
1104
|
+
const serialization = determineQuerySerialization(propSchema.type);
|
|
1105
|
+
if (isObjectLike) {
|
|
1029
1106
|
parameters.push({
|
|
1030
1107
|
name: propName,
|
|
1031
1108
|
in: "query",
|
|
1032
1109
|
required: isRequired,
|
|
1033
|
-
|
|
1034
|
-
|
|
1110
|
+
content: {
|
|
1111
|
+
"application/json": {
|
|
1112
|
+
schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
1115
|
+
description: `URL-encoded JSON. Example: ${propName}=${encodeURIComponent(JSON.stringify({ example: "value" }))}`
|
|
1035
1116
|
});
|
|
1117
|
+
} else {
|
|
1118
|
+
const paramDef = {
|
|
1119
|
+
name: propName,
|
|
1120
|
+
in: "query",
|
|
1121
|
+
required: isRequired,
|
|
1122
|
+
schema: propSchema.$ref ? { $ref: propSchema.$ref } : propSchema
|
|
1123
|
+
};
|
|
1124
|
+
if (propName === "page") {
|
|
1125
|
+
paramDef.schema = { type: "integer", default: 1, minimum: 1 };
|
|
1126
|
+
} else if (propName === "pageSize") {
|
|
1127
|
+
paramDef.schema = { type: "integer", default: 10, minimum: 1 };
|
|
1128
|
+
} else if (propName === "totalItems") {
|
|
1129
|
+
paramDef.schema = { type: "integer", minimum: 0 };
|
|
1130
|
+
} else if (propName === "sort") {
|
|
1131
|
+
paramDef.schema = {
|
|
1132
|
+
oneOf: [
|
|
1133
|
+
{ type: "string" },
|
|
1134
|
+
{ type: "array", items: { type: "string" } }
|
|
1135
|
+
]
|
|
1136
|
+
};
|
|
1137
|
+
} else if (propName === "q") {
|
|
1138
|
+
paramDef.schema = { type: "string" };
|
|
1139
|
+
} else if (propName === "hasComments") {
|
|
1140
|
+
paramDef.schema = { type: "boolean" };
|
|
1141
|
+
}
|
|
1142
|
+
if (Object.keys(serialization).length > 0) {
|
|
1143
|
+
Object.assign(paramDef, serialization);
|
|
1144
|
+
}
|
|
1145
|
+
parameters.push(paramDef);
|
|
1036
1146
|
}
|
|
1037
1147
|
}
|
|
1038
1148
|
}
|
|
@@ -1046,14 +1156,28 @@ function buildQueryParameters(operation, ctx, parameters) {
|
|
|
1046
1156
|
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
1047
1157
|
}
|
|
1048
1158
|
}
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1159
|
+
const isObjectLike = isObjectLikeSchema(paramSchema, ctx);
|
|
1160
|
+
if (isObjectLike) {
|
|
1161
|
+
parameters.push({
|
|
1162
|
+
name: param.name,
|
|
1163
|
+
in: "query",
|
|
1164
|
+
required: !param.isOptional,
|
|
1165
|
+
content: {
|
|
1166
|
+
"application/json": {
|
|
1167
|
+
schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
} else {
|
|
1172
|
+
const serialization = determineQuerySerialization(paramSchema.type);
|
|
1173
|
+
parameters.push({
|
|
1174
|
+
name: param.name,
|
|
1175
|
+
in: "query",
|
|
1176
|
+
required: !param.isOptional,
|
|
1177
|
+
schema: paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema,
|
|
1178
|
+
...Object.keys(serialization).length > 0 ? serialization : {}
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1057
1181
|
}
|
|
1058
1182
|
}
|
|
1059
1183
|
}
|
|
@@ -1103,14 +1227,15 @@ function buildCookieParameters(operation, ctx, parameters) {
|
|
|
1103
1227
|
}
|
|
1104
1228
|
|
|
1105
1229
|
// src/compiler/manifest/emit.ts
|
|
1106
|
-
import
|
|
1230
|
+
import ts6 from "typescript";
|
|
1107
1231
|
function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
|
|
1108
1232
|
const components = /* @__PURE__ */ new Map();
|
|
1109
1233
|
const ctx = {
|
|
1110
1234
|
checker,
|
|
1111
1235
|
components,
|
|
1112
1236
|
typeStack: /* @__PURE__ */ new Set(),
|
|
1113
|
-
typeNameStack: []
|
|
1237
|
+
typeNameStack: [],
|
|
1238
|
+
mode: "request"
|
|
1114
1239
|
};
|
|
1115
1240
|
const controllerEntries = controllers.map((ctrl) => ({
|
|
1116
1241
|
controllerId: ctrl.className,
|
|
@@ -1124,7 +1249,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
|
|
|
1124
1249
|
generator: {
|
|
1125
1250
|
name: "adorn-api",
|
|
1126
1251
|
version,
|
|
1127
|
-
typescript:
|
|
1252
|
+
typescript: ts6.version
|
|
1128
1253
|
},
|
|
1129
1254
|
schemas: {
|
|
1130
1255
|
kind: "openapi-3.1",
|
|
@@ -1135,6 +1260,63 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
|
|
|
1135
1260
|
controllers: controllerEntries
|
|
1136
1261
|
};
|
|
1137
1262
|
}
|
|
1263
|
+
function resolveSchemaRef2(schema, components) {
|
|
1264
|
+
const ref = schema.$ref;
|
|
1265
|
+
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
1266
|
+
return schema;
|
|
1267
|
+
}
|
|
1268
|
+
const name = ref.replace("#/components/schemas/", "");
|
|
1269
|
+
const next = components.get(name);
|
|
1270
|
+
if (!next) return schema;
|
|
1271
|
+
return resolveSchemaRef2(next, components);
|
|
1272
|
+
}
|
|
1273
|
+
function resolveAndCollectObjectProps2(schema, components) {
|
|
1274
|
+
const resolved = resolveSchemaRef2(schema, components);
|
|
1275
|
+
const properties = {};
|
|
1276
|
+
const required = [];
|
|
1277
|
+
const processSchema = (s) => {
|
|
1278
|
+
const current = resolveSchemaRef2(s, components);
|
|
1279
|
+
if (current.properties) {
|
|
1280
|
+
for (const [key, val] of Object.entries(current.properties)) {
|
|
1281
|
+
if (!properties[key]) {
|
|
1282
|
+
properties[key] = val;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (current.required) {
|
|
1287
|
+
for (const req of current.required) {
|
|
1288
|
+
if (!required.includes(req)) {
|
|
1289
|
+
required.push(req);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (current.allOf) {
|
|
1294
|
+
for (const branch of current.allOf) {
|
|
1295
|
+
processSchema(branch);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
processSchema(resolved);
|
|
1300
|
+
return { properties, required };
|
|
1301
|
+
}
|
|
1302
|
+
function isObjectLikeSchema2(schema, components) {
|
|
1303
|
+
const resolved = resolveSchemaRef2(schema, components);
|
|
1304
|
+
if (resolved.type === "object" || resolved.properties || resolved.additionalProperties) {
|
|
1305
|
+
return true;
|
|
1306
|
+
}
|
|
1307
|
+
if (resolved.allOf) {
|
|
1308
|
+
for (const branch of resolved.allOf) {
|
|
1309
|
+
if (isObjectLikeSchema2(branch, components)) {
|
|
1310
|
+
return true;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
if (resolved.type === "array" && resolved.items) {
|
|
1315
|
+
const itemsSchema = resolveSchemaRef2(resolved.items, components);
|
|
1316
|
+
return isObjectLikeSchema2(itemsSchema, components);
|
|
1317
|
+
}
|
|
1318
|
+
return false;
|
|
1319
|
+
}
|
|
1138
1320
|
function buildOperationEntry(op, ctx) {
|
|
1139
1321
|
const args = {
|
|
1140
1322
|
body: null,
|
|
@@ -1208,53 +1390,39 @@ function buildPathArgs(op, ctx, args) {
|
|
|
1208
1390
|
function buildQueryArgs(op, ctx, args) {
|
|
1209
1391
|
if (op.queryObjectParamIndex !== null) {
|
|
1210
1392
|
const queryParam = op.parameters[op.queryObjectParamIndex];
|
|
1211
|
-
if (queryParam)
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
schemaRef,
|
|
1221
|
-
schemaType: querySchema.type,
|
|
1222
|
-
serialization: {
|
|
1223
|
-
style: "deepObject",
|
|
1224
|
-
explode: queryStyle.explode ?? true,
|
|
1225
|
-
allowReserved: queryStyle.allowReserved
|
|
1226
|
-
}
|
|
1227
|
-
});
|
|
1228
|
-
} else {
|
|
1229
|
-
if (!querySchema.properties) return;
|
|
1230
|
-
for (const [propName, propSchema] of Object.entries(querySchema.properties)) {
|
|
1231
|
-
const isRequired = querySchema.required?.includes(propName) ?? false;
|
|
1232
|
-
let schemaRef = propSchema.$ref;
|
|
1233
|
-
if (!schemaRef) {
|
|
1234
|
-
schemaRef = "#/components/schemas/InlineQueryParam";
|
|
1235
|
-
}
|
|
1236
|
-
args.query.push({
|
|
1237
|
-
name: propName,
|
|
1238
|
-
index: queryParam.index,
|
|
1239
|
-
required: !isRequired,
|
|
1240
|
-
schemaRef,
|
|
1241
|
-
schemaType: propSchema.type
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1393
|
+
if (!queryParam) return;
|
|
1394
|
+
const querySchema = typeToJsonSchema(queryParam.type, ctx);
|
|
1395
|
+
const { properties: queryObjProps, required: queryRequired } = resolveAndCollectObjectProps2(querySchema, ctx.components);
|
|
1396
|
+
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1397
|
+
const isRequired = queryRequired.includes(propName) ?? false;
|
|
1398
|
+
const isObjectLike = isObjectLikeSchema2(propSchema, ctx.components);
|
|
1399
|
+
let schemaRef = propSchema.$ref;
|
|
1400
|
+
if (!schemaRef) {
|
|
1401
|
+
schemaRef = "#/components/schemas/InlineQueryParam";
|
|
1244
1402
|
}
|
|
1403
|
+
args.query.push({
|
|
1404
|
+
name: propName,
|
|
1405
|
+
index: queryParam.index,
|
|
1406
|
+
required: !isRequired,
|
|
1407
|
+
schemaRef,
|
|
1408
|
+
schemaType: propSchema.type,
|
|
1409
|
+
content: isObjectLike ? "application/json" : void 0
|
|
1410
|
+
});
|
|
1245
1411
|
}
|
|
1246
1412
|
}
|
|
1247
1413
|
for (const paramIndex of op.queryParamIndices) {
|
|
1248
1414
|
const param = op.parameters[paramIndex];
|
|
1249
1415
|
if (param) {
|
|
1250
1416
|
const paramSchema = typeToJsonSchema(param.type, ctx);
|
|
1417
|
+
const isObjectLike = isObjectLikeSchema2(paramSchema, ctx.components);
|
|
1251
1418
|
const schemaRef = paramSchema.$ref ?? "#/components/schemas/InlineQueryParam";
|
|
1252
1419
|
args.query.push({
|
|
1253
1420
|
name: param.name,
|
|
1254
1421
|
index: param.index,
|
|
1255
1422
|
required: !param.isOptional,
|
|
1256
1423
|
schemaRef,
|
|
1257
|
-
schemaType: paramSchema.type
|
|
1424
|
+
schemaType: paramSchema.type,
|
|
1425
|
+
content: isObjectLike ? "application/json" : void 0
|
|
1258
1426
|
});
|
|
1259
1427
|
}
|
|
1260
1428
|
}
|
|
@@ -1549,7 +1717,7 @@ async function isStale(params) {
|
|
|
1549
1717
|
// src/compiler/cache/writeCache.ts
|
|
1550
1718
|
import fs3 from "fs";
|
|
1551
1719
|
import path3 from "path";
|
|
1552
|
-
import
|
|
1720
|
+
import ts8 from "typescript";
|
|
1553
1721
|
function statMtimeMs2(p) {
|
|
1554
1722
|
return fs3.statSync(p).mtimeMs;
|
|
1555
1723
|
}
|
|
@@ -1582,7 +1750,7 @@ function writeCache(params) {
|
|
|
1582
1750
|
generator: {
|
|
1583
1751
|
name: "adorn-api",
|
|
1584
1752
|
version: params.adornVersion,
|
|
1585
|
-
typescript:
|
|
1753
|
+
typescript: ts8.version
|
|
1586
1754
|
},
|
|
1587
1755
|
project: {
|
|
1588
1756
|
tsconfigPath: params.tsconfigAbs,
|
|
@@ -1595,7 +1763,7 @@ function writeCache(params) {
|
|
|
1595
1763
|
}
|
|
1596
1764
|
|
|
1597
1765
|
// src/cli.ts
|
|
1598
|
-
import
|
|
1766
|
+
import ts9 from "typescript";
|
|
1599
1767
|
import process from "process";
|
|
1600
1768
|
var ADORN_VERSION = "0.1.0";
|
|
1601
1769
|
function log(msg) {
|
|
@@ -1606,6 +1774,26 @@ function debug(...args) {
|
|
|
1606
1774
|
console.error("[adorn-api]", ...args);
|
|
1607
1775
|
}
|
|
1608
1776
|
}
|
|
1777
|
+
function sanitizeForJson(obj) {
|
|
1778
|
+
if (obj === null || obj === void 0) return obj;
|
|
1779
|
+
if (typeof obj !== "object") return obj;
|
|
1780
|
+
if (Array.isArray(obj)) {
|
|
1781
|
+
return obj.map((item) => sanitizeForJson(item));
|
|
1782
|
+
}
|
|
1783
|
+
const result = {};
|
|
1784
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1785
|
+
if (key.startsWith("__@") || key.startsWith("[")) continue;
|
|
1786
|
+
if (typeof value === "function") continue;
|
|
1787
|
+
if (value !== null && typeof value === "object") {
|
|
1788
|
+
const typeName = value.constructor?.name;
|
|
1789
|
+
if (typeName && !["Object", "Array", "String", "Number", "Boolean", "Date", "RegExp"].includes(typeName)) {
|
|
1790
|
+
continue;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
result[key] = sanitizeForJson(value);
|
|
1794
|
+
}
|
|
1795
|
+
return result;
|
|
1796
|
+
}
|
|
1609
1797
|
async function buildCommand(args) {
|
|
1610
1798
|
const projectIndex = args.indexOf("-p");
|
|
1611
1799
|
const projectPath = projectIndex !== -1 ? args[projectIndex + 1] : "./tsconfig.json";
|
|
@@ -1623,7 +1811,7 @@ async function buildCommand(args) {
|
|
|
1623
1811
|
outDir: outputDir,
|
|
1624
1812
|
project: projectPath,
|
|
1625
1813
|
adornVersion: ADORN_VERSION,
|
|
1626
|
-
typescriptVersion:
|
|
1814
|
+
typescriptVersion: ts9.version
|
|
1627
1815
|
});
|
|
1628
1816
|
if (!stale.stale) {
|
|
1629
1817
|
log("adorn-api: artifacts up-to-date");
|
|
@@ -1644,7 +1832,7 @@ async function buildCommand(args) {
|
|
|
1644
1832
|
const openapi = generateOpenAPI(controllers, checker, { title: "API", version: "1.0.0" });
|
|
1645
1833
|
const manifest = generateManifest(controllers, checker, ADORN_VERSION, validationMode);
|
|
1646
1834
|
mkdirSync(outputPath, { recursive: true });
|
|
1647
|
-
writeFileSync(resolve2(outputPath, "openapi.json"), JSON.stringify(openapi, null, 2));
|
|
1835
|
+
writeFileSync(resolve2(outputPath, "openapi.json"), JSON.stringify(sanitizeForJson(openapi), null, 2));
|
|
1648
1836
|
writeFileSync(resolve2(outputPath, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
1649
1837
|
if (validationMode === "precompiled") {
|
|
1650
1838
|
log("Generating precompiled validators...");
|