adorn-api 1.0.11 → 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/merge.d.ts +0 -3
- package/dist/adapter/express/merge.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 +332 -153
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +331 -152
- package/dist/cli.js.map +1 -1
- package/dist/compiler/analyze/scanControllers.d.ts +0 -1
- package/dist/compiler/analyze/scanControllers.d.ts.map +1 -1
- package/dist/compiler/manifest/emit.d.ts.map +1 -1
- package/dist/compiler/manifest/format.d.ts +1 -1
- 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/decorators/index.d.ts +0 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/express.cjs +619 -596
- package/dist/express.cjs.map +1 -1
- package/dist/express.js +616 -593
- package/dist/express.js.map +1 -1
- package/dist/http.d.ts +1 -9
- package/dist/http.d.ts.map +1 -1
- package/dist/index.cjs +2 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -33
- 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/dist/runtime/metadata/types.d.ts +0 -3
- package/dist/runtime/metadata/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/compiler/analyze/extractQueryStyle.d.ts +0 -8
- package/dist/compiler/analyze/extractQueryStyle.d.ts.map +0 -1
- package/dist/decorators/Paginated.d.ts +0 -5
- package/dist/decorators/Paginated.d.ts.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -154,7 +154,7 @@ function analyzeMethod(node, className, checker) {
|
|
|
154
154
|
}
|
|
155
155
|
const pathParamNames = extractPathParams(path4);
|
|
156
156
|
const pathParamIndices = matchPathParamsToIndices(pathParamNames, parameters);
|
|
157
|
-
const { bodyParamIndex, queryParamIndices, queryObjectParamIndex, headerObjectParamIndex, cookieObjectParamIndex,
|
|
157
|
+
const { bodyParamIndex, queryParamIndices, queryObjectParamIndex, headerObjectParamIndex, cookieObjectParamIndex, bodyContentType } = classifyParameters(parameters, httpMethod, pathParamIndices, checker);
|
|
158
158
|
return {
|
|
159
159
|
methodName,
|
|
160
160
|
httpMethod,
|
|
@@ -170,7 +170,6 @@ function analyzeMethod(node, className, checker) {
|
|
|
170
170
|
queryObjectParamIndex,
|
|
171
171
|
headerObjectParamIndex,
|
|
172
172
|
cookieObjectParamIndex,
|
|
173
|
-
paginationParamIndex,
|
|
174
173
|
bodyContentType
|
|
175
174
|
};
|
|
176
175
|
}
|
|
@@ -196,7 +195,6 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
|
|
|
196
195
|
let queryObjectParamIndex = null;
|
|
197
196
|
let headerObjectParamIndex = null;
|
|
198
197
|
let cookieObjectParamIndex = null;
|
|
199
|
-
let paginationParamIndex = null;
|
|
200
198
|
const isBodyMethod = ["POST", "PUT", "PATCH"].includes(httpMethod);
|
|
201
199
|
for (let i = 0; i < parameters.length; i++) {
|
|
202
200
|
const param = parameters[i];
|
|
@@ -223,11 +221,6 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
|
|
|
223
221
|
usedIndices.add(i);
|
|
224
222
|
continue;
|
|
225
223
|
}
|
|
226
|
-
if (typeStr === "PaginationParams") {
|
|
227
|
-
paginationParamIndex = i;
|
|
228
|
-
usedIndices.add(i);
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
224
|
if (isBodyMethod && bodyParamIndex === null) {
|
|
232
225
|
bodyParamIndex = i;
|
|
233
226
|
usedIndices.add(i);
|
|
@@ -248,19 +241,19 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
|
|
|
248
241
|
queryObjectParamIndex,
|
|
249
242
|
headerObjectParamIndex,
|
|
250
243
|
cookieObjectParamIndex,
|
|
251
|
-
paginationParamIndex,
|
|
252
244
|
bodyContentType: void 0
|
|
253
245
|
};
|
|
254
246
|
}
|
|
255
247
|
function isObjectType(type, checker) {
|
|
256
248
|
const objectFlags = (type.flags & ts2.TypeFlags.Object) !== 0;
|
|
257
|
-
|
|
249
|
+
const intersectionFlags = (type.flags & ts2.TypeFlags.Intersection) !== 0;
|
|
250
|
+
if (!objectFlags && !intersectionFlags) return false;
|
|
258
251
|
const symbol = type.getSymbol();
|
|
259
252
|
if (symbol?.getName() === "__object") return true;
|
|
260
253
|
const properties = checker.getPropertiesOfType(type);
|
|
261
254
|
if (properties.length > 0) return true;
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
255
|
+
const callSigs = type.getCallSignatures?.();
|
|
256
|
+
if (callSigs && callSigs.length > 0) return false;
|
|
264
257
|
return true;
|
|
265
258
|
}
|
|
266
259
|
function getTypeName(type) {
|
|
@@ -312,7 +305,7 @@ function unwrapPromiseTypeNode(typeNode) {
|
|
|
312
305
|
}
|
|
313
306
|
|
|
314
307
|
// src/compiler/schema/openapi.ts
|
|
315
|
-
import
|
|
308
|
+
import ts5 from "typescript";
|
|
316
309
|
|
|
317
310
|
// src/compiler/schema/typeToJsonSchema.ts
|
|
318
311
|
import ts3 from "typescript";
|
|
@@ -331,7 +324,7 @@ function typeToJsonSchema(type, ctx, typeNode) {
|
|
|
331
324
|
return { type: "string" };
|
|
332
325
|
}
|
|
333
326
|
if (type.flags & ts3.TypeFlags.Number) {
|
|
334
|
-
return
|
|
327
|
+
return normalizeNumericType(type, checker, typeNode);
|
|
335
328
|
}
|
|
336
329
|
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
337
330
|
return { type: "boolean" };
|
|
@@ -593,9 +586,12 @@ function getBranchSchemaName(type, ctx) {
|
|
|
593
586
|
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
594
587
|
}
|
|
595
588
|
function handleObjectType(type, ctx, typeNode) {
|
|
596
|
-
const { checker, components, typeStack } = ctx;
|
|
589
|
+
const { checker, components, typeStack, mode } = ctx;
|
|
597
590
|
const symbol = type.getSymbol();
|
|
598
591
|
const typeName = symbol?.getName?.() ?? getTypeNameFromNode(typeNode, ctx);
|
|
592
|
+
if (isMetalOrmWrapperType(type, checker)) {
|
|
593
|
+
return handleMetalOrmWrapper(type, ctx);
|
|
594
|
+
}
|
|
599
595
|
if (typeName && typeName !== "__type") {
|
|
600
596
|
if (components.has(typeName)) {
|
|
601
597
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
@@ -608,7 +604,8 @@ function handleObjectType(type, ctx, typeNode) {
|
|
|
608
604
|
const schema = buildObjectSchema(type, ctx, typeNode);
|
|
609
605
|
if (typeName && typeName !== "__type") {
|
|
610
606
|
typeStack.delete(type);
|
|
611
|
-
|
|
607
|
+
const existing = components.get(typeName);
|
|
608
|
+
if (!existing) {
|
|
612
609
|
components.set(typeName, schema);
|
|
613
610
|
}
|
|
614
611
|
return { $ref: `#/components/schemas/${typeName}` };
|
|
@@ -630,22 +627,44 @@ function getExplicitTypeNameFromNode(typeNode) {
|
|
|
630
627
|
}
|
|
631
628
|
return null;
|
|
632
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
|
+
}
|
|
633
644
|
function getTypeNameFromNode(typeNode, ctx) {
|
|
634
645
|
const explicitName = getExplicitTypeNameFromNode(typeNode);
|
|
635
646
|
if (explicitName) return explicitName;
|
|
636
647
|
return `Anonymous_${ctx.typeNameStack.length}`;
|
|
637
648
|
}
|
|
638
649
|
function buildObjectSchema(type, ctx, typeNode) {
|
|
639
|
-
const { checker } = ctx;
|
|
650
|
+
const { checker, mode } = ctx;
|
|
640
651
|
const properties = {};
|
|
641
652
|
const required = [];
|
|
642
653
|
const props = checker.getPropertiesOfType(type);
|
|
643
654
|
for (const prop of props) {
|
|
644
655
|
const propName = prop.getName();
|
|
656
|
+
if (isIteratorOrSymbolProperty(propName)) {
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
645
659
|
const propType = checker.getTypeOfSymbol(prop);
|
|
660
|
+
if (isMethodLike(propType)) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
646
663
|
const isOptional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
664
|
+
const isRelation = isMetalOrmWrapperType(propType, checker);
|
|
647
665
|
properties[propName] = typeToJsonSchema(propType, ctx);
|
|
648
|
-
|
|
666
|
+
const shouldRequire = mode === "response" ? !isRelation && !isOptional : !isOptional;
|
|
667
|
+
if (shouldRequire) {
|
|
649
668
|
required.push(propName);
|
|
650
669
|
}
|
|
651
670
|
}
|
|
@@ -684,6 +703,56 @@ function getRecordValueType(type, checker) {
|
|
|
684
703
|
}
|
|
685
704
|
return null;
|
|
686
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
|
+
}
|
|
687
756
|
|
|
688
757
|
// src/compiler/schema/extractAnnotations.ts
|
|
689
758
|
import ts4 from "typescript";
|
|
@@ -831,56 +900,6 @@ function mergeFragments(base, ...frags) {
|
|
|
831
900
|
return result;
|
|
832
901
|
}
|
|
833
902
|
|
|
834
|
-
// src/compiler/analyze/extractQueryStyle.ts
|
|
835
|
-
import ts5 from "typescript";
|
|
836
|
-
function extractQueryStyleOptions(checker, method) {
|
|
837
|
-
if (!ts5.canHaveDecorators(method)) return null;
|
|
838
|
-
const decorators = ts5.getDecorators(method);
|
|
839
|
-
if (!decorators || decorators.length === 0) return null;
|
|
840
|
-
for (const decorator of decorators) {
|
|
841
|
-
const expr = decorator.expression;
|
|
842
|
-
const isCall = ts5.isCallExpression(expr);
|
|
843
|
-
const callee = isCall ? expr.expression : expr;
|
|
844
|
-
const args = isCall ? expr.arguments : ts5.factory.createNodeArray([]);
|
|
845
|
-
const sym = checker.getSymbolAtLocation(callee);
|
|
846
|
-
if (!sym) continue;
|
|
847
|
-
const resolved = sym.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
|
|
848
|
-
const name = resolved.getName();
|
|
849
|
-
if (name !== "QueryStyle") continue;
|
|
850
|
-
const optsNode = args[0];
|
|
851
|
-
if (!optsNode || !ts5.isObjectLiteralExpression(optsNode)) {
|
|
852
|
-
return {};
|
|
853
|
-
}
|
|
854
|
-
return parseQueryStyleOptions(optsNode);
|
|
855
|
-
}
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
858
|
-
function parseQueryStyleOptions(node) {
|
|
859
|
-
const opts = {};
|
|
860
|
-
for (const prop of node.properties) {
|
|
861
|
-
if (!ts5.isPropertyAssignment(prop)) continue;
|
|
862
|
-
const name = getPropName(prop.name);
|
|
863
|
-
if (!name) continue;
|
|
864
|
-
if (name === "style" && ts5.isStringLiteral(prop.initializer)) {
|
|
865
|
-
const style = prop.initializer.text;
|
|
866
|
-
opts.style = style;
|
|
867
|
-
} else if (name === "explode" && isBooleanLiteral(prop.initializer)) {
|
|
868
|
-
opts.explode = prop.initializer.kind === ts5.SyntaxKind.TrueKeyword;
|
|
869
|
-
} else if (name === "allowReserved" && isBooleanLiteral(prop.initializer)) {
|
|
870
|
-
opts.allowReserved = prop.initializer.kind === ts5.SyntaxKind.TrueKeyword;
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
return opts;
|
|
874
|
-
}
|
|
875
|
-
function getPropName(name) {
|
|
876
|
-
if (ts5.isIdentifier(name)) return name.text;
|
|
877
|
-
if (ts5.isStringLiteral(name)) return name.text;
|
|
878
|
-
return null;
|
|
879
|
-
}
|
|
880
|
-
function isBooleanLiteral(node) {
|
|
881
|
-
return node.kind === ts5.SyntaxKind.TrueKeyword || node.kind === ts5.SyntaxKind.FalseKeyword;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
903
|
// src/compiler/schema/openapi.ts
|
|
885
904
|
function generateOpenAPI(controllers, checker, options = {}) {
|
|
886
905
|
const components = /* @__PURE__ */ new Map();
|
|
@@ -888,7 +907,8 @@ function generateOpenAPI(controllers, checker, options = {}) {
|
|
|
888
907
|
checker,
|
|
889
908
|
components,
|
|
890
909
|
typeStack: /* @__PURE__ */ new Set(),
|
|
891
|
-
typeNameStack: []
|
|
910
|
+
typeNameStack: [],
|
|
911
|
+
mode: "response"
|
|
892
912
|
};
|
|
893
913
|
const paths = {};
|
|
894
914
|
for (const controller of controllers) {
|
|
@@ -916,7 +936,11 @@ function generateOpenAPI(controllers, checker, options = {}) {
|
|
|
916
936
|
function convertToOpenApiPath(basePath, path4) {
|
|
917
937
|
const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
918
938
|
const converted = path4.replace(/:([^/]+)/g, "{$1}");
|
|
919
|
-
|
|
939
|
+
let fullPath = base + converted || "/";
|
|
940
|
+
if (fullPath.endsWith("/") && fullPath !== "/") {
|
|
941
|
+
fullPath = fullPath.slice(0, -1);
|
|
942
|
+
}
|
|
943
|
+
return fullPath;
|
|
920
944
|
}
|
|
921
945
|
function buildOperation(operation, ctx, controllerConsumes) {
|
|
922
946
|
const op = {
|
|
@@ -931,7 +955,8 @@ function buildOperation(operation, ctx, controllerConsumes) {
|
|
|
931
955
|
if (parameters.length > 0) {
|
|
932
956
|
op.parameters = parameters;
|
|
933
957
|
}
|
|
934
|
-
const
|
|
958
|
+
const responseCtx = { ...ctx, mode: "response" };
|
|
959
|
+
const responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
|
|
935
960
|
const status = operation.httpMethod === "POST" ? 201 : 200;
|
|
936
961
|
op.responses[status] = {
|
|
937
962
|
description: status === 201 ? "Created" : "OK",
|
|
@@ -944,8 +969,9 @@ function buildOperation(operation, ctx, controllerConsumes) {
|
|
|
944
969
|
if (["POST", "PUT", "PATCH"].includes(operation.httpMethod) && operation.bodyParamIndex !== null) {
|
|
945
970
|
const bodyParam = operation.parameters[operation.bodyParamIndex];
|
|
946
971
|
if (bodyParam) {
|
|
947
|
-
|
|
948
|
-
bodySchema =
|
|
972
|
+
const requestCtx = { ...ctx, mode: "request" };
|
|
973
|
+
let bodySchema = typeToJsonSchema(bodyParam.type, requestCtx);
|
|
974
|
+
bodySchema = mergeBodySchemaAnnotations(bodyParam, requestCtx, bodySchema);
|
|
949
975
|
const contentType = operation.bodyContentType ?? controllerConsumes?.[0] ?? "application/json";
|
|
950
976
|
const requestBody = {
|
|
951
977
|
required: !bodyParam.isOptional,
|
|
@@ -972,12 +998,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
|
972
998
|
const declarations = typeSymbol.getDeclarations();
|
|
973
999
|
if (!declarations || declarations.length === 0) return schema;
|
|
974
1000
|
const classDecl = declarations[0];
|
|
975
|
-
if (!
|
|
1001
|
+
if (!ts5.isClassDeclaration(classDecl)) return schema;
|
|
976
1002
|
const result = { ...schema };
|
|
977
1003
|
const props = { ...result.properties };
|
|
978
1004
|
for (const member of classDecl.members) {
|
|
979
|
-
if (!
|
|
980
|
-
const propName =
|
|
1005
|
+
if (!ts5.isPropertyDeclaration(member) || !member.name) continue;
|
|
1006
|
+
const propName = ts5.isIdentifier(member.name) ? member.name.text : null;
|
|
981
1007
|
if (!propName) continue;
|
|
982
1008
|
if (!props[propName]) continue;
|
|
983
1009
|
const frags = extractPropertySchemaFragments(ctx.checker, member);
|
|
@@ -999,48 +1025,124 @@ function buildPathParameters(operation, ctx, parameters) {
|
|
|
999
1025
|
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
1000
1026
|
}
|
|
1001
1027
|
}
|
|
1028
|
+
const schema = paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema;
|
|
1002
1029
|
parameters.push({
|
|
1003
1030
|
name: param.name,
|
|
1004
1031
|
in: "path",
|
|
1005
1032
|
required: !param.isOptional,
|
|
1006
|
-
schema
|
|
1033
|
+
schema
|
|
1007
1034
|
});
|
|
1008
1035
|
}
|
|
1009
1036
|
}
|
|
1010
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
|
+
}
|
|
1011
1095
|
function buildQueryParameters(operation, ctx, parameters) {
|
|
1012
1096
|
if (operation.queryObjectParamIndex !== null) {
|
|
1013
1097
|
const queryParam = operation.parameters[operation.queryObjectParamIndex];
|
|
1014
1098
|
if (!queryParam) return;
|
|
1015
|
-
const queryStyle = extractQueryStyleOptions(ctx.checker, operation.methodDeclaration);
|
|
1016
1099
|
const querySchema = typeToJsonSchema(queryParam.type, ctx);
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
schema: querySchema.$ref ? { $ref: querySchema.$ref } : querySchema,
|
|
1024
|
-
style: "deepObject",
|
|
1025
|
-
explode
|
|
1026
|
-
};
|
|
1027
|
-
if (queryStyle.allowReserved !== void 0) {
|
|
1028
|
-
deepParam.allowReserved = queryStyle.allowReserved;
|
|
1029
|
-
}
|
|
1030
|
-
parameters.push(deepParam);
|
|
1031
|
-
} else {
|
|
1032
|
-
if (!querySchema.properties) return;
|
|
1033
|
-
const queryObjProps = querySchema.properties;
|
|
1034
|
-
for (const [propName, propSchema] of Object.entries(queryObjProps)) {
|
|
1035
|
-
const isRequired = querySchema.required?.includes(propName) ?? false;
|
|
1036
|
-
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) {
|
|
1037
1106
|
parameters.push({
|
|
1038
1107
|
name: propName,
|
|
1039
1108
|
in: "query",
|
|
1040
1109
|
required: isRequired,
|
|
1041
|
-
|
|
1042
|
-
|
|
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" }))}`
|
|
1043
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);
|
|
1044
1146
|
}
|
|
1045
1147
|
}
|
|
1046
1148
|
}
|
|
@@ -1054,14 +1156,28 @@ function buildQueryParameters(operation, ctx, parameters) {
|
|
|
1054
1156
|
paramSchema = mergeFragments(paramSchema, ...frags);
|
|
1055
1157
|
}
|
|
1056
1158
|
}
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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
|
+
}
|
|
1065
1181
|
}
|
|
1066
1182
|
}
|
|
1067
1183
|
}
|
|
@@ -1111,14 +1227,15 @@ function buildCookieParameters(operation, ctx, parameters) {
|
|
|
1111
1227
|
}
|
|
1112
1228
|
|
|
1113
1229
|
// src/compiler/manifest/emit.ts
|
|
1114
|
-
import
|
|
1230
|
+
import ts6 from "typescript";
|
|
1115
1231
|
function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
|
|
1116
1232
|
const components = /* @__PURE__ */ new Map();
|
|
1117
1233
|
const ctx = {
|
|
1118
1234
|
checker,
|
|
1119
1235
|
components,
|
|
1120
1236
|
typeStack: /* @__PURE__ */ new Set(),
|
|
1121
|
-
typeNameStack: []
|
|
1237
|
+
typeNameStack: [],
|
|
1238
|
+
mode: "request"
|
|
1122
1239
|
};
|
|
1123
1240
|
const controllerEntries = controllers.map((ctrl) => ({
|
|
1124
1241
|
controllerId: ctrl.className,
|
|
@@ -1132,7 +1249,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
|
|
|
1132
1249
|
generator: {
|
|
1133
1250
|
name: "adorn-api",
|
|
1134
1251
|
version,
|
|
1135
|
-
typescript:
|
|
1252
|
+
typescript: ts6.version
|
|
1136
1253
|
},
|
|
1137
1254
|
schemas: {
|
|
1138
1255
|
kind: "openapi-3.1",
|
|
@@ -1143,14 +1260,70 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
|
|
|
1143
1260
|
controllers: controllerEntries
|
|
1144
1261
|
};
|
|
1145
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
|
+
}
|
|
1146
1320
|
function buildOperationEntry(op, ctx) {
|
|
1147
1321
|
const args = {
|
|
1148
1322
|
body: null,
|
|
1149
1323
|
path: [],
|
|
1150
1324
|
query: [],
|
|
1151
1325
|
headers: [],
|
|
1152
|
-
cookies: []
|
|
1153
|
-
paginationParamIndex: op.paginationParamIndex
|
|
1326
|
+
cookies: []
|
|
1154
1327
|
};
|
|
1155
1328
|
buildPathArgs(op, ctx, args);
|
|
1156
1329
|
buildQueryArgs(op, ctx, args);
|
|
@@ -1217,53 +1390,39 @@ function buildPathArgs(op, ctx, args) {
|
|
|
1217
1390
|
function buildQueryArgs(op, ctx, args) {
|
|
1218
1391
|
if (op.queryObjectParamIndex !== null) {
|
|
1219
1392
|
const queryParam = op.parameters[op.queryObjectParamIndex];
|
|
1220
|
-
if (queryParam)
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
schemaRef,
|
|
1230
|
-
schemaType: querySchema.type,
|
|
1231
|
-
serialization: {
|
|
1232
|
-
style: "deepObject",
|
|
1233
|
-
explode: queryStyle.explode ?? true,
|
|
1234
|
-
allowReserved: queryStyle.allowReserved
|
|
1235
|
-
}
|
|
1236
|
-
});
|
|
1237
|
-
} else {
|
|
1238
|
-
if (!querySchema.properties) return;
|
|
1239
|
-
for (const [propName, propSchema] of Object.entries(querySchema.properties)) {
|
|
1240
|
-
const isRequired = querySchema.required?.includes(propName) ?? false;
|
|
1241
|
-
let schemaRef = propSchema.$ref;
|
|
1242
|
-
if (!schemaRef) {
|
|
1243
|
-
schemaRef = "#/components/schemas/InlineQueryParam";
|
|
1244
|
-
}
|
|
1245
|
-
args.query.push({
|
|
1246
|
-
name: propName,
|
|
1247
|
-
index: queryParam.index,
|
|
1248
|
-
required: !isRequired,
|
|
1249
|
-
schemaRef,
|
|
1250
|
-
schemaType: propSchema.type
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
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";
|
|
1253
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
|
+
});
|
|
1254
1411
|
}
|
|
1255
1412
|
}
|
|
1256
1413
|
for (const paramIndex of op.queryParamIndices) {
|
|
1257
1414
|
const param = op.parameters[paramIndex];
|
|
1258
1415
|
if (param) {
|
|
1259
1416
|
const paramSchema = typeToJsonSchema(param.type, ctx);
|
|
1417
|
+
const isObjectLike = isObjectLikeSchema2(paramSchema, ctx.components);
|
|
1260
1418
|
const schemaRef = paramSchema.$ref ?? "#/components/schemas/InlineQueryParam";
|
|
1261
1419
|
args.query.push({
|
|
1262
1420
|
name: param.name,
|
|
1263
1421
|
index: param.index,
|
|
1264
1422
|
required: !param.isOptional,
|
|
1265
1423
|
schemaRef,
|
|
1266
|
-
schemaType: paramSchema.type
|
|
1424
|
+
schemaType: paramSchema.type,
|
|
1425
|
+
content: isObjectLike ? "application/json" : void 0
|
|
1267
1426
|
});
|
|
1268
1427
|
}
|
|
1269
1428
|
}
|
|
@@ -1558,7 +1717,7 @@ async function isStale(params) {
|
|
|
1558
1717
|
// src/compiler/cache/writeCache.ts
|
|
1559
1718
|
import fs3 from "fs";
|
|
1560
1719
|
import path3 from "path";
|
|
1561
|
-
import
|
|
1720
|
+
import ts8 from "typescript";
|
|
1562
1721
|
function statMtimeMs2(p) {
|
|
1563
1722
|
return fs3.statSync(p).mtimeMs;
|
|
1564
1723
|
}
|
|
@@ -1591,7 +1750,7 @@ function writeCache(params) {
|
|
|
1591
1750
|
generator: {
|
|
1592
1751
|
name: "adorn-api",
|
|
1593
1752
|
version: params.adornVersion,
|
|
1594
|
-
typescript:
|
|
1753
|
+
typescript: ts8.version
|
|
1595
1754
|
},
|
|
1596
1755
|
project: {
|
|
1597
1756
|
tsconfigPath: params.tsconfigAbs,
|
|
@@ -1604,7 +1763,7 @@ function writeCache(params) {
|
|
|
1604
1763
|
}
|
|
1605
1764
|
|
|
1606
1765
|
// src/cli.ts
|
|
1607
|
-
import
|
|
1766
|
+
import ts9 from "typescript";
|
|
1608
1767
|
import process from "process";
|
|
1609
1768
|
var ADORN_VERSION = "0.1.0";
|
|
1610
1769
|
function log(msg) {
|
|
@@ -1615,6 +1774,26 @@ function debug(...args) {
|
|
|
1615
1774
|
console.error("[adorn-api]", ...args);
|
|
1616
1775
|
}
|
|
1617
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
|
+
}
|
|
1618
1797
|
async function buildCommand(args) {
|
|
1619
1798
|
const projectIndex = args.indexOf("-p");
|
|
1620
1799
|
const projectPath = projectIndex !== -1 ? args[projectIndex + 1] : "./tsconfig.json";
|
|
@@ -1632,7 +1811,7 @@ async function buildCommand(args) {
|
|
|
1632
1811
|
outDir: outputDir,
|
|
1633
1812
|
project: projectPath,
|
|
1634
1813
|
adornVersion: ADORN_VERSION,
|
|
1635
|
-
typescriptVersion:
|
|
1814
|
+
typescriptVersion: ts9.version
|
|
1636
1815
|
});
|
|
1637
1816
|
if (!stale.stale) {
|
|
1638
1817
|
log("adorn-api: artifacts up-to-date");
|
|
@@ -1653,7 +1832,7 @@ async function buildCommand(args) {
|
|
|
1653
1832
|
const openapi = generateOpenAPI(controllers, checker, { title: "API", version: "1.0.0" });
|
|
1654
1833
|
const manifest = generateManifest(controllers, checker, ADORN_VERSION, validationMode);
|
|
1655
1834
|
mkdirSync(outputPath, { recursive: true });
|
|
1656
|
-
writeFileSync(resolve2(outputPath, "openapi.json"), JSON.stringify(openapi, null, 2));
|
|
1835
|
+
writeFileSync(resolve2(outputPath, "openapi.json"), JSON.stringify(sanitizeForJson(openapi), null, 2));
|
|
1657
1836
|
writeFileSync(resolve2(outputPath, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
1658
1837
|
if (validationMode === "precompiled") {
|
|
1659
1838
|
log("Generating precompiled validators...");
|