adorn-api 1.0.20 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -323,7 +323,7 @@ function unwrapPromiseTypeNode(typeNode) {
323
323
  }
324
324
 
325
325
  // src/compiler/schema/openapi.ts
326
- var import_typescript9 = __toESM(require("typescript"), 1);
326
+ var import_typescript11 = __toESM(require("typescript"), 1);
327
327
 
328
328
  // src/compiler/schema/typeToJsonSchema.ts
329
329
  var import_typescript7 = __toESM(require("typescript"), 1);
@@ -718,6 +718,48 @@ function getExplicitTypeNameFromNode3(typeNode) {
718
718
 
719
719
  // src/compiler/schema/objectHandler.ts
720
720
  var import_typescript6 = __toESM(require("typescript"), 1);
721
+ function getTypeParameterName(type) {
722
+ if (type.flags & import_typescript6.default.TypeFlags.TypeParameter) {
723
+ const typeParam = type;
724
+ return typeParam.symbol?.getName() ?? null;
725
+ }
726
+ return null;
727
+ }
728
+ function getTypeArguments(type) {
729
+ const typeRef = type;
730
+ const args = typeRef.typeArguments;
731
+ if (!args) return void 0;
732
+ return Array.from(args);
733
+ }
734
+ function createTypeParameterSubstitutions(type, typeNode, checker) {
735
+ const typeArgs = getTypeArguments(type);
736
+ if (!typeArgs || typeArgs.length === 0) {
737
+ return void 0;
738
+ }
739
+ if (!typeNode || !import_typescript6.default.isTypeReferenceNode(typeNode)) {
740
+ return void 0;
741
+ }
742
+ const typeParams = typeNode.typeArguments;
743
+ if (!typeParams || typeParams.length !== typeArgs.length) {
744
+ return void 0;
745
+ }
746
+ const substitutions = /* @__PURE__ */ new Map();
747
+ for (let i = 0; i < typeParams.length; i++) {
748
+ const typeParamNode = typeParams[i];
749
+ const typeArg = typeArgs[i];
750
+ if (import_typescript6.default.isIdentifier(typeParamNode)) {
751
+ substitutions.set(typeParamNode.text, typeArg);
752
+ }
753
+ }
754
+ return substitutions.size > 0 ? substitutions : void 0;
755
+ }
756
+ function resolveTypeParameter(type, substitutions, _checker) {
757
+ if (!substitutions) return null;
758
+ const paramName = getTypeParameterName(type);
759
+ if (!paramName) return null;
760
+ const resolved = substitutions.get(paramName);
761
+ return resolved ?? null;
762
+ }
721
763
  function handleObjectType(type, ctx, typeNode) {
722
764
  const { checker, components, typeStack } = ctx;
723
765
  const symbol = type.getSymbol();
@@ -737,7 +779,9 @@ function handleObjectType(type, ctx, typeNode) {
737
779
  }
738
780
  typeStack.add(type);
739
781
  }
740
- const schema = buildObjectSchema(type, ctx, typeNode);
782
+ const typeParamSubstitutions = createTypeParameterSubstitutions(type, typeNode, checker);
783
+ const buildCtx = typeParamSubstitutions ? { ...ctx, typeParameterSubstitutions: typeParamSubstitutions } : ctx;
784
+ const schema = buildObjectSchema(type, buildCtx, typeNode);
741
785
  if (typeName && typeName !== "__type") {
742
786
  typeStack.delete(type);
743
787
  const existing = components.get(typeName);
@@ -755,7 +799,7 @@ function handleObjectType(type, ctx, typeNode) {
755
799
  return schema;
756
800
  }
757
801
  function buildObjectSchema(type, ctx, _typeNode) {
758
- const { checker, mode } = ctx;
802
+ const { checker, mode, typeParameterSubstitutions } = ctx;
759
803
  const properties = {};
760
804
  const required = [];
761
805
  const props = checker.getPropertiesOfType(type);
@@ -764,10 +808,14 @@ function buildObjectSchema(type, ctx, _typeNode) {
764
808
  if (isIteratorOrSymbolProperty(propName)) {
765
809
  continue;
766
810
  }
767
- const propType = checker.getTypeOfSymbol(prop);
811
+ let propType = checker.getTypeOfSymbol(prop);
768
812
  if (isMethodLike(propType)) {
769
813
  continue;
770
814
  }
815
+ const resolvedType = resolveTypeParameter(propType, typeParameterSubstitutions, checker);
816
+ if (resolvedType) {
817
+ propType = resolvedType;
818
+ }
771
819
  const isOptional = !!(prop.flags & import_typescript6.default.SymbolFlags.Optional);
772
820
  const isRelation = isMetalOrmWrapperType(propType, checker);
773
821
  const propCtx = { ...ctx, propertyName: propName };
@@ -787,7 +835,8 @@ function buildObjectSchema(type, ctx, _typeNode) {
787
835
  if (isRecordType(type, checker)) {
788
836
  const valueType = getRecordValueType(type, checker);
789
837
  if (valueType) {
790
- schema.additionalProperties = typeToJsonSchema(valueType, ctx);
838
+ const resolvedValueType = resolveTypeParameter(valueType, typeParameterSubstitutions, checker);
839
+ schema.additionalProperties = typeToJsonSchema(resolvedValueType ?? valueType, ctx);
791
840
  }
792
841
  }
793
842
  return schema;
@@ -1579,6 +1628,384 @@ function resolveAndCollectObjectProps(schema, components) {
1579
1628
  return { properties, required };
1580
1629
  }
1581
1630
 
1631
+ // src/compiler/schema/queryBuilderAnalyzer.ts
1632
+ var import_typescript9 = __toESM(require("typescript"), 1);
1633
+ function analyzeQueryBuilderForSchema(methodDeclaration, checker, options = {}) {
1634
+ const body = methodDeclaration.body;
1635
+ if (!body) {
1636
+ return null;
1637
+ }
1638
+ const trackedSchema = analyzeWithVariableTracking(body, checker, options);
1639
+ if (trackedSchema) {
1640
+ return trackedSchema;
1641
+ }
1642
+ const returnStatement = findReturnStatement(body);
1643
+ if (!returnStatement) {
1644
+ return null;
1645
+ }
1646
+ const callChain = analyzeReturnExpression(returnStatement.expression);
1647
+ if (!callChain) {
1648
+ return null;
1649
+ }
1650
+ return parseQueryBuilderChain(callChain, checker, options);
1651
+ }
1652
+ function analyzeQueryBuilderWithDetails(methodDeclaration, checker, options = {}, operationInfo) {
1653
+ const schema = analyzeQueryBuilderForSchema(methodDeclaration, checker, options);
1654
+ return {
1655
+ detected: schema !== null,
1656
+ schema,
1657
+ ...operationInfo
1658
+ };
1659
+ }
1660
+ function analyzeWithVariableTracking(body, checker, options) {
1661
+ let queryBuilderVar = null;
1662
+ let entityName = null;
1663
+ const selectedFields = /* @__PURE__ */ new Set();
1664
+ const includes = {};
1665
+ let isPaged = false;
1666
+ let hasReturn = false;
1667
+ for (const statement of body.statements) {
1668
+ if (import_typescript9.default.isReturnStatement(statement)) {
1669
+ hasReturn = true;
1670
+ const returnExpr = statement.expression;
1671
+ if (returnExpr && import_typescript9.default.isCallExpression(returnExpr)) {
1672
+ const callExpr = returnExpr;
1673
+ if (import_typescript9.default.isIdentifier(callExpr.expression) && queryBuilderVar) {
1674
+ const varName = callExpr.expression.text;
1675
+ if (varName === queryBuilderVar) {
1676
+ const methodName = callExpr.expression.text;
1677
+ if (methodName === "executePaged") {
1678
+ isPaged = true;
1679
+ }
1680
+ }
1681
+ }
1682
+ if (import_typescript9.default.isPropertyAccessExpression(callExpr.expression) && queryBuilderVar) {
1683
+ const propAccess = callExpr.expression;
1684
+ if (import_typescript9.default.isIdentifier(propAccess.expression) && propAccess.expression.text === queryBuilderVar) {
1685
+ const methodName = propAccess.name.text;
1686
+ if (methodName === "executePaged") {
1687
+ isPaged = true;
1688
+ }
1689
+ }
1690
+ }
1691
+ }
1692
+ continue;
1693
+ }
1694
+ if (!import_typescript9.default.isExpressionStatement(statement)) {
1695
+ if (import_typescript9.default.isVariableStatement(statement)) {
1696
+ for (const declaration of statement.declarationList.declarations) {
1697
+ if (!import_typescript9.default.isIdentifier(declaration.name)) continue;
1698
+ const varName = declaration.name.text;
1699
+ const initializer = declaration.initializer;
1700
+ if (!initializer || !import_typescript9.default.isCallExpression(initializer)) continue;
1701
+ const opInfo = extractChainedOperation(initializer);
1702
+ if (opInfo && (opInfo.operation === "selectFromEntity" || opInfo.operation === "selectFrom")) {
1703
+ queryBuilderVar = varName;
1704
+ if (opInfo.entityName) {
1705
+ entityName = opInfo.entityName;
1706
+ }
1707
+ }
1708
+ }
1709
+ }
1710
+ continue;
1711
+ }
1712
+ const expr = statement.expression;
1713
+ if (import_typescript9.default.isBinaryExpression(expr) && expr.operatorToken.kind === import_typescript9.default.SyntaxKind.EqualsToken) {
1714
+ if (!import_typescript9.default.isIdentifier(expr.left)) {
1715
+ continue;
1716
+ }
1717
+ const varName = expr.left.text;
1718
+ const rightSide = expr.right;
1719
+ if (import_typescript9.default.isCallExpression(rightSide)) {
1720
+ const opInfo = extractChainedOperation(rightSide);
1721
+ if (opInfo) {
1722
+ if (opInfo.operation === "selectFromEntity" || opInfo.operation === "selectFrom") {
1723
+ queryBuilderVar = varName;
1724
+ if (opInfo.entityName) {
1725
+ entityName = opInfo.entityName;
1726
+ }
1727
+ }
1728
+ if ((opInfo.operation === "select" || opInfo.operation === "include") && queryBuilderVar === varName) {
1729
+ if (opInfo.operation === "select") {
1730
+ for (const field of opInfo.fields || []) {
1731
+ selectedFields.add(field);
1732
+ }
1733
+ } else if (opInfo.operation === "include" && opInfo.includeArg) {
1734
+ const parsedIncludes = parseIncludeObjectLiteral(opInfo.includeArg);
1735
+ if (parsedIncludes) {
1736
+ for (const [relName, relSchema] of Object.entries(parsedIncludes)) {
1737
+ includes[relName] = relSchema;
1738
+ }
1739
+ }
1740
+ }
1741
+ }
1742
+ }
1743
+ }
1744
+ }
1745
+ }
1746
+ if (!hasReturn || !queryBuilderVar || !entityName) {
1747
+ return null;
1748
+ }
1749
+ return {
1750
+ entityName,
1751
+ selectedFields: Array.from(selectedFields),
1752
+ includes,
1753
+ isPaged
1754
+ };
1755
+ }
1756
+ function extractChainedOperation(callExpr) {
1757
+ if (import_typescript9.default.isIdentifier(callExpr.expression)) {
1758
+ const methodName2 = callExpr.expression.text;
1759
+ if (methodName2 === "selectFromEntity" || methodName2 === "selectFrom") {
1760
+ const entityArg = callExpr.arguments[0];
1761
+ let entityName = null;
1762
+ if (import_typescript9.default.isIdentifier(entityArg)) {
1763
+ entityName = entityArg.text;
1764
+ } else if (import_typescript9.default.isPropertyAccessExpression(entityArg)) {
1765
+ entityName = entityArg.name.text;
1766
+ }
1767
+ return {
1768
+ operation: methodName2 === "selectFromEntity" ? "selectFromEntity" : "selectFrom",
1769
+ fields: null,
1770
+ includeArg: null,
1771
+ entityName
1772
+ };
1773
+ }
1774
+ }
1775
+ if (!import_typescript9.default.isPropertyAccessExpression(callExpr.expression)) {
1776
+ return null;
1777
+ }
1778
+ const propAccess = callExpr.expression;
1779
+ const methodName = propAccess.name.text;
1780
+ if (methodName === "select") {
1781
+ const fields = [];
1782
+ for (const arg of callExpr.arguments) {
1783
+ if (import_typescript9.default.isStringLiteral(arg)) {
1784
+ fields.push(arg.text);
1785
+ }
1786
+ }
1787
+ return {
1788
+ operation: "select",
1789
+ fields,
1790
+ includeArg: null,
1791
+ entityName: null
1792
+ };
1793
+ }
1794
+ if (methodName === "include") {
1795
+ return {
1796
+ operation: "include",
1797
+ fields: null,
1798
+ includeArg: callExpr.arguments[0] || null,
1799
+ entityName: null
1800
+ };
1801
+ }
1802
+ return null;
1803
+ }
1804
+ function parseIncludeObjectLiteral(arg) {
1805
+ if (!import_typescript9.default.isObjectLiteralExpression(arg)) {
1806
+ return null;
1807
+ }
1808
+ const includes = {};
1809
+ for (const prop of arg.properties) {
1810
+ if (!import_typescript9.default.isPropertyAssignment(prop) || !import_typescript9.default.isIdentifier(prop.name)) {
1811
+ continue;
1812
+ }
1813
+ const relationName = prop.name.text;
1814
+ const value = prop.initializer;
1815
+ if (value.kind === import_typescript9.default.SyntaxKind.TrueKeyword) {
1816
+ includes[relationName] = true;
1817
+ } else if (import_typescript9.default.isObjectLiteralExpression(value)) {
1818
+ const nestedSchema = parseNestedInclude(value, 0);
1819
+ if (nestedSchema) {
1820
+ includes[relationName] = nestedSchema;
1821
+ }
1822
+ }
1823
+ }
1824
+ return includes;
1825
+ }
1826
+ function parseNestedInclude(obj, depth) {
1827
+ const selectedFields = [];
1828
+ const includes = {};
1829
+ for (const prop of obj.properties) {
1830
+ if (!import_typescript9.default.isPropertyAssignment(prop) || !import_typescript9.default.isIdentifier(prop.name)) {
1831
+ continue;
1832
+ }
1833
+ const propName = prop.name.text;
1834
+ const value = prop.initializer;
1835
+ if (propName === "select" && import_typescript9.default.isArrayLiteralExpression(value)) {
1836
+ for (const element of value.elements) {
1837
+ if (import_typescript9.default.isStringLiteral(element)) {
1838
+ selectedFields.push(element.text);
1839
+ }
1840
+ }
1841
+ } else if (propName === "include" && import_typescript9.default.isObjectLiteralExpression(value)) {
1842
+ const nestedIncludes = parseIncludeObjectLiteral(value);
1843
+ if (nestedIncludes) {
1844
+ for (const [relName, relSchema] of Object.entries(nestedIncludes)) {
1845
+ includes[relName] = relSchema;
1846
+ }
1847
+ }
1848
+ }
1849
+ }
1850
+ return {
1851
+ entityName: "",
1852
+ selectedFields,
1853
+ includes,
1854
+ isPaged: false
1855
+ };
1856
+ }
1857
+ function getMethodName(expression) {
1858
+ if (import_typescript9.default.isIdentifier(expression)) {
1859
+ return expression.text;
1860
+ }
1861
+ if (import_typescript9.default.isPropertyAccessExpression(expression)) {
1862
+ return expression.name.text;
1863
+ }
1864
+ return null;
1865
+ }
1866
+ function findReturnStatement(body) {
1867
+ let returnStatement = null;
1868
+ for (const statement of body.statements) {
1869
+ if (import_typescript9.default.isReturnStatement(statement)) {
1870
+ if (returnStatement !== null) {
1871
+ return null;
1872
+ }
1873
+ returnStatement = statement;
1874
+ }
1875
+ }
1876
+ return returnStatement;
1877
+ }
1878
+ function analyzeReturnExpression(expression) {
1879
+ if (!expression) {
1880
+ return null;
1881
+ }
1882
+ if (import_typescript9.default.isCallExpression(expression)) {
1883
+ return buildCallChain(expression, null);
1884
+ }
1885
+ return null;
1886
+ }
1887
+ function buildCallChain(node, parent) {
1888
+ if (import_typescript9.default.isCallExpression(node)) {
1889
+ const callNode = {
1890
+ expression: node.expression,
1891
+ methodName: getMethodName(node.expression),
1892
+ arguments: node.arguments,
1893
+ parent
1894
+ };
1895
+ if (import_typescript9.default.isPropertyAccessExpression(node.expression)) {
1896
+ return buildCallChain(node.expression.expression, callNode);
1897
+ }
1898
+ return callNode;
1899
+ }
1900
+ return parent;
1901
+ }
1902
+ function parseQueryBuilderChain(chain, checker, options) {
1903
+ if (!chain) {
1904
+ return null;
1905
+ }
1906
+ const rootNode = findSelectFromEntityCall(chain);
1907
+ if (!rootNode) {
1908
+ return null;
1909
+ }
1910
+ const entityName = extractEntityName(rootNode, checker);
1911
+ if (!entityName) {
1912
+ return null;
1913
+ }
1914
+ const selectedFields = /* @__PURE__ */ new Set();
1915
+ const includes = {};
1916
+ let isPaged = false;
1917
+ let currentNode = chain;
1918
+ while (currentNode) {
1919
+ const methodName = currentNode.methodName;
1920
+ if (methodName === "select") {
1921
+ for (const arg of currentNode.arguments) {
1922
+ if (import_typescript9.default.isStringLiteral(arg)) {
1923
+ selectedFields.add(arg.text);
1924
+ }
1925
+ }
1926
+ } else if (methodName === "include") {
1927
+ parseIncludeArgument(currentNode.arguments[0], includes, checker, options, 0);
1928
+ } else if (methodName === "executePaged") {
1929
+ isPaged = true;
1930
+ }
1931
+ currentNode = currentNode.parent;
1932
+ }
1933
+ return {
1934
+ entityName,
1935
+ selectedFields: Array.from(selectedFields),
1936
+ includes,
1937
+ isPaged
1938
+ };
1939
+ }
1940
+ function findSelectFromEntityCall(chain) {
1941
+ let currentNode = chain;
1942
+ let lastNode = null;
1943
+ while (currentNode) {
1944
+ if (currentNode.methodName === "selectFromEntity" || currentNode.methodName === "selectFrom") {
1945
+ return currentNode;
1946
+ }
1947
+ lastNode = currentNode;
1948
+ currentNode = currentNode.parent;
1949
+ }
1950
+ return lastNode;
1951
+ }
1952
+ function extractEntityName(callNode, checker) {
1953
+ if (callNode.arguments.length === 0) {
1954
+ return null;
1955
+ }
1956
+ const entityArg = callNode.arguments[0];
1957
+ if (import_typescript9.default.isIdentifier(entityArg)) {
1958
+ return entityArg.text;
1959
+ }
1960
+ if (import_typescript9.default.isPropertyAccessExpression(entityArg)) {
1961
+ return entityArg.name.text;
1962
+ }
1963
+ return null;
1964
+ }
1965
+ function parseIncludeArgument(arg, includes, checker, options, depth) {
1966
+ if (!arg) {
1967
+ return;
1968
+ }
1969
+ if (import_typescript9.default.isObjectLiteralExpression(arg)) {
1970
+ for (const prop of arg.properties) {
1971
+ if (!import_typescript9.default.isPropertyAssignment(prop) || !import_typescript9.default.isIdentifier(prop.name)) {
1972
+ continue;
1973
+ }
1974
+ const relationName = prop.name.text;
1975
+ const value = prop.initializer;
1976
+ if (value.kind === import_typescript9.default.SyntaxKind.TrueKeyword) {
1977
+ includes[relationName] = true;
1978
+ } else if (import_typescript9.default.isObjectLiteralExpression(value)) {
1979
+ const maxDepth = options.maxDepth ?? 5;
1980
+ if (depth < maxDepth) {
1981
+ const nestedSchema = parseNestedInclude(value, depth + 1);
1982
+ if (nestedSchema) {
1983
+ includes[relationName] = nestedSchema;
1984
+ }
1985
+ }
1986
+ }
1987
+ }
1988
+ }
1989
+ }
1990
+
1991
+ // src/compiler/schema/queryBuilderSchemaBuilder.ts
1992
+ var import_typescript10 = require("typescript");
1993
+ function wrapInPaginatedResult(schema) {
1994
+ return {
1995
+ type: "object",
1996
+ properties: {
1997
+ items: {
1998
+ type: "array",
1999
+ items: schema
2000
+ },
2001
+ page: { type: "integer" },
2002
+ pageSize: { type: "integer" },
2003
+ totalItems: { type: "integer" }
2004
+ },
2005
+ required: ["items", "page", "pageSize", "totalItems"]
2006
+ };
2007
+ }
2008
+
1582
2009
  // src/compiler/schema/openapi.ts
1583
2010
  var METAL_ORM_WRAPPER_NAMES2 = ["BelongsToReference", "HasOneReference", "HasManyCollection", "ManyToManyCollection"];
1584
2011
  function generateOpenAPI(controllers, checker, options = {}) {
@@ -1591,7 +2018,12 @@ function generateOpenAPI(controllers, checker, options = {}) {
1591
2018
  mode: "response"
1592
2019
  };
1593
2020
  const paths = {};
1594
- const { onProgress } = options;
2021
+ const { onProgress, onQueryBuilderProgress } = options;
2022
+ let totalOperations = 0;
2023
+ for (const controller of controllers) {
2024
+ totalOperations += controller.operations.length;
2025
+ }
2026
+ let currentOperation = 0;
1595
2027
  for (let i = 0; i < controllers.length; i++) {
1596
2028
  const controller = controllers[i];
1597
2029
  if (onProgress) {
@@ -1603,6 +2035,32 @@ function generateOpenAPI(controllers, checker, options = {}) {
1603
2035
  paths[fullPath] = {};
1604
2036
  }
1605
2037
  const method = operation.httpMethod.toLowerCase();
2038
+ const analysisResult = analyzeQueryBuilderWithDetails(
2039
+ operation.methodDeclaration,
2040
+ checker,
2041
+ {},
2042
+ {
2043
+ methodName: operation.operationId,
2044
+ httpMethod: operation.httpMethod,
2045
+ path: operation.path,
2046
+ operationId: operation.operationId
2047
+ }
2048
+ );
2049
+ if (onQueryBuilderProgress) {
2050
+ currentOperation++;
2051
+ onQueryBuilderProgress({
2052
+ controller: controller.className,
2053
+ operation: operation.operationId,
2054
+ method: operation.httpMethod,
2055
+ path: operation.path,
2056
+ queryBuilderDetected: analysisResult.detected,
2057
+ entityName: analysisResult.schema?.entityName,
2058
+ selectedFields: analysisResult.schema?.selectedFields,
2059
+ isPaged: analysisResult.schema?.isPaged,
2060
+ current: currentOperation,
2061
+ total: totalOperations
2062
+ });
2063
+ }
1606
2064
  paths[fullPath][method] = buildOperation(operation, ctx, controller.consumes);
1607
2065
  }
1608
2066
  }
@@ -1733,6 +2191,88 @@ function convertToOpenApiPath(basePath, path4) {
1733
2191
  }
1734
2192
  return fullPath;
1735
2193
  }
2194
+ function tryInferQueryBuilderSchema(operation, checker) {
2195
+ return analyzeQueryBuilderForSchema(operation.methodDeclaration, checker) ?? null;
2196
+ }
2197
+ function getEntityTypeFromReturnType(operation, checker) {
2198
+ const returnType = operation.returnType;
2199
+ const unwrapPromise2 = (type) => {
2200
+ const symbol2 = type.getSymbol();
2201
+ if (symbol2?.getName() === "Promise") {
2202
+ const typeArgs = type.typeArguments;
2203
+ if (typeArgs && typeArgs.length > 0) {
2204
+ return typeArgs[0];
2205
+ }
2206
+ }
2207
+ return type;
2208
+ };
2209
+ const innerType = unwrapPromise2(returnType);
2210
+ const symbol = innerType.getSymbol();
2211
+ if (symbol?.getName() === "PaginatedResult") {
2212
+ const typeArgs = innerType.typeArguments;
2213
+ if (typeArgs && typeArgs.length > 0) {
2214
+ return typeArgs[0];
2215
+ }
2216
+ }
2217
+ return null;
2218
+ }
2219
+ function filterSchemaByQueryBuilder(querySchema, operation, ctx) {
2220
+ const entityType = getEntityTypeFromReturnType(operation, ctx.checker);
2221
+ if (!entityType) {
2222
+ return {};
2223
+ }
2224
+ const entitySchema = typeToJsonSchema(entityType, ctx);
2225
+ let baseSchema = entitySchema;
2226
+ if (entitySchema.$ref && entitySchema.$ref.startsWith("#/components/schemas/")) {
2227
+ const schemaName = entitySchema.$ref.replace("#/components/schemas/", "");
2228
+ const componentSchema = ctx.components.get(schemaName);
2229
+ if (componentSchema) {
2230
+ baseSchema = componentSchema;
2231
+ }
2232
+ }
2233
+ if (!baseSchema.properties || Object.keys(baseSchema.properties).length === 0) {
2234
+ return {};
2235
+ }
2236
+ const filteredSchema = buildFilteredSchema(querySchema, baseSchema);
2237
+ if (querySchema.isPaged) {
2238
+ return wrapInPaginatedResult(filteredSchema);
2239
+ }
2240
+ return filteredSchema;
2241
+ }
2242
+ function buildFilteredSchema(querySchema, entitySchema) {
2243
+ const properties = {};
2244
+ const required = [];
2245
+ for (const field of querySchema.selectedFields) {
2246
+ if (entitySchema.properties?.[field]) {
2247
+ properties[field] = entitySchema.properties[field];
2248
+ if (entitySchema.required && entitySchema.required.includes(field)) {
2249
+ required.push(field);
2250
+ }
2251
+ }
2252
+ }
2253
+ for (const [relationName, includeSpec] of Object.entries(querySchema.includes)) {
2254
+ if (entitySchema.properties?.[relationName]) {
2255
+ properties[relationName] = {
2256
+ type: "object",
2257
+ properties: {
2258
+ id: { type: "integer" }
2259
+ },
2260
+ required: ["id"]
2261
+ };
2262
+ if (entitySchema.required && entitySchema.required.includes(relationName)) {
2263
+ required.push(relationName);
2264
+ }
2265
+ }
2266
+ }
2267
+ const schema = {
2268
+ type: "object",
2269
+ properties
2270
+ };
2271
+ if (required.length > 0) {
2272
+ schema.required = required;
2273
+ }
2274
+ return schema;
2275
+ }
1736
2276
  function buildOperation(operation, ctx, controllerConsumes) {
1737
2277
  const op = {
1738
2278
  operationId: operation.operationId,
@@ -1747,7 +2287,18 @@ function buildOperation(operation, ctx, controllerConsumes) {
1747
2287
  op.parameters = parameters;
1748
2288
  }
1749
2289
  const responseCtx = { ...ctx, mode: "response" };
1750
- const responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
2290
+ let responseSchema;
2291
+ const querySchema = tryInferQueryBuilderSchema(operation, ctx.checker);
2292
+ if (querySchema) {
2293
+ const entityType = getEntityTypeFromReturnType(operation, ctx.checker);
2294
+ if (entityType) {
2295
+ responseSchema = filterSchemaByQueryBuilder(querySchema, operation, responseCtx);
2296
+ } else {
2297
+ responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
2298
+ }
2299
+ } else {
2300
+ responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
2301
+ }
1751
2302
  const status = operation.httpMethod === "POST" ? 201 : 200;
1752
2303
  op.responses[status] = {
1753
2304
  description: status === 201 ? "Created" : "OK",
@@ -1789,12 +2340,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
1789
2340
  const declarations = typeSymbol.getDeclarations();
1790
2341
  if (!declarations || declarations.length === 0) return schema;
1791
2342
  const classDecl = declarations[0];
1792
- if (!import_typescript9.default.isClassDeclaration(classDecl)) return schema;
2343
+ if (!import_typescript11.default.isClassDeclaration(classDecl)) return schema;
1793
2344
  const result = { ...schema };
1794
2345
  const props = { ...result.properties };
1795
2346
  for (const member of classDecl.members) {
1796
- if (!import_typescript9.default.isPropertyDeclaration(member) || !member.name) continue;
1797
- const propName = import_typescript9.default.isIdentifier(member.name) ? member.name.text : null;
2347
+ if (!import_typescript11.default.isPropertyDeclaration(member) || !member.name) continue;
2348
+ const propName = import_typescript11.default.isIdentifier(member.name) ? member.name.text : null;
1798
2349
  if (!propName) continue;
1799
2350
  if (!props[propName]) continue;
1800
2351
  const frags = extractPropertySchemaFragments(ctx.checker, member);
@@ -1807,7 +2358,7 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
1807
2358
  }
1808
2359
 
1809
2360
  // src/compiler/manifest/emit.ts
1810
- var import_typescript10 = __toESM(require("typescript"), 1);
2361
+ var import_typescript12 = __toESM(require("typescript"), 1);
1811
2362
  function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
1812
2363
  const components = /* @__PURE__ */ new Map();
1813
2364
  const ctx = {
@@ -1829,7 +2380,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
1829
2380
  generator: {
1830
2381
  name: "adorn-api",
1831
2382
  version,
1832
- typescript: import_typescript10.default.version
2383
+ typescript: import_typescript12.default.version
1833
2384
  },
1834
2385
  schemas: {
1835
2386
  kind: "openapi-3.1",
@@ -2315,7 +2866,7 @@ async function isStale(params) {
2315
2866
  // src/compiler/cache/writeCache.ts
2316
2867
  var import_node_fs4 = __toESM(require("fs"), 1);
2317
2868
  var import_node_path4 = __toESM(require("path"), 1);
2318
- var import_typescript11 = __toESM(require("typescript"), 1);
2869
+ var import_typescript13 = __toESM(require("typescript"), 1);
2319
2870
  function statMtimeMs2(p) {
2320
2871
  return import_node_fs4.default.statSync(p).mtimeMs;
2321
2872
  }
@@ -2348,7 +2899,7 @@ function writeCache(params) {
2348
2899
  generator: {
2349
2900
  name: "adorn-api",
2350
2901
  version: params.adornVersion,
2351
- typescript: import_typescript11.default.version
2902
+ typescript: import_typescript13.default.version
2352
2903
  },
2353
2904
  project: {
2354
2905
  tsconfigPath: params.tsconfigAbs,
@@ -3101,7 +3652,7 @@ function getEdgesByRelation(graph, relation) {
3101
3652
  }
3102
3653
 
3103
3654
  // src/compiler/graph/builder.ts
3104
- var import_typescript12 = __toESM(require("typescript"), 1);
3655
+ var import_typescript14 = __toESM(require("typescript"), 1);
3105
3656
 
3106
3657
  // src/compiler/graph/schemaGraph.ts
3107
3658
  var SchemaGraph = class {
@@ -3353,7 +3904,7 @@ var SchemaGraph = class {
3353
3904
  };
3354
3905
 
3355
3906
  // src/cli.ts
3356
- var import_typescript13 = __toESM(require("typescript"), 1);
3907
+ var import_typescript15 = __toESM(require("typescript"), 1);
3357
3908
  var import_node_process2 = __toESM(require("process"), 1);
3358
3909
  var import_meta2 = {};
3359
3910
  var ADORN_VERSION = (() => {
@@ -3432,7 +3983,7 @@ function sanitizeForJson(obj) {
3432
3983
  return result;
3433
3984
  }
3434
3985
  function buildControllerGraph(controllers) {
3435
- const graph = createGraph(import_typescript13.default.version);
3986
+ const graph = createGraph(import_typescript15.default.version);
3436
3987
  const nodeMap = /* @__PURE__ */ new Map();
3437
3988
  for (const ctrl of controllers) {
3438
3989
  const nodeId = `Controller:${ctrl.className}`;
@@ -3524,6 +4075,7 @@ async function buildCommand(args) {
3524
4075
  const verbose = args.includes("--verbose");
3525
4076
  const quiet = args.includes("--quiet");
3526
4077
  const split = args.includes("--split");
4078
+ const showQueryBuilder = args.includes("--show-query-builder");
3527
4079
  const splitStrategyIndex = args.indexOf("--split-strategy");
3528
4080
  const splitStrategy = splitStrategyIndex !== -1 ? args[splitStrategyIndex + 1] : void 0;
3529
4081
  const splitThresholdIndex = args.indexOf("--split-threshold");
@@ -3543,7 +4095,7 @@ async function buildCommand(args) {
3543
4095
  outDir: outputDir,
3544
4096
  project: projectPath,
3545
4097
  adornVersion: ADORN_VERSION,
3546
- typescriptVersion: import_typescript13.default.version
4098
+ typescriptVersion: import_typescript15.default.version
3547
4099
  });
3548
4100
  if (!stale.stale) {
3549
4101
  progress.completePhase("staleness-check");
@@ -3584,6 +4136,12 @@ async function buildCommand(args) {
3584
4136
  }
3585
4137
  }
3586
4138
  progress.startPhase("openapi", "Generating OpenAPI schema");
4139
+ const queryBuilderStats = {
4140
+ totalOperations,
4141
+ detected: 0,
4142
+ fallback: 0,
4143
+ operations: []
4144
+ };
3587
4145
  const openapiSpinner = new Spinner("Processing schemas");
3588
4146
  if (!quiet) openapiSpinner.start();
3589
4147
  const openapi = generateOpenAPI(controllers, checker, {
@@ -3593,6 +4151,31 @@ async function buildCommand(args) {
3593
4151
  if (!quiet) {
3594
4152
  openapiSpinner.setStatus(`${message} (${current}/${total})`);
3595
4153
  }
4154
+ },
4155
+ onQueryBuilderProgress: (info) => {
4156
+ if (info.queryBuilderDetected) {
4157
+ queryBuilderStats.detected++;
4158
+ } else {
4159
+ queryBuilderStats.fallback++;
4160
+ }
4161
+ queryBuilderStats.operations.push({
4162
+ operationId: info.operation,
4163
+ method: info.method,
4164
+ path: info.path,
4165
+ detected: info.queryBuilderDetected,
4166
+ entityName: info.entityName,
4167
+ selectedFields: info.selectedFields,
4168
+ isPaged: info.isPaged
4169
+ });
4170
+ if (showQueryBuilder || verbose) {
4171
+ if (info.queryBuilderDetected) {
4172
+ const fieldsStr = info.selectedFields && info.selectedFields.length > 0 ? ` [select: ${info.selectedFields.join(",")}]` : "";
4173
+ const pagedStr = info.isPaged ? " (paged)" : "";
4174
+ progress.verboseLog(` \u2713 Query builder: ${info.method} ${info.path} \u2192 ${info.entityName}${fieldsStr}${pagedStr}`);
4175
+ } else {
4176
+ progress.verboseLog(` \u25CB No query builder: ${info.method} ${info.path} \u2192 using full entity schema`);
4177
+ }
4178
+ }
3596
4179
  }
3597
4180
  });
3598
4181
  if (!quiet) {
@@ -3651,6 +4234,17 @@ async function buildCommand(args) {
3651
4234
  }
3652
4235
  }
3653
4236
  progress.completePhase("openapi", `Generated ${schemaCount} schema(s)${splitEnabled ? " (split into groups)" : ""}`);
4237
+ if (showQueryBuilder && totalOperations > 0) {
4238
+ log("");
4239
+ log("Query Builder Analysis:");
4240
+ log(` Operations analyzed: ${totalOperations}`);
4241
+ log(` Patterns detected: ${queryBuilderStats.detected} (${Math.round(queryBuilderStats.detected / totalOperations * 100)}%)`);
4242
+ log(` Full schemas used: ${queryBuilderStats.fallback} (${Math.round(queryBuilderStats.fallback / totalOperations * 100)}%)`);
4243
+ if (queryBuilderStats.detected > 0) {
4244
+ const totalFields = queryBuilderStats.operations.filter((op) => op.detected && op.selectedFields).reduce((sum, op) => sum + (op.selectedFields?.length || 0), 0);
4245
+ log(` Fields selected: ${totalFields} total (avg ${Math.round(totalFields / queryBuilderStats.detected)} per query)`);
4246
+ }
4247
+ }
3654
4248
  progress.startPhase("manifest", "Generating manifest");
3655
4249
  const manifest = generateManifest(controllers, checker, ADORN_VERSION, validationMode);
3656
4250
  progress.completePhase("manifest");
@@ -3732,7 +4326,12 @@ async function buildCommand(args) {
3732
4326
  schemas: schemaCount,
3733
4327
  sourceFiles: projectSourceFiles.length,
3734
4328
  artifactsWritten: artifacts.map((a) => a.name),
3735
- splitEnabled
4329
+ splitEnabled,
4330
+ queryBuilder: {
4331
+ detected: queryBuilderStats.detected,
4332
+ fallback: queryBuilderStats.fallback,
4333
+ total: totalOperations
4334
+ }
3736
4335
  };
3737
4336
  progress.printSummary(stats);
3738
4337
  progress.printArtifacts(artifacts);
@@ -3760,30 +4359,32 @@ if (command === "build") {
3760
4359
  console.log(`
3761
4360
  adorn-api CLI v${ADORN_VERSION}
3762
4361
 
3763
- Commands:
3764
- build Generate OpenAPI and manifest from TypeScript source
3765
- clean Remove generated artifacts
4362
+ Commands:
4363
+ build Generate OpenAPI and manifest from TypeScript source
4364
+ clean Remove generated artifacts
3766
4365
 
3767
- Options:
3768
- -p <path> Path to tsconfig.json (default: ./tsconfig.json)
3769
- --output <dir> Output directory (default: .adorn)
3770
- --if-stale Only rebuild if artifacts are stale
3771
- --validation-mode <mode> Validation mode: none, ajv-runtime, precompiled (default: ajv-runtime)
3772
- --split Enable automatic schema splitting (default: disabled)
3773
- --split-strategy <mode> Override splitting strategy: controller, dependency, size, auto (default: auto)
3774
- --split-threshold <num> Schema count threshold for auto-split (default: 50)
3775
- --verbose Show detailed progress information
3776
- --quiet Suppress non-essential output
4366
+ Options:
4367
+ -p <path> Path to tsconfig.json (default: ./tsconfig.json)
4368
+ --output <dir> Output directory (default: .adorn)
4369
+ --if-stale Only rebuild if artifacts are stale
4370
+ --validation-mode <mode> Validation mode: none, ajv-runtime, precompiled (default: ajv-runtime)
4371
+ --split Enable automatic schema splitting (default: disabled)
4372
+ --split-strategy <mode> Override splitting strategy: controller, dependency, size, auto (default: auto)
4373
+ --split-threshold <num> Schema count threshold for auto-split (default: 50)
4374
+ --verbose Show detailed progress information
4375
+ --quiet Suppress non-essential output
4376
+ --show-query-builder Show query builder inspection details and statistics
3777
4377
 
3778
- Examples:
3779
- adorn-api build -p ./tsconfig.json --output .adorn
3780
- adorn-api build --if-stale
3781
- adorn-api build --validation-mode precompiled
3782
- adorn-api build --verbose
3783
- adorn-api build --split # Enable split mode
3784
- adorn-api build --split-strategy controller # Force controller-based splitting
3785
- adorn-api build --split-threshold 100 # Increase threshold to 100
3786
- adorn-api clean
3787
- `);
4378
+ Examples:
4379
+ adorn-api build -p ./tsconfig.json --output .adorn
4380
+ adorn-api build --if-stale
4381
+ adorn-api build --validation-mode precompiled
4382
+ adorn-api build --verbose
4383
+ adorn-api build --show-query-builder # Show query builder analysis details
4384
+ adorn-api build --split # Enable split mode
4385
+ adorn-api build --split-strategy controller # Force controller-based splitting
4386
+ adorn-api build --split-threshold 100 # Increase threshold to 100
4387
+ adorn-api clean
4388
+ `);
3788
4389
  }
3789
4390
  //# sourceMappingURL=cli.cjs.map