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.
Files changed (64) hide show
  1. package/README.md +318 -620
  2. package/dist/adapter/express/auth.d.ts +5 -0
  3. package/dist/adapter/express/auth.d.ts.map +1 -0
  4. package/dist/adapter/express/coercion.d.ts +22 -0
  5. package/dist/adapter/express/coercion.d.ts.map +1 -0
  6. package/dist/adapter/express/index.d.ts +3 -50
  7. package/dist/adapter/express/index.d.ts.map +1 -1
  8. package/dist/adapter/express/merge.d.ts +0 -3
  9. package/dist/adapter/express/merge.d.ts.map +1 -1
  10. package/dist/adapter/express/openapi.d.ts +11 -0
  11. package/dist/adapter/express/openapi.d.ts.map +1 -0
  12. package/dist/adapter/express/router.d.ts +4 -0
  13. package/dist/adapter/express/router.d.ts.map +1 -0
  14. package/dist/adapter/express/swagger.d.ts +4 -0
  15. package/dist/adapter/express/swagger.d.ts.map +1 -0
  16. package/dist/adapter/express/types.d.ts +64 -0
  17. package/dist/adapter/express/types.d.ts.map +1 -0
  18. package/dist/adapter/express/validation.d.ts +10 -0
  19. package/dist/adapter/express/validation.d.ts.map +1 -0
  20. package/dist/cli.cjs +332 -153
  21. package/dist/cli.cjs.map +1 -1
  22. package/dist/cli.js +331 -152
  23. package/dist/cli.js.map +1 -1
  24. package/dist/compiler/analyze/scanControllers.d.ts +0 -1
  25. package/dist/compiler/analyze/scanControllers.d.ts.map +1 -1
  26. package/dist/compiler/manifest/emit.d.ts.map +1 -1
  27. package/dist/compiler/manifest/format.d.ts +1 -1
  28. package/dist/compiler/manifest/format.d.ts.map +1 -1
  29. package/dist/compiler/schema/openapi.d.ts.map +1 -1
  30. package/dist/compiler/schema/typeToJsonSchema.d.ts +7 -1
  31. package/dist/compiler/schema/typeToJsonSchema.d.ts.map +1 -1
  32. package/dist/decorators/index.d.ts +0 -1
  33. package/dist/decorators/index.d.ts.map +1 -1
  34. package/dist/express.cjs +619 -596
  35. package/dist/express.cjs.map +1 -1
  36. package/dist/express.js +616 -593
  37. package/dist/express.js.map +1 -1
  38. package/dist/http.d.ts +1 -9
  39. package/dist/http.d.ts.map +1 -1
  40. package/dist/index.cjs +2 -35
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.ts +3 -4
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1 -33
  45. package/dist/index.js.map +1 -1
  46. package/dist/metal/applyListQuery.d.ts +27 -0
  47. package/dist/metal/applyListQuery.d.ts.map +1 -0
  48. package/dist/metal/index.cjs +59 -0
  49. package/dist/metal/index.cjs.map +1 -1
  50. package/dist/metal/index.d.ts +4 -0
  51. package/dist/metal/index.d.ts.map +1 -1
  52. package/dist/metal/index.js +55 -0
  53. package/dist/metal/index.js.map +1 -1
  54. package/dist/metal/listQuery.d.ts +7 -0
  55. package/dist/metal/listQuery.d.ts.map +1 -0
  56. package/dist/metal/queryOptions.d.ts +8 -0
  57. package/dist/metal/queryOptions.d.ts.map +1 -0
  58. package/dist/runtime/metadata/types.d.ts +0 -3
  59. package/dist/runtime/metadata/types.d.ts.map +1 -1
  60. package/package.json +1 -1
  61. package/dist/compiler/analyze/extractQueryStyle.d.ts +0 -8
  62. package/dist/compiler/analyze/extractQueryStyle.d.ts.map +0 -1
  63. package/dist/decorators/Paginated.d.ts +0 -5
  64. package/dist/decorators/Paginated.d.ts.map +0 -1
package/dist/cli.cjs CHANGED
@@ -171,7 +171,7 @@ function analyzeMethod(node, className, checker) {
171
171
  }
172
172
  const pathParamNames = extractPathParams(path4);
173
173
  const pathParamIndices = matchPathParamsToIndices(pathParamNames, parameters);
174
- const { bodyParamIndex, queryParamIndices, queryObjectParamIndex, headerObjectParamIndex, cookieObjectParamIndex, paginationParamIndex, bodyContentType } = classifyParameters(parameters, httpMethod, pathParamIndices, checker);
174
+ const { bodyParamIndex, queryParamIndices, queryObjectParamIndex, headerObjectParamIndex, cookieObjectParamIndex, bodyContentType } = classifyParameters(parameters, httpMethod, pathParamIndices, checker);
175
175
  return {
176
176
  methodName,
177
177
  httpMethod,
@@ -187,7 +187,6 @@ function analyzeMethod(node, className, checker) {
187
187
  queryObjectParamIndex,
188
188
  headerObjectParamIndex,
189
189
  cookieObjectParamIndex,
190
- paginationParamIndex,
191
190
  bodyContentType
192
191
  };
193
192
  }
@@ -213,7 +212,6 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
213
212
  let queryObjectParamIndex = null;
214
213
  let headerObjectParamIndex = null;
215
214
  let cookieObjectParamIndex = null;
216
- let paginationParamIndex = null;
217
215
  const isBodyMethod = ["POST", "PUT", "PATCH"].includes(httpMethod);
218
216
  for (let i = 0; i < parameters.length; i++) {
219
217
  const param = parameters[i];
@@ -240,11 +238,6 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
240
238
  usedIndices.add(i);
241
239
  continue;
242
240
  }
243
- if (typeStr === "PaginationParams") {
244
- paginationParamIndex = i;
245
- usedIndices.add(i);
246
- continue;
247
- }
248
241
  if (isBodyMethod && bodyParamIndex === null) {
249
242
  bodyParamIndex = i;
250
243
  usedIndices.add(i);
@@ -265,19 +258,19 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
265
258
  queryObjectParamIndex,
266
259
  headerObjectParamIndex,
267
260
  cookieObjectParamIndex,
268
- paginationParamIndex,
269
261
  bodyContentType: void 0
270
262
  };
271
263
  }
272
264
  function isObjectType(type, checker) {
273
265
  const objectFlags = (type.flags & import_typescript2.default.TypeFlags.Object) !== 0;
274
- if (!objectFlags) return false;
266
+ const intersectionFlags = (type.flags & import_typescript2.default.TypeFlags.Intersection) !== 0;
267
+ if (!objectFlags && !intersectionFlags) return false;
275
268
  const symbol = type.getSymbol();
276
269
  if (symbol?.getName() === "__object") return true;
277
270
  const properties = checker.getPropertiesOfType(type);
278
271
  if (properties.length > 0) return true;
279
- const callSignatures = type.getCallSignatures();
280
- if (callSignatures && callSignatures.length > 0) return false;
272
+ const callSigs = type.getCallSignatures?.();
273
+ if (callSigs && callSigs.length > 0) return false;
281
274
  return true;
282
275
  }
283
276
  function getTypeName(type) {
@@ -329,7 +322,7 @@ function unwrapPromiseTypeNode(typeNode) {
329
322
  }
330
323
 
331
324
  // src/compiler/schema/openapi.ts
332
- var import_typescript6 = __toESM(require("typescript"), 1);
325
+ var import_typescript5 = __toESM(require("typescript"), 1);
333
326
 
334
327
  // src/compiler/schema/typeToJsonSchema.ts
335
328
  var import_typescript3 = __toESM(require("typescript"), 1);
@@ -348,7 +341,7 @@ function typeToJsonSchema(type, ctx, typeNode) {
348
341
  return { type: "string" };
349
342
  }
350
343
  if (type.flags & import_typescript3.default.TypeFlags.Number) {
351
- return { type: "number" };
344
+ return normalizeNumericType(type, checker, typeNode);
352
345
  }
353
346
  if (type.flags & import_typescript3.default.TypeFlags.Boolean) {
354
347
  return { type: "boolean" };
@@ -610,9 +603,12 @@ function getBranchSchemaName(type, ctx) {
610
603
  return `Anonymous_${ctx.typeNameStack.length}`;
611
604
  }
612
605
  function handleObjectType(type, ctx, typeNode) {
613
- const { checker, components, typeStack } = ctx;
606
+ const { checker, components, typeStack, mode } = ctx;
614
607
  const symbol = type.getSymbol();
615
608
  const typeName = symbol?.getName?.() ?? getTypeNameFromNode(typeNode, ctx);
609
+ if (isMetalOrmWrapperType(type, checker)) {
610
+ return handleMetalOrmWrapper(type, ctx);
611
+ }
616
612
  if (typeName && typeName !== "__type") {
617
613
  if (components.has(typeName)) {
618
614
  return { $ref: `#/components/schemas/${typeName}` };
@@ -625,7 +621,8 @@ function handleObjectType(type, ctx, typeNode) {
625
621
  const schema = buildObjectSchema(type, ctx, typeNode);
626
622
  if (typeName && typeName !== "__type") {
627
623
  typeStack.delete(type);
628
- if (!components.has(typeName)) {
624
+ const existing = components.get(typeName);
625
+ if (!existing) {
629
626
  components.set(typeName, schema);
630
627
  }
631
628
  return { $ref: `#/components/schemas/${typeName}` };
@@ -647,22 +644,44 @@ function getExplicitTypeNameFromNode(typeNode) {
647
644
  }
648
645
  return null;
649
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
+ }
650
661
  function getTypeNameFromNode(typeNode, ctx) {
651
662
  const explicitName = getExplicitTypeNameFromNode(typeNode);
652
663
  if (explicitName) return explicitName;
653
664
  return `Anonymous_${ctx.typeNameStack.length}`;
654
665
  }
655
666
  function buildObjectSchema(type, ctx, typeNode) {
656
- const { checker } = ctx;
667
+ const { checker, mode } = ctx;
657
668
  const properties = {};
658
669
  const required = [];
659
670
  const props = checker.getPropertiesOfType(type);
660
671
  for (const prop of props) {
661
672
  const propName = prop.getName();
673
+ if (isIteratorOrSymbolProperty(propName)) {
674
+ continue;
675
+ }
662
676
  const propType = checker.getTypeOfSymbol(prop);
677
+ if (isMethodLike(propType)) {
678
+ continue;
679
+ }
663
680
  const isOptional = !!(prop.flags & import_typescript3.default.SymbolFlags.Optional);
681
+ const isRelation = isMetalOrmWrapperType(propType, checker);
664
682
  properties[propName] = typeToJsonSchema(propType, ctx);
665
- if (!isOptional) {
683
+ const shouldRequire = mode === "response" ? !isRelation && !isOptional : !isOptional;
684
+ if (shouldRequire) {
666
685
  required.push(propName);
667
686
  }
668
687
  }
@@ -701,6 +720,56 @@ function getRecordValueType(type, checker) {
701
720
  }
702
721
  return null;
703
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
+ }
704
773
 
705
774
  // src/compiler/schema/extractAnnotations.ts
706
775
  var import_typescript4 = __toESM(require("typescript"), 1);
@@ -848,56 +917,6 @@ function mergeFragments(base, ...frags) {
848
917
  return result;
849
918
  }
850
919
 
851
- // src/compiler/analyze/extractQueryStyle.ts
852
- var import_typescript5 = __toESM(require("typescript"), 1);
853
- function extractQueryStyleOptions(checker, method) {
854
- if (!import_typescript5.default.canHaveDecorators(method)) return null;
855
- const decorators = import_typescript5.default.getDecorators(method);
856
- if (!decorators || decorators.length === 0) return null;
857
- for (const decorator of decorators) {
858
- const expr = decorator.expression;
859
- const isCall = import_typescript5.default.isCallExpression(expr);
860
- const callee = isCall ? expr.expression : expr;
861
- const args = isCall ? expr.arguments : import_typescript5.default.factory.createNodeArray([]);
862
- const sym = checker.getSymbolAtLocation(callee);
863
- if (!sym) continue;
864
- const resolved = sym.flags & import_typescript5.default.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
865
- const name = resolved.getName();
866
- if (name !== "QueryStyle") continue;
867
- const optsNode = args[0];
868
- if (!optsNode || !import_typescript5.default.isObjectLiteralExpression(optsNode)) {
869
- return {};
870
- }
871
- return parseQueryStyleOptions(optsNode);
872
- }
873
- return null;
874
- }
875
- function parseQueryStyleOptions(node) {
876
- const opts = {};
877
- for (const prop of node.properties) {
878
- if (!import_typescript5.default.isPropertyAssignment(prop)) continue;
879
- const name = getPropName(prop.name);
880
- if (!name) continue;
881
- if (name === "style" && import_typescript5.default.isStringLiteral(prop.initializer)) {
882
- const style = prop.initializer.text;
883
- opts.style = style;
884
- } else if (name === "explode" && isBooleanLiteral(prop.initializer)) {
885
- opts.explode = prop.initializer.kind === import_typescript5.default.SyntaxKind.TrueKeyword;
886
- } else if (name === "allowReserved" && isBooleanLiteral(prop.initializer)) {
887
- opts.allowReserved = prop.initializer.kind === import_typescript5.default.SyntaxKind.TrueKeyword;
888
- }
889
- }
890
- return opts;
891
- }
892
- function getPropName(name) {
893
- if (import_typescript5.default.isIdentifier(name)) return name.text;
894
- if (import_typescript5.default.isStringLiteral(name)) return name.text;
895
- return null;
896
- }
897
- function isBooleanLiteral(node) {
898
- return node.kind === import_typescript5.default.SyntaxKind.TrueKeyword || node.kind === import_typescript5.default.SyntaxKind.FalseKeyword;
899
- }
900
-
901
920
  // src/compiler/schema/openapi.ts
902
921
  function generateOpenAPI(controllers, checker, options = {}) {
903
922
  const components = /* @__PURE__ */ new Map();
@@ -905,7 +924,8 @@ function generateOpenAPI(controllers, checker, options = {}) {
905
924
  checker,
906
925
  components,
907
926
  typeStack: /* @__PURE__ */ new Set(),
908
- typeNameStack: []
927
+ typeNameStack: [],
928
+ mode: "response"
909
929
  };
910
930
  const paths = {};
911
931
  for (const controller of controllers) {
@@ -933,7 +953,11 @@ function generateOpenAPI(controllers, checker, options = {}) {
933
953
  function convertToOpenApiPath(basePath, path4) {
934
954
  const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
935
955
  const converted = path4.replace(/:([^/]+)/g, "{$1}");
936
- return base + converted || "/";
956
+ let fullPath = base + converted || "/";
957
+ if (fullPath.endsWith("/") && fullPath !== "/") {
958
+ fullPath = fullPath.slice(0, -1);
959
+ }
960
+ return fullPath;
937
961
  }
938
962
  function buildOperation(operation, ctx, controllerConsumes) {
939
963
  const op = {
@@ -948,7 +972,8 @@ function buildOperation(operation, ctx, controllerConsumes) {
948
972
  if (parameters.length > 0) {
949
973
  op.parameters = parameters;
950
974
  }
951
- const responseSchema = typeToJsonSchema(operation.returnType, ctx, operation.returnTypeNode);
975
+ const responseCtx = { ...ctx, mode: "response" };
976
+ const responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
952
977
  const status = operation.httpMethod === "POST" ? 201 : 200;
953
978
  op.responses[status] = {
954
979
  description: status === 201 ? "Created" : "OK",
@@ -961,8 +986,9 @@ function buildOperation(operation, ctx, controllerConsumes) {
961
986
  if (["POST", "PUT", "PATCH"].includes(operation.httpMethod) && operation.bodyParamIndex !== null) {
962
987
  const bodyParam = operation.parameters[operation.bodyParamIndex];
963
988
  if (bodyParam) {
964
- let bodySchema = typeToJsonSchema(bodyParam.type, ctx);
965
- bodySchema = mergeBodySchemaAnnotations(bodyParam, ctx, bodySchema);
989
+ const requestCtx = { ...ctx, mode: "request" };
990
+ let bodySchema = typeToJsonSchema(bodyParam.type, requestCtx);
991
+ bodySchema = mergeBodySchemaAnnotations(bodyParam, requestCtx, bodySchema);
966
992
  const contentType = operation.bodyContentType ?? controllerConsumes?.[0] ?? "application/json";
967
993
  const requestBody = {
968
994
  required: !bodyParam.isOptional,
@@ -989,12 +1015,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
989
1015
  const declarations = typeSymbol.getDeclarations();
990
1016
  if (!declarations || declarations.length === 0) return schema;
991
1017
  const classDecl = declarations[0];
992
- if (!import_typescript6.default.isClassDeclaration(classDecl)) return schema;
1018
+ if (!import_typescript5.default.isClassDeclaration(classDecl)) return schema;
993
1019
  const result = { ...schema };
994
1020
  const props = { ...result.properties };
995
1021
  for (const member of classDecl.members) {
996
- if (!import_typescript6.default.isPropertyDeclaration(member) || !member.name) continue;
997
- const propName = import_typescript6.default.isIdentifier(member.name) ? member.name.text : null;
1022
+ if (!import_typescript5.default.isPropertyDeclaration(member) || !member.name) continue;
1023
+ const propName = import_typescript5.default.isIdentifier(member.name) ? member.name.text : null;
998
1024
  if (!propName) continue;
999
1025
  if (!props[propName]) continue;
1000
1026
  const frags = extractPropertySchemaFragments(ctx.checker, member);
@@ -1016,48 +1042,124 @@ function buildPathParameters(operation, ctx, parameters) {
1016
1042
  paramSchema = mergeFragments(paramSchema, ...frags);
1017
1043
  }
1018
1044
  }
1045
+ const schema = paramSchema.$ref ? { $ref: paramSchema.$ref } : paramSchema;
1019
1046
  parameters.push({
1020
1047
  name: param.name,
1021
1048
  in: "path",
1022
1049
  required: !param.isOptional,
1023
- schema: paramSchema.$ref ? { type: "string", $ref: paramSchema.$ref } : paramSchema
1050
+ schema
1024
1051
  });
1025
1052
  }
1026
1053
  }
1027
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
+ }
1028
1112
  function buildQueryParameters(operation, ctx, parameters) {
1029
1113
  if (operation.queryObjectParamIndex !== null) {
1030
1114
  const queryParam = operation.parameters[operation.queryObjectParamIndex];
1031
1115
  if (!queryParam) return;
1032
- const queryStyle = extractQueryStyleOptions(ctx.checker, operation.methodDeclaration);
1033
1116
  const querySchema = typeToJsonSchema(queryParam.type, ctx);
1034
- if (queryStyle?.style === "deepObject") {
1035
- const explode = queryStyle.explode ?? true;
1036
- const deepParam = {
1037
- name: queryParam.name,
1038
- in: "query",
1039
- required: !queryParam.isOptional,
1040
- schema: querySchema.$ref ? { $ref: querySchema.$ref } : querySchema,
1041
- style: "deepObject",
1042
- explode
1043
- };
1044
- if (queryStyle.allowReserved !== void 0) {
1045
- deepParam.allowReserved = queryStyle.allowReserved;
1046
- }
1047
- parameters.push(deepParam);
1048
- } else {
1049
- if (!querySchema.properties) return;
1050
- const queryObjProps = querySchema.properties;
1051
- for (const [propName, propSchema] of Object.entries(queryObjProps)) {
1052
- const isRequired = querySchema.required?.includes(propName) ?? false;
1053
- 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) {
1054
1123
  parameters.push({
1055
1124
  name: propName,
1056
1125
  in: "query",
1057
1126
  required: isRequired,
1058
- schema: propSchema,
1059
- ...Object.keys(serialization).length > 0 ? serialization : {}
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" }))}`
1060
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);
1061
1163
  }
1062
1164
  }
1063
1165
  }
@@ -1071,14 +1173,28 @@ function buildQueryParameters(operation, ctx, parameters) {
1071
1173
  paramSchema = mergeFragments(paramSchema, ...frags);
1072
1174
  }
1073
1175
  }
1074
- const serialization = determineQuerySerialization(paramSchema.type);
1075
- parameters.push({
1076
- name: param.name,
1077
- in: "query",
1078
- required: !param.isOptional,
1079
- schema: paramSchema.$ref ? { type: "string", $ref: paramSchema.$ref } : paramSchema,
1080
- ...Object.keys(serialization).length > 0 ? serialization : {}
1081
- });
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
+ }
1082
1198
  }
1083
1199
  }
1084
1200
  }
@@ -1128,14 +1244,15 @@ function buildCookieParameters(operation, ctx, parameters) {
1128
1244
  }
1129
1245
 
1130
1246
  // src/compiler/manifest/emit.ts
1131
- var import_typescript7 = __toESM(require("typescript"), 1);
1247
+ var import_typescript6 = __toESM(require("typescript"), 1);
1132
1248
  function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
1133
1249
  const components = /* @__PURE__ */ new Map();
1134
1250
  const ctx = {
1135
1251
  checker,
1136
1252
  components,
1137
1253
  typeStack: /* @__PURE__ */ new Set(),
1138
- typeNameStack: []
1254
+ typeNameStack: [],
1255
+ mode: "request"
1139
1256
  };
1140
1257
  const controllerEntries = controllers.map((ctrl) => ({
1141
1258
  controllerId: ctrl.className,
@@ -1149,7 +1266,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
1149
1266
  generator: {
1150
1267
  name: "adorn-api",
1151
1268
  version,
1152
- typescript: import_typescript7.default.version
1269
+ typescript: import_typescript6.default.version
1153
1270
  },
1154
1271
  schemas: {
1155
1272
  kind: "openapi-3.1",
@@ -1160,14 +1277,70 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
1160
1277
  controllers: controllerEntries
1161
1278
  };
1162
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
+ }
1163
1337
  function buildOperationEntry(op, ctx) {
1164
1338
  const args = {
1165
1339
  body: null,
1166
1340
  path: [],
1167
1341
  query: [],
1168
1342
  headers: [],
1169
- cookies: [],
1170
- paginationParamIndex: op.paginationParamIndex
1343
+ cookies: []
1171
1344
  };
1172
1345
  buildPathArgs(op, ctx, args);
1173
1346
  buildQueryArgs(op, ctx, args);
@@ -1234,53 +1407,39 @@ function buildPathArgs(op, ctx, args) {
1234
1407
  function buildQueryArgs(op, ctx, args) {
1235
1408
  if (op.queryObjectParamIndex !== null) {
1236
1409
  const queryParam = op.parameters[op.queryObjectParamIndex];
1237
- if (queryParam) {
1238
- const queryStyle = extractQueryStyleOptions(ctx.checker, op.methodDeclaration);
1239
- const querySchema = typeToJsonSchema(queryParam.type, ctx);
1240
- if (queryStyle?.style === "deepObject") {
1241
- const schemaRef = querySchema.$ref ?? "#/components/schemas/InlineQueryParam";
1242
- args.query.push({
1243
- name: queryParam.name,
1244
- index: queryParam.index,
1245
- required: !queryParam.isOptional,
1246
- schemaRef,
1247
- schemaType: querySchema.type,
1248
- serialization: {
1249
- style: "deepObject",
1250
- explode: queryStyle.explode ?? true,
1251
- allowReserved: queryStyle.allowReserved
1252
- }
1253
- });
1254
- } else {
1255
- if (!querySchema.properties) return;
1256
- for (const [propName, propSchema] of Object.entries(querySchema.properties)) {
1257
- const isRequired = querySchema.required?.includes(propName) ?? false;
1258
- let schemaRef = propSchema.$ref;
1259
- if (!schemaRef) {
1260
- schemaRef = "#/components/schemas/InlineQueryParam";
1261
- }
1262
- args.query.push({
1263
- name: propName,
1264
- index: queryParam.index,
1265
- required: !isRequired,
1266
- schemaRef,
1267
- schemaType: propSchema.type
1268
- });
1269
- }
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";
1270
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
+ });
1271
1428
  }
1272
1429
  }
1273
1430
  for (const paramIndex of op.queryParamIndices) {
1274
1431
  const param = op.parameters[paramIndex];
1275
1432
  if (param) {
1276
1433
  const paramSchema = typeToJsonSchema(param.type, ctx);
1434
+ const isObjectLike = isObjectLikeSchema2(paramSchema, ctx.components);
1277
1435
  const schemaRef = paramSchema.$ref ?? "#/components/schemas/InlineQueryParam";
1278
1436
  args.query.push({
1279
1437
  name: param.name,
1280
1438
  index: param.index,
1281
1439
  required: !param.isOptional,
1282
1440
  schemaRef,
1283
- schemaType: paramSchema.type
1441
+ schemaType: paramSchema.type,
1442
+ content: isObjectLike ? "application/json" : void 0
1284
1443
  });
1285
1444
  }
1286
1445
  }
@@ -1469,7 +1628,7 @@ export function validateResponse(operationId, status, contentType, data) {
1469
1628
  // src/compiler/cache/isStale.ts
1470
1629
  var import_node_fs3 = __toESM(require("fs"), 1);
1471
1630
  var import_node_path3 = __toESM(require("path"), 1);
1472
- var import_typescript8 = require("typescript");
1631
+ var import_typescript7 = require("typescript");
1473
1632
  var import_meta = {};
1474
1633
  function readJson(p) {
1475
1634
  try {
@@ -1576,7 +1735,7 @@ async function isStale(params) {
1576
1735
  // src/compiler/cache/writeCache.ts
1577
1736
  var import_node_fs4 = __toESM(require("fs"), 1);
1578
1737
  var import_node_path4 = __toESM(require("path"), 1);
1579
- var import_typescript9 = __toESM(require("typescript"), 1);
1738
+ var import_typescript8 = __toESM(require("typescript"), 1);
1580
1739
  function statMtimeMs2(p) {
1581
1740
  return import_node_fs4.default.statSync(p).mtimeMs;
1582
1741
  }
@@ -1609,7 +1768,7 @@ function writeCache(params) {
1609
1768
  generator: {
1610
1769
  name: "adorn-api",
1611
1770
  version: params.adornVersion,
1612
- typescript: import_typescript9.default.version
1771
+ typescript: import_typescript8.default.version
1613
1772
  },
1614
1773
  project: {
1615
1774
  tsconfigPath: params.tsconfigAbs,
@@ -1622,7 +1781,7 @@ function writeCache(params) {
1622
1781
  }
1623
1782
 
1624
1783
  // src/cli.ts
1625
- var import_typescript10 = __toESM(require("typescript"), 1);
1784
+ var import_typescript9 = __toESM(require("typescript"), 1);
1626
1785
  var import_node_process = __toESM(require("process"), 1);
1627
1786
  var ADORN_VERSION = "0.1.0";
1628
1787
  function log(msg) {
@@ -1633,6 +1792,26 @@ function debug(...args) {
1633
1792
  console.error("[adorn-api]", ...args);
1634
1793
  }
1635
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
+ }
1636
1815
  async function buildCommand(args) {
1637
1816
  const projectIndex = args.indexOf("-p");
1638
1817
  const projectPath = projectIndex !== -1 ? args[projectIndex + 1] : "./tsconfig.json";
@@ -1650,7 +1829,7 @@ async function buildCommand(args) {
1650
1829
  outDir: outputDir,
1651
1830
  project: projectPath,
1652
1831
  adornVersion: ADORN_VERSION,
1653
- typescriptVersion: import_typescript10.default.version
1832
+ typescriptVersion: import_typescript9.default.version
1654
1833
  });
1655
1834
  if (!stale.stale) {
1656
1835
  log("adorn-api: artifacts up-to-date");
@@ -1671,7 +1850,7 @@ async function buildCommand(args) {
1671
1850
  const openapi = generateOpenAPI(controllers, checker, { title: "API", version: "1.0.0" });
1672
1851
  const manifest = generateManifest(controllers, checker, ADORN_VERSION, validationMode);
1673
1852
  (0, import_node_fs5.mkdirSync)(outputPath, { recursive: true });
1674
- (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));
1675
1854
  (0, import_node_fs5.writeFileSync)((0, import_node_path5.resolve)(outputPath, "manifest.json"), JSON.stringify(manifest, null, 2));
1676
1855
  if (validationMode === "precompiled") {
1677
1856
  log("Generating precompiled validators...");