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.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, paginationParamIndex, bodyContentType } = classifyParameters(parameters, httpMethod, pathParamIndices, checker);
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
- if (!objectFlags) return false;
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 callSignatures = type.getCallSignatures();
263
- if (callSignatures && callSignatures.length > 0) return false;
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 ts6 from "typescript";
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 { type: "number" };
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
- if (!components.has(typeName)) {
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
- if (!isOptional) {
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
- return base + converted || "/";
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 responseSchema = typeToJsonSchema(operation.returnType, ctx, operation.returnTypeNode);
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
- let bodySchema = typeToJsonSchema(bodyParam.type, ctx);
948
- bodySchema = mergeBodySchemaAnnotations(bodyParam, ctx, 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 (!ts6.isClassDeclaration(classDecl)) return schema;
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 (!ts6.isPropertyDeclaration(member) || !member.name) continue;
980
- const propName = ts6.isIdentifier(member.name) ? member.name.text : null;
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: paramSchema.$ref ? { type: "string", $ref: paramSchema.$ref } : paramSchema
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
- if (queryStyle?.style === "deepObject") {
1018
- const explode = queryStyle.explode ?? true;
1019
- const deepParam = {
1020
- name: queryParam.name,
1021
- in: "query",
1022
- required: !queryParam.isOptional,
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
- schema: propSchema,
1042
- ...Object.keys(serialization).length > 0 ? serialization : {}
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 serialization = determineQuerySerialization(paramSchema.type);
1058
- parameters.push({
1059
- name: param.name,
1060
- in: "query",
1061
- required: !param.isOptional,
1062
- schema: paramSchema.$ref ? { type: "string", $ref: paramSchema.$ref } : paramSchema,
1063
- ...Object.keys(serialization).length > 0 ? serialization : {}
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 ts7 from "typescript";
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: ts7.version
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
- const queryStyle = extractQueryStyleOptions(ctx.checker, op.methodDeclaration);
1222
- const querySchema = typeToJsonSchema(queryParam.type, ctx);
1223
- if (queryStyle?.style === "deepObject") {
1224
- const schemaRef = querySchema.$ref ?? "#/components/schemas/InlineQueryParam";
1225
- args.query.push({
1226
- name: queryParam.name,
1227
- index: queryParam.index,
1228
- required: !queryParam.isOptional,
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 ts9 from "typescript";
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: ts9.version
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 ts10 from "typescript";
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: ts10.version
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...");