adorn-api 1.0.10 → 1.0.12

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