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.js CHANGED
@@ -306,7 +306,7 @@ function unwrapPromiseTypeNode(typeNode) {
306
306
  }
307
307
 
308
308
  // src/compiler/schema/openapi.ts
309
- import ts9 from "typescript";
309
+ import ts11 from "typescript";
310
310
 
311
311
  // src/compiler/schema/typeToJsonSchema.ts
312
312
  import ts7 from "typescript";
@@ -701,6 +701,48 @@ function getExplicitTypeNameFromNode3(typeNode) {
701
701
 
702
702
  // src/compiler/schema/objectHandler.ts
703
703
  import ts6 from "typescript";
704
+ function getTypeParameterName(type) {
705
+ if (type.flags & ts6.TypeFlags.TypeParameter) {
706
+ const typeParam = type;
707
+ return typeParam.symbol?.getName() ?? null;
708
+ }
709
+ return null;
710
+ }
711
+ function getTypeArguments(type) {
712
+ const typeRef = type;
713
+ const args = typeRef.typeArguments;
714
+ if (!args) return void 0;
715
+ return Array.from(args);
716
+ }
717
+ function createTypeParameterSubstitutions(type, typeNode, checker) {
718
+ const typeArgs = getTypeArguments(type);
719
+ if (!typeArgs || typeArgs.length === 0) {
720
+ return void 0;
721
+ }
722
+ if (!typeNode || !ts6.isTypeReferenceNode(typeNode)) {
723
+ return void 0;
724
+ }
725
+ const typeParams = typeNode.typeArguments;
726
+ if (!typeParams || typeParams.length !== typeArgs.length) {
727
+ return void 0;
728
+ }
729
+ const substitutions = /* @__PURE__ */ new Map();
730
+ for (let i = 0; i < typeParams.length; i++) {
731
+ const typeParamNode = typeParams[i];
732
+ const typeArg = typeArgs[i];
733
+ if (ts6.isIdentifier(typeParamNode)) {
734
+ substitutions.set(typeParamNode.text, typeArg);
735
+ }
736
+ }
737
+ return substitutions.size > 0 ? substitutions : void 0;
738
+ }
739
+ function resolveTypeParameter(type, substitutions, _checker) {
740
+ if (!substitutions) return null;
741
+ const paramName = getTypeParameterName(type);
742
+ if (!paramName) return null;
743
+ const resolved = substitutions.get(paramName);
744
+ return resolved ?? null;
745
+ }
704
746
  function handleObjectType(type, ctx, typeNode) {
705
747
  const { checker, components, typeStack } = ctx;
706
748
  const symbol = type.getSymbol();
@@ -720,7 +762,9 @@ function handleObjectType(type, ctx, typeNode) {
720
762
  }
721
763
  typeStack.add(type);
722
764
  }
723
- const schema = buildObjectSchema(type, ctx, typeNode);
765
+ const typeParamSubstitutions = createTypeParameterSubstitutions(type, typeNode, checker);
766
+ const buildCtx = typeParamSubstitutions ? { ...ctx, typeParameterSubstitutions: typeParamSubstitutions } : ctx;
767
+ const schema = buildObjectSchema(type, buildCtx, typeNode);
724
768
  if (typeName && typeName !== "__type") {
725
769
  typeStack.delete(type);
726
770
  const existing = components.get(typeName);
@@ -738,7 +782,7 @@ function handleObjectType(type, ctx, typeNode) {
738
782
  return schema;
739
783
  }
740
784
  function buildObjectSchema(type, ctx, _typeNode) {
741
- const { checker, mode } = ctx;
785
+ const { checker, mode, typeParameterSubstitutions } = ctx;
742
786
  const properties = {};
743
787
  const required = [];
744
788
  const props = checker.getPropertiesOfType(type);
@@ -747,10 +791,14 @@ function buildObjectSchema(type, ctx, _typeNode) {
747
791
  if (isIteratorOrSymbolProperty(propName)) {
748
792
  continue;
749
793
  }
750
- const propType = checker.getTypeOfSymbol(prop);
794
+ let propType = checker.getTypeOfSymbol(prop);
751
795
  if (isMethodLike(propType)) {
752
796
  continue;
753
797
  }
798
+ const resolvedType = resolveTypeParameter(propType, typeParameterSubstitutions, checker);
799
+ if (resolvedType) {
800
+ propType = resolvedType;
801
+ }
754
802
  const isOptional = !!(prop.flags & ts6.SymbolFlags.Optional);
755
803
  const isRelation = isMetalOrmWrapperType(propType, checker);
756
804
  const propCtx = { ...ctx, propertyName: propName };
@@ -770,7 +818,8 @@ function buildObjectSchema(type, ctx, _typeNode) {
770
818
  if (isRecordType(type, checker)) {
771
819
  const valueType = getRecordValueType(type, checker);
772
820
  if (valueType) {
773
- schema.additionalProperties = typeToJsonSchema(valueType, ctx);
821
+ const resolvedValueType = resolveTypeParameter(valueType, typeParameterSubstitutions, checker);
822
+ schema.additionalProperties = typeToJsonSchema(resolvedValueType ?? valueType, ctx);
774
823
  }
775
824
  }
776
825
  return schema;
@@ -1562,6 +1611,384 @@ function resolveAndCollectObjectProps(schema, components) {
1562
1611
  return { properties, required };
1563
1612
  }
1564
1613
 
1614
+ // src/compiler/schema/queryBuilderAnalyzer.ts
1615
+ import ts9 from "typescript";
1616
+ function analyzeQueryBuilderForSchema(methodDeclaration, checker, options = {}) {
1617
+ const body = methodDeclaration.body;
1618
+ if (!body) {
1619
+ return null;
1620
+ }
1621
+ const trackedSchema = analyzeWithVariableTracking(body, checker, options);
1622
+ if (trackedSchema) {
1623
+ return trackedSchema;
1624
+ }
1625
+ const returnStatement = findReturnStatement(body);
1626
+ if (!returnStatement) {
1627
+ return null;
1628
+ }
1629
+ const callChain = analyzeReturnExpression(returnStatement.expression);
1630
+ if (!callChain) {
1631
+ return null;
1632
+ }
1633
+ return parseQueryBuilderChain(callChain, checker, options);
1634
+ }
1635
+ function analyzeQueryBuilderWithDetails(methodDeclaration, checker, options = {}, operationInfo) {
1636
+ const schema = analyzeQueryBuilderForSchema(methodDeclaration, checker, options);
1637
+ return {
1638
+ detected: schema !== null,
1639
+ schema,
1640
+ ...operationInfo
1641
+ };
1642
+ }
1643
+ function analyzeWithVariableTracking(body, checker, options) {
1644
+ let queryBuilderVar = null;
1645
+ let entityName = null;
1646
+ const selectedFields = /* @__PURE__ */ new Set();
1647
+ const includes = {};
1648
+ let isPaged = false;
1649
+ let hasReturn = false;
1650
+ for (const statement of body.statements) {
1651
+ if (ts9.isReturnStatement(statement)) {
1652
+ hasReturn = true;
1653
+ const returnExpr = statement.expression;
1654
+ if (returnExpr && ts9.isCallExpression(returnExpr)) {
1655
+ const callExpr = returnExpr;
1656
+ if (ts9.isIdentifier(callExpr.expression) && queryBuilderVar) {
1657
+ const varName = callExpr.expression.text;
1658
+ if (varName === queryBuilderVar) {
1659
+ const methodName = callExpr.expression.text;
1660
+ if (methodName === "executePaged") {
1661
+ isPaged = true;
1662
+ }
1663
+ }
1664
+ }
1665
+ if (ts9.isPropertyAccessExpression(callExpr.expression) && queryBuilderVar) {
1666
+ const propAccess = callExpr.expression;
1667
+ if (ts9.isIdentifier(propAccess.expression) && propAccess.expression.text === queryBuilderVar) {
1668
+ const methodName = propAccess.name.text;
1669
+ if (methodName === "executePaged") {
1670
+ isPaged = true;
1671
+ }
1672
+ }
1673
+ }
1674
+ }
1675
+ continue;
1676
+ }
1677
+ if (!ts9.isExpressionStatement(statement)) {
1678
+ if (ts9.isVariableStatement(statement)) {
1679
+ for (const declaration of statement.declarationList.declarations) {
1680
+ if (!ts9.isIdentifier(declaration.name)) continue;
1681
+ const varName = declaration.name.text;
1682
+ const initializer = declaration.initializer;
1683
+ if (!initializer || !ts9.isCallExpression(initializer)) continue;
1684
+ const opInfo = extractChainedOperation(initializer);
1685
+ if (opInfo && (opInfo.operation === "selectFromEntity" || opInfo.operation === "selectFrom")) {
1686
+ queryBuilderVar = varName;
1687
+ if (opInfo.entityName) {
1688
+ entityName = opInfo.entityName;
1689
+ }
1690
+ }
1691
+ }
1692
+ }
1693
+ continue;
1694
+ }
1695
+ const expr = statement.expression;
1696
+ if (ts9.isBinaryExpression(expr) && expr.operatorToken.kind === ts9.SyntaxKind.EqualsToken) {
1697
+ if (!ts9.isIdentifier(expr.left)) {
1698
+ continue;
1699
+ }
1700
+ const varName = expr.left.text;
1701
+ const rightSide = expr.right;
1702
+ if (ts9.isCallExpression(rightSide)) {
1703
+ const opInfo = extractChainedOperation(rightSide);
1704
+ if (opInfo) {
1705
+ if (opInfo.operation === "selectFromEntity" || opInfo.operation === "selectFrom") {
1706
+ queryBuilderVar = varName;
1707
+ if (opInfo.entityName) {
1708
+ entityName = opInfo.entityName;
1709
+ }
1710
+ }
1711
+ if ((opInfo.operation === "select" || opInfo.operation === "include") && queryBuilderVar === varName) {
1712
+ if (opInfo.operation === "select") {
1713
+ for (const field of opInfo.fields || []) {
1714
+ selectedFields.add(field);
1715
+ }
1716
+ } else if (opInfo.operation === "include" && opInfo.includeArg) {
1717
+ const parsedIncludes = parseIncludeObjectLiteral(opInfo.includeArg);
1718
+ if (parsedIncludes) {
1719
+ for (const [relName, relSchema] of Object.entries(parsedIncludes)) {
1720
+ includes[relName] = relSchema;
1721
+ }
1722
+ }
1723
+ }
1724
+ }
1725
+ }
1726
+ }
1727
+ }
1728
+ }
1729
+ if (!hasReturn || !queryBuilderVar || !entityName) {
1730
+ return null;
1731
+ }
1732
+ return {
1733
+ entityName,
1734
+ selectedFields: Array.from(selectedFields),
1735
+ includes,
1736
+ isPaged
1737
+ };
1738
+ }
1739
+ function extractChainedOperation(callExpr) {
1740
+ if (ts9.isIdentifier(callExpr.expression)) {
1741
+ const methodName2 = callExpr.expression.text;
1742
+ if (methodName2 === "selectFromEntity" || methodName2 === "selectFrom") {
1743
+ const entityArg = callExpr.arguments[0];
1744
+ let entityName = null;
1745
+ if (ts9.isIdentifier(entityArg)) {
1746
+ entityName = entityArg.text;
1747
+ } else if (ts9.isPropertyAccessExpression(entityArg)) {
1748
+ entityName = entityArg.name.text;
1749
+ }
1750
+ return {
1751
+ operation: methodName2 === "selectFromEntity" ? "selectFromEntity" : "selectFrom",
1752
+ fields: null,
1753
+ includeArg: null,
1754
+ entityName
1755
+ };
1756
+ }
1757
+ }
1758
+ if (!ts9.isPropertyAccessExpression(callExpr.expression)) {
1759
+ return null;
1760
+ }
1761
+ const propAccess = callExpr.expression;
1762
+ const methodName = propAccess.name.text;
1763
+ if (methodName === "select") {
1764
+ const fields = [];
1765
+ for (const arg of callExpr.arguments) {
1766
+ if (ts9.isStringLiteral(arg)) {
1767
+ fields.push(arg.text);
1768
+ }
1769
+ }
1770
+ return {
1771
+ operation: "select",
1772
+ fields,
1773
+ includeArg: null,
1774
+ entityName: null
1775
+ };
1776
+ }
1777
+ if (methodName === "include") {
1778
+ return {
1779
+ operation: "include",
1780
+ fields: null,
1781
+ includeArg: callExpr.arguments[0] || null,
1782
+ entityName: null
1783
+ };
1784
+ }
1785
+ return null;
1786
+ }
1787
+ function parseIncludeObjectLiteral(arg) {
1788
+ if (!ts9.isObjectLiteralExpression(arg)) {
1789
+ return null;
1790
+ }
1791
+ const includes = {};
1792
+ for (const prop of arg.properties) {
1793
+ if (!ts9.isPropertyAssignment(prop) || !ts9.isIdentifier(prop.name)) {
1794
+ continue;
1795
+ }
1796
+ const relationName = prop.name.text;
1797
+ const value = prop.initializer;
1798
+ if (value.kind === ts9.SyntaxKind.TrueKeyword) {
1799
+ includes[relationName] = true;
1800
+ } else if (ts9.isObjectLiteralExpression(value)) {
1801
+ const nestedSchema = parseNestedInclude(value, 0);
1802
+ if (nestedSchema) {
1803
+ includes[relationName] = nestedSchema;
1804
+ }
1805
+ }
1806
+ }
1807
+ return includes;
1808
+ }
1809
+ function parseNestedInclude(obj, depth) {
1810
+ const selectedFields = [];
1811
+ const includes = {};
1812
+ for (const prop of obj.properties) {
1813
+ if (!ts9.isPropertyAssignment(prop) || !ts9.isIdentifier(prop.name)) {
1814
+ continue;
1815
+ }
1816
+ const propName = prop.name.text;
1817
+ const value = prop.initializer;
1818
+ if (propName === "select" && ts9.isArrayLiteralExpression(value)) {
1819
+ for (const element of value.elements) {
1820
+ if (ts9.isStringLiteral(element)) {
1821
+ selectedFields.push(element.text);
1822
+ }
1823
+ }
1824
+ } else if (propName === "include" && ts9.isObjectLiteralExpression(value)) {
1825
+ const nestedIncludes = parseIncludeObjectLiteral(value);
1826
+ if (nestedIncludes) {
1827
+ for (const [relName, relSchema] of Object.entries(nestedIncludes)) {
1828
+ includes[relName] = relSchema;
1829
+ }
1830
+ }
1831
+ }
1832
+ }
1833
+ return {
1834
+ entityName: "",
1835
+ selectedFields,
1836
+ includes,
1837
+ isPaged: false
1838
+ };
1839
+ }
1840
+ function getMethodName(expression) {
1841
+ if (ts9.isIdentifier(expression)) {
1842
+ return expression.text;
1843
+ }
1844
+ if (ts9.isPropertyAccessExpression(expression)) {
1845
+ return expression.name.text;
1846
+ }
1847
+ return null;
1848
+ }
1849
+ function findReturnStatement(body) {
1850
+ let returnStatement = null;
1851
+ for (const statement of body.statements) {
1852
+ if (ts9.isReturnStatement(statement)) {
1853
+ if (returnStatement !== null) {
1854
+ return null;
1855
+ }
1856
+ returnStatement = statement;
1857
+ }
1858
+ }
1859
+ return returnStatement;
1860
+ }
1861
+ function analyzeReturnExpression(expression) {
1862
+ if (!expression) {
1863
+ return null;
1864
+ }
1865
+ if (ts9.isCallExpression(expression)) {
1866
+ return buildCallChain(expression, null);
1867
+ }
1868
+ return null;
1869
+ }
1870
+ function buildCallChain(node, parent) {
1871
+ if (ts9.isCallExpression(node)) {
1872
+ const callNode = {
1873
+ expression: node.expression,
1874
+ methodName: getMethodName(node.expression),
1875
+ arguments: node.arguments,
1876
+ parent
1877
+ };
1878
+ if (ts9.isPropertyAccessExpression(node.expression)) {
1879
+ return buildCallChain(node.expression.expression, callNode);
1880
+ }
1881
+ return callNode;
1882
+ }
1883
+ return parent;
1884
+ }
1885
+ function parseQueryBuilderChain(chain, checker, options) {
1886
+ if (!chain) {
1887
+ return null;
1888
+ }
1889
+ const rootNode = findSelectFromEntityCall(chain);
1890
+ if (!rootNode) {
1891
+ return null;
1892
+ }
1893
+ const entityName = extractEntityName(rootNode, checker);
1894
+ if (!entityName) {
1895
+ return null;
1896
+ }
1897
+ const selectedFields = /* @__PURE__ */ new Set();
1898
+ const includes = {};
1899
+ let isPaged = false;
1900
+ let currentNode = chain;
1901
+ while (currentNode) {
1902
+ const methodName = currentNode.methodName;
1903
+ if (methodName === "select") {
1904
+ for (const arg of currentNode.arguments) {
1905
+ if (ts9.isStringLiteral(arg)) {
1906
+ selectedFields.add(arg.text);
1907
+ }
1908
+ }
1909
+ } else if (methodName === "include") {
1910
+ parseIncludeArgument(currentNode.arguments[0], includes, checker, options, 0);
1911
+ } else if (methodName === "executePaged") {
1912
+ isPaged = true;
1913
+ }
1914
+ currentNode = currentNode.parent;
1915
+ }
1916
+ return {
1917
+ entityName,
1918
+ selectedFields: Array.from(selectedFields),
1919
+ includes,
1920
+ isPaged
1921
+ };
1922
+ }
1923
+ function findSelectFromEntityCall(chain) {
1924
+ let currentNode = chain;
1925
+ let lastNode = null;
1926
+ while (currentNode) {
1927
+ if (currentNode.methodName === "selectFromEntity" || currentNode.methodName === "selectFrom") {
1928
+ return currentNode;
1929
+ }
1930
+ lastNode = currentNode;
1931
+ currentNode = currentNode.parent;
1932
+ }
1933
+ return lastNode;
1934
+ }
1935
+ function extractEntityName(callNode, checker) {
1936
+ if (callNode.arguments.length === 0) {
1937
+ return null;
1938
+ }
1939
+ const entityArg = callNode.arguments[0];
1940
+ if (ts9.isIdentifier(entityArg)) {
1941
+ return entityArg.text;
1942
+ }
1943
+ if (ts9.isPropertyAccessExpression(entityArg)) {
1944
+ return entityArg.name.text;
1945
+ }
1946
+ return null;
1947
+ }
1948
+ function parseIncludeArgument(arg, includes, checker, options, depth) {
1949
+ if (!arg) {
1950
+ return;
1951
+ }
1952
+ if (ts9.isObjectLiteralExpression(arg)) {
1953
+ for (const prop of arg.properties) {
1954
+ if (!ts9.isPropertyAssignment(prop) || !ts9.isIdentifier(prop.name)) {
1955
+ continue;
1956
+ }
1957
+ const relationName = prop.name.text;
1958
+ const value = prop.initializer;
1959
+ if (value.kind === ts9.SyntaxKind.TrueKeyword) {
1960
+ includes[relationName] = true;
1961
+ } else if (ts9.isObjectLiteralExpression(value)) {
1962
+ const maxDepth = options.maxDepth ?? 5;
1963
+ if (depth < maxDepth) {
1964
+ const nestedSchema = parseNestedInclude(value, depth + 1);
1965
+ if (nestedSchema) {
1966
+ includes[relationName] = nestedSchema;
1967
+ }
1968
+ }
1969
+ }
1970
+ }
1971
+ }
1972
+ }
1973
+
1974
+ // src/compiler/schema/queryBuilderSchemaBuilder.ts
1975
+ import "typescript";
1976
+ function wrapInPaginatedResult(schema) {
1977
+ return {
1978
+ type: "object",
1979
+ properties: {
1980
+ items: {
1981
+ type: "array",
1982
+ items: schema
1983
+ },
1984
+ page: { type: "integer" },
1985
+ pageSize: { type: "integer" },
1986
+ totalItems: { type: "integer" }
1987
+ },
1988
+ required: ["items", "page", "pageSize", "totalItems"]
1989
+ };
1990
+ }
1991
+
1565
1992
  // src/compiler/schema/openapi.ts
1566
1993
  var METAL_ORM_WRAPPER_NAMES2 = ["BelongsToReference", "HasOneReference", "HasManyCollection", "ManyToManyCollection"];
1567
1994
  function generateOpenAPI(controllers, checker, options = {}) {
@@ -1574,7 +2001,12 @@ function generateOpenAPI(controllers, checker, options = {}) {
1574
2001
  mode: "response"
1575
2002
  };
1576
2003
  const paths = {};
1577
- const { onProgress } = options;
2004
+ const { onProgress, onQueryBuilderProgress } = options;
2005
+ let totalOperations = 0;
2006
+ for (const controller of controllers) {
2007
+ totalOperations += controller.operations.length;
2008
+ }
2009
+ let currentOperation = 0;
1578
2010
  for (let i = 0; i < controllers.length; i++) {
1579
2011
  const controller = controllers[i];
1580
2012
  if (onProgress) {
@@ -1586,6 +2018,32 @@ function generateOpenAPI(controllers, checker, options = {}) {
1586
2018
  paths[fullPath] = {};
1587
2019
  }
1588
2020
  const method = operation.httpMethod.toLowerCase();
2021
+ const analysisResult = analyzeQueryBuilderWithDetails(
2022
+ operation.methodDeclaration,
2023
+ checker,
2024
+ {},
2025
+ {
2026
+ methodName: operation.operationId,
2027
+ httpMethod: operation.httpMethod,
2028
+ path: operation.path,
2029
+ operationId: operation.operationId
2030
+ }
2031
+ );
2032
+ if (onQueryBuilderProgress) {
2033
+ currentOperation++;
2034
+ onQueryBuilderProgress({
2035
+ controller: controller.className,
2036
+ operation: operation.operationId,
2037
+ method: operation.httpMethod,
2038
+ path: operation.path,
2039
+ queryBuilderDetected: analysisResult.detected,
2040
+ entityName: analysisResult.schema?.entityName,
2041
+ selectedFields: analysisResult.schema?.selectedFields,
2042
+ isPaged: analysisResult.schema?.isPaged,
2043
+ current: currentOperation,
2044
+ total: totalOperations
2045
+ });
2046
+ }
1589
2047
  paths[fullPath][method] = buildOperation(operation, ctx, controller.consumes);
1590
2048
  }
1591
2049
  }
@@ -1716,6 +2174,88 @@ function convertToOpenApiPath(basePath, path4) {
1716
2174
  }
1717
2175
  return fullPath;
1718
2176
  }
2177
+ function tryInferQueryBuilderSchema(operation, checker) {
2178
+ return analyzeQueryBuilderForSchema(operation.methodDeclaration, checker) ?? null;
2179
+ }
2180
+ function getEntityTypeFromReturnType(operation, checker) {
2181
+ const returnType = operation.returnType;
2182
+ const unwrapPromise2 = (type) => {
2183
+ const symbol2 = type.getSymbol();
2184
+ if (symbol2?.getName() === "Promise") {
2185
+ const typeArgs = type.typeArguments;
2186
+ if (typeArgs && typeArgs.length > 0) {
2187
+ return typeArgs[0];
2188
+ }
2189
+ }
2190
+ return type;
2191
+ };
2192
+ const innerType = unwrapPromise2(returnType);
2193
+ const symbol = innerType.getSymbol();
2194
+ if (symbol?.getName() === "PaginatedResult") {
2195
+ const typeArgs = innerType.typeArguments;
2196
+ if (typeArgs && typeArgs.length > 0) {
2197
+ return typeArgs[0];
2198
+ }
2199
+ }
2200
+ return null;
2201
+ }
2202
+ function filterSchemaByQueryBuilder(querySchema, operation, ctx) {
2203
+ const entityType = getEntityTypeFromReturnType(operation, ctx.checker);
2204
+ if (!entityType) {
2205
+ return {};
2206
+ }
2207
+ const entitySchema = typeToJsonSchema(entityType, ctx);
2208
+ let baseSchema = entitySchema;
2209
+ if (entitySchema.$ref && entitySchema.$ref.startsWith("#/components/schemas/")) {
2210
+ const schemaName = entitySchema.$ref.replace("#/components/schemas/", "");
2211
+ const componentSchema = ctx.components.get(schemaName);
2212
+ if (componentSchema) {
2213
+ baseSchema = componentSchema;
2214
+ }
2215
+ }
2216
+ if (!baseSchema.properties || Object.keys(baseSchema.properties).length === 0) {
2217
+ return {};
2218
+ }
2219
+ const filteredSchema = buildFilteredSchema(querySchema, baseSchema);
2220
+ if (querySchema.isPaged) {
2221
+ return wrapInPaginatedResult(filteredSchema);
2222
+ }
2223
+ return filteredSchema;
2224
+ }
2225
+ function buildFilteredSchema(querySchema, entitySchema) {
2226
+ const properties = {};
2227
+ const required = [];
2228
+ for (const field of querySchema.selectedFields) {
2229
+ if (entitySchema.properties?.[field]) {
2230
+ properties[field] = entitySchema.properties[field];
2231
+ if (entitySchema.required && entitySchema.required.includes(field)) {
2232
+ required.push(field);
2233
+ }
2234
+ }
2235
+ }
2236
+ for (const [relationName, includeSpec] of Object.entries(querySchema.includes)) {
2237
+ if (entitySchema.properties?.[relationName]) {
2238
+ properties[relationName] = {
2239
+ type: "object",
2240
+ properties: {
2241
+ id: { type: "integer" }
2242
+ },
2243
+ required: ["id"]
2244
+ };
2245
+ if (entitySchema.required && entitySchema.required.includes(relationName)) {
2246
+ required.push(relationName);
2247
+ }
2248
+ }
2249
+ }
2250
+ const schema = {
2251
+ type: "object",
2252
+ properties
2253
+ };
2254
+ if (required.length > 0) {
2255
+ schema.required = required;
2256
+ }
2257
+ return schema;
2258
+ }
1719
2259
  function buildOperation(operation, ctx, controllerConsumes) {
1720
2260
  const op = {
1721
2261
  operationId: operation.operationId,
@@ -1730,7 +2270,18 @@ function buildOperation(operation, ctx, controllerConsumes) {
1730
2270
  op.parameters = parameters;
1731
2271
  }
1732
2272
  const responseCtx = { ...ctx, mode: "response" };
1733
- const responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
2273
+ let responseSchema;
2274
+ const querySchema = tryInferQueryBuilderSchema(operation, ctx.checker);
2275
+ if (querySchema) {
2276
+ const entityType = getEntityTypeFromReturnType(operation, ctx.checker);
2277
+ if (entityType) {
2278
+ responseSchema = filterSchemaByQueryBuilder(querySchema, operation, responseCtx);
2279
+ } else {
2280
+ responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
2281
+ }
2282
+ } else {
2283
+ responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
2284
+ }
1734
2285
  const status = operation.httpMethod === "POST" ? 201 : 200;
1735
2286
  op.responses[status] = {
1736
2287
  description: status === 201 ? "Created" : "OK",
@@ -1772,12 +2323,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
1772
2323
  const declarations = typeSymbol.getDeclarations();
1773
2324
  if (!declarations || declarations.length === 0) return schema;
1774
2325
  const classDecl = declarations[0];
1775
- if (!ts9.isClassDeclaration(classDecl)) return schema;
2326
+ if (!ts11.isClassDeclaration(classDecl)) return schema;
1776
2327
  const result = { ...schema };
1777
2328
  const props = { ...result.properties };
1778
2329
  for (const member of classDecl.members) {
1779
- if (!ts9.isPropertyDeclaration(member) || !member.name) continue;
1780
- const propName = ts9.isIdentifier(member.name) ? member.name.text : null;
2330
+ if (!ts11.isPropertyDeclaration(member) || !member.name) continue;
2331
+ const propName = ts11.isIdentifier(member.name) ? member.name.text : null;
1781
2332
  if (!propName) continue;
1782
2333
  if (!props[propName]) continue;
1783
2334
  const frags = extractPropertySchemaFragments(ctx.checker, member);
@@ -1790,7 +2341,7 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
1790
2341
  }
1791
2342
 
1792
2343
  // src/compiler/manifest/emit.ts
1793
- import ts10 from "typescript";
2344
+ import ts12 from "typescript";
1794
2345
  function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
1795
2346
  const components = /* @__PURE__ */ new Map();
1796
2347
  const ctx = {
@@ -1812,7 +2363,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
1812
2363
  generator: {
1813
2364
  name: "adorn-api",
1814
2365
  version,
1815
- typescript: ts10.version
2366
+ typescript: ts12.version
1816
2367
  },
1817
2368
  schemas: {
1818
2369
  kind: "openapi-3.1",
@@ -2297,7 +2848,7 @@ async function isStale(params) {
2297
2848
  // src/compiler/cache/writeCache.ts
2298
2849
  import fs3 from "fs";
2299
2850
  import path3 from "path";
2300
- import ts11 from "typescript";
2851
+ import ts13 from "typescript";
2301
2852
  function statMtimeMs2(p) {
2302
2853
  return fs3.statSync(p).mtimeMs;
2303
2854
  }
@@ -2330,7 +2881,7 @@ function writeCache(params) {
2330
2881
  generator: {
2331
2882
  name: "adorn-api",
2332
2883
  version: params.adornVersion,
2333
- typescript: ts11.version
2884
+ typescript: ts13.version
2334
2885
  },
2335
2886
  project: {
2336
2887
  tsconfigPath: params.tsconfigAbs,
@@ -3083,7 +3634,7 @@ function getEdgesByRelation(graph, relation) {
3083
3634
  }
3084
3635
 
3085
3636
  // src/compiler/graph/builder.ts
3086
- import ts12 from "typescript";
3637
+ import ts14 from "typescript";
3087
3638
 
3088
3639
  // src/compiler/graph/schemaGraph.ts
3089
3640
  var SchemaGraph = class {
@@ -3335,7 +3886,7 @@ var SchemaGraph = class {
3335
3886
  };
3336
3887
 
3337
3888
  // src/cli.ts
3338
- import ts13 from "typescript";
3889
+ import ts15 from "typescript";
3339
3890
  import process2 from "process";
3340
3891
  var ADORN_VERSION = (() => {
3341
3892
  const tryReadPackageJson = (filePath) => {
@@ -3413,7 +3964,7 @@ function sanitizeForJson(obj) {
3413
3964
  return result;
3414
3965
  }
3415
3966
  function buildControllerGraph(controllers) {
3416
- const graph = createGraph(ts13.version);
3967
+ const graph = createGraph(ts15.version);
3417
3968
  const nodeMap = /* @__PURE__ */ new Map();
3418
3969
  for (const ctrl of controllers) {
3419
3970
  const nodeId = `Controller:${ctrl.className}`;
@@ -3505,6 +4056,7 @@ async function buildCommand(args) {
3505
4056
  const verbose = args.includes("--verbose");
3506
4057
  const quiet = args.includes("--quiet");
3507
4058
  const split = args.includes("--split");
4059
+ const showQueryBuilder = args.includes("--show-query-builder");
3508
4060
  const splitStrategyIndex = args.indexOf("--split-strategy");
3509
4061
  const splitStrategy = splitStrategyIndex !== -1 ? args[splitStrategyIndex + 1] : void 0;
3510
4062
  const splitThresholdIndex = args.indexOf("--split-threshold");
@@ -3524,7 +4076,7 @@ async function buildCommand(args) {
3524
4076
  outDir: outputDir,
3525
4077
  project: projectPath,
3526
4078
  adornVersion: ADORN_VERSION,
3527
- typescriptVersion: ts13.version
4079
+ typescriptVersion: ts15.version
3528
4080
  });
3529
4081
  if (!stale.stale) {
3530
4082
  progress.completePhase("staleness-check");
@@ -3565,6 +4117,12 @@ async function buildCommand(args) {
3565
4117
  }
3566
4118
  }
3567
4119
  progress.startPhase("openapi", "Generating OpenAPI schema");
4120
+ const queryBuilderStats = {
4121
+ totalOperations,
4122
+ detected: 0,
4123
+ fallback: 0,
4124
+ operations: []
4125
+ };
3568
4126
  const openapiSpinner = new Spinner("Processing schemas");
3569
4127
  if (!quiet) openapiSpinner.start();
3570
4128
  const openapi = generateOpenAPI(controllers, checker, {
@@ -3574,6 +4132,31 @@ async function buildCommand(args) {
3574
4132
  if (!quiet) {
3575
4133
  openapiSpinner.setStatus(`${message} (${current}/${total})`);
3576
4134
  }
4135
+ },
4136
+ onQueryBuilderProgress: (info) => {
4137
+ if (info.queryBuilderDetected) {
4138
+ queryBuilderStats.detected++;
4139
+ } else {
4140
+ queryBuilderStats.fallback++;
4141
+ }
4142
+ queryBuilderStats.operations.push({
4143
+ operationId: info.operation,
4144
+ method: info.method,
4145
+ path: info.path,
4146
+ detected: info.queryBuilderDetected,
4147
+ entityName: info.entityName,
4148
+ selectedFields: info.selectedFields,
4149
+ isPaged: info.isPaged
4150
+ });
4151
+ if (showQueryBuilder || verbose) {
4152
+ if (info.queryBuilderDetected) {
4153
+ const fieldsStr = info.selectedFields && info.selectedFields.length > 0 ? ` [select: ${info.selectedFields.join(",")}]` : "";
4154
+ const pagedStr = info.isPaged ? " (paged)" : "";
4155
+ progress.verboseLog(` \u2713 Query builder: ${info.method} ${info.path} \u2192 ${info.entityName}${fieldsStr}${pagedStr}`);
4156
+ } else {
4157
+ progress.verboseLog(` \u25CB No query builder: ${info.method} ${info.path} \u2192 using full entity schema`);
4158
+ }
4159
+ }
3577
4160
  }
3578
4161
  });
3579
4162
  if (!quiet) {
@@ -3632,6 +4215,17 @@ async function buildCommand(args) {
3632
4215
  }
3633
4216
  }
3634
4217
  progress.completePhase("openapi", `Generated ${schemaCount} schema(s)${splitEnabled ? " (split into groups)" : ""}`);
4218
+ if (showQueryBuilder && totalOperations > 0) {
4219
+ log("");
4220
+ log("Query Builder Analysis:");
4221
+ log(` Operations analyzed: ${totalOperations}`);
4222
+ log(` Patterns detected: ${queryBuilderStats.detected} (${Math.round(queryBuilderStats.detected / totalOperations * 100)}%)`);
4223
+ log(` Full schemas used: ${queryBuilderStats.fallback} (${Math.round(queryBuilderStats.fallback / totalOperations * 100)}%)`);
4224
+ if (queryBuilderStats.detected > 0) {
4225
+ const totalFields = queryBuilderStats.operations.filter((op) => op.detected && op.selectedFields).reduce((sum, op) => sum + (op.selectedFields?.length || 0), 0);
4226
+ log(` Fields selected: ${totalFields} total (avg ${Math.round(totalFields / queryBuilderStats.detected)} per query)`);
4227
+ }
4228
+ }
3635
4229
  progress.startPhase("manifest", "Generating manifest");
3636
4230
  const manifest = generateManifest(controllers, checker, ADORN_VERSION, validationMode);
3637
4231
  progress.completePhase("manifest");
@@ -3713,7 +4307,12 @@ async function buildCommand(args) {
3713
4307
  schemas: schemaCount,
3714
4308
  sourceFiles: projectSourceFiles.length,
3715
4309
  artifactsWritten: artifacts.map((a) => a.name),
3716
- splitEnabled
4310
+ splitEnabled,
4311
+ queryBuilder: {
4312
+ detected: queryBuilderStats.detected,
4313
+ fallback: queryBuilderStats.fallback,
4314
+ total: totalOperations
4315
+ }
3717
4316
  };
3718
4317
  progress.printSummary(stats);
3719
4318
  progress.printArtifacts(artifacts);
@@ -3741,30 +4340,32 @@ if (command === "build") {
3741
4340
  console.log(`
3742
4341
  adorn-api CLI v${ADORN_VERSION}
3743
4342
 
3744
- Commands:
3745
- build Generate OpenAPI and manifest from TypeScript source
3746
- clean Remove generated artifacts
4343
+ Commands:
4344
+ build Generate OpenAPI and manifest from TypeScript source
4345
+ clean Remove generated artifacts
3747
4346
 
3748
- Options:
3749
- -p <path> Path to tsconfig.json (default: ./tsconfig.json)
3750
- --output <dir> Output directory (default: .adorn)
3751
- --if-stale Only rebuild if artifacts are stale
3752
- --validation-mode <mode> Validation mode: none, ajv-runtime, precompiled (default: ajv-runtime)
3753
- --split Enable automatic schema splitting (default: disabled)
3754
- --split-strategy <mode> Override splitting strategy: controller, dependency, size, auto (default: auto)
3755
- --split-threshold <num> Schema count threshold for auto-split (default: 50)
3756
- --verbose Show detailed progress information
3757
- --quiet Suppress non-essential output
4347
+ Options:
4348
+ -p <path> Path to tsconfig.json (default: ./tsconfig.json)
4349
+ --output <dir> Output directory (default: .adorn)
4350
+ --if-stale Only rebuild if artifacts are stale
4351
+ --validation-mode <mode> Validation mode: none, ajv-runtime, precompiled (default: ajv-runtime)
4352
+ --split Enable automatic schema splitting (default: disabled)
4353
+ --split-strategy <mode> Override splitting strategy: controller, dependency, size, auto (default: auto)
4354
+ --split-threshold <num> Schema count threshold for auto-split (default: 50)
4355
+ --verbose Show detailed progress information
4356
+ --quiet Suppress non-essential output
4357
+ --show-query-builder Show query builder inspection details and statistics
3758
4358
 
3759
- Examples:
3760
- adorn-api build -p ./tsconfig.json --output .adorn
3761
- adorn-api build --if-stale
3762
- adorn-api build --validation-mode precompiled
3763
- adorn-api build --verbose
3764
- adorn-api build --split # Enable split mode
3765
- adorn-api build --split-strategy controller # Force controller-based splitting
3766
- adorn-api build --split-threshold 100 # Increase threshold to 100
3767
- adorn-api clean
3768
- `);
4359
+ Examples:
4360
+ adorn-api build -p ./tsconfig.json --output .adorn
4361
+ adorn-api build --if-stale
4362
+ adorn-api build --validation-mode precompiled
4363
+ adorn-api build --verbose
4364
+ adorn-api build --show-query-builder # Show query builder analysis details
4365
+ adorn-api build --split # Enable split mode
4366
+ adorn-api build --split-strategy controller # Force controller-based splitting
4367
+ adorn-api build --split-threshold 100 # Increase threshold to 100
4368
+ adorn-api clean
4369
+ `);
3769
4370
  }
3770
4371
  //# sourceMappingURL=cli.js.map