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