adorn-api 1.0.21 → 1.0.23
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/progress.d.ts +2 -0
- package/dist/cli/progress.d.ts.map +1 -1
- package/dist/cli.cjs +403 -82
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +402 -81
- package/dist/cli.js.map +1 -1
- package/dist/compiler/schema/index.d.ts +2 -0
- package/dist/compiler/schema/index.d.ts.map +1 -1
- package/dist/compiler/schema/openapi.d.ts +29 -0
- package/dist/compiler/schema/openapi.d.ts.map +1 -1
- package/dist/compiler/schema/queryBuilderAnalyzer.d.ts +52 -0
- package/dist/compiler/schema/queryBuilderAnalyzer.d.ts.map +1 -1
- package/dist/compiler/schema/serviceCallAnalyzer.d.ts +102 -0
- package/dist/compiler/schema/serviceCallAnalyzer.d.ts.map +1 -0
- package/package.json +1 -1
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
|
|
309
|
+
import ts12 from "typescript";
|
|
310
310
|
|
|
311
311
|
// src/compiler/schema/typeToJsonSchema.ts
|
|
312
312
|
import ts7 from "typescript";
|
|
@@ -1612,7 +1612,221 @@ function resolveAndCollectObjectProps(schema, components) {
|
|
|
1612
1612
|
}
|
|
1613
1613
|
|
|
1614
1614
|
// src/compiler/schema/queryBuilderAnalyzer.ts
|
|
1615
|
+
import ts10 from "typescript";
|
|
1616
|
+
|
|
1617
|
+
// src/compiler/schema/serviceCallAnalyzer.ts
|
|
1615
1618
|
import ts9 from "typescript";
|
|
1619
|
+
var ServiceCallAnalyzer = class {
|
|
1620
|
+
checker;
|
|
1621
|
+
program;
|
|
1622
|
+
cache = /* @__PURE__ */ new Map();
|
|
1623
|
+
analyzedMethods = /* @__PURE__ */ new Set();
|
|
1624
|
+
constructor(checker, program) {
|
|
1625
|
+
this.checker = checker;
|
|
1626
|
+
this.program = program;
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Analyzes a controller method for query builder patterns, following service calls
|
|
1630
|
+
*/
|
|
1631
|
+
analyzeControllerMethod(methodDeclaration, options = {}) {
|
|
1632
|
+
const cacheKey = this.getMethodCacheKey(methodDeclaration);
|
|
1633
|
+
if (this.cache.has(cacheKey)) {
|
|
1634
|
+
return this.cache.get(cacheKey) ?? null;
|
|
1635
|
+
}
|
|
1636
|
+
const maxDepth = options.maxDepth ?? 3;
|
|
1637
|
+
const schema = this.analyzeMethodWithServiceCalls(methodDeclaration, 0, maxDepth, options);
|
|
1638
|
+
this.cache.set(cacheKey, schema);
|
|
1639
|
+
return schema;
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Recursively analyzes method with service call traversal
|
|
1643
|
+
*/
|
|
1644
|
+
analyzeMethodWithServiceCalls(methodDeclaration, currentDepth, maxDepth, options) {
|
|
1645
|
+
if (currentDepth >= maxDepth) {
|
|
1646
|
+
return null;
|
|
1647
|
+
}
|
|
1648
|
+
const methodKey = this.getMethodCacheKey(methodDeclaration);
|
|
1649
|
+
if (this.analyzedMethods.has(methodKey)) {
|
|
1650
|
+
return null;
|
|
1651
|
+
}
|
|
1652
|
+
this.analyzedMethods.add(methodKey);
|
|
1653
|
+
const directSchema = analyzeQueryBuilderForSchema(methodDeclaration, this.checker);
|
|
1654
|
+
if (directSchema) {
|
|
1655
|
+
return directSchema;
|
|
1656
|
+
}
|
|
1657
|
+
const serviceCalls = this.findServiceCalls(methodDeclaration);
|
|
1658
|
+
for (const serviceCall of serviceCalls) {
|
|
1659
|
+
const serviceSchema = this.analyzeServiceMethod(serviceCall, currentDepth, maxDepth, options);
|
|
1660
|
+
if (serviceSchema) {
|
|
1661
|
+
return serviceSchema;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Analyzes a service method for query builder patterns
|
|
1668
|
+
*/
|
|
1669
|
+
analyzeServiceMethod(serviceCall, currentDepth, maxDepth, options) {
|
|
1670
|
+
const directSchema = analyzeQueryBuilderForSchema(serviceCall.methodDeclaration, this.checker);
|
|
1671
|
+
if (directSchema) {
|
|
1672
|
+
return directSchema;
|
|
1673
|
+
}
|
|
1674
|
+
if (options.analyzeHelpers) {
|
|
1675
|
+
return this.analyzeMethodWithServiceCalls(
|
|
1676
|
+
serviceCall.methodDeclaration,
|
|
1677
|
+
currentDepth + 1,
|
|
1678
|
+
maxDepth,
|
|
1679
|
+
options
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Finds service calls in a method body
|
|
1686
|
+
*/
|
|
1687
|
+
findServiceCalls(methodDeclaration) {
|
|
1688
|
+
const serviceCalls = [];
|
|
1689
|
+
const body = methodDeclaration.body;
|
|
1690
|
+
if (!body) {
|
|
1691
|
+
return serviceCalls;
|
|
1692
|
+
}
|
|
1693
|
+
const visitor = (node) => {
|
|
1694
|
+
if (ts9.isCallExpression(node)) {
|
|
1695
|
+
const serviceCall = this.resolveServiceCall(node);
|
|
1696
|
+
if (serviceCall) {
|
|
1697
|
+
serviceCalls.push(serviceCall);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
ts9.forEachChild(node, visitor);
|
|
1701
|
+
};
|
|
1702
|
+
ts9.forEachChild(body, visitor);
|
|
1703
|
+
return serviceCalls;
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Resolves a call expression to a service method
|
|
1707
|
+
*/
|
|
1708
|
+
resolveServiceCall(callExpression) {
|
|
1709
|
+
if (ts9.isPropertyAccessExpression(callExpression.expression)) {
|
|
1710
|
+
const propAccess = callExpression.expression;
|
|
1711
|
+
const methodName = propAccess.name.text;
|
|
1712
|
+
const objectType = this.checker.getTypeAtLocation(propAccess.expression);
|
|
1713
|
+
const objectSymbol = objectType.getSymbol();
|
|
1714
|
+
if (objectSymbol) {
|
|
1715
|
+
const classDeclaration = this.findClassDeclaration(objectSymbol);
|
|
1716
|
+
if (classDeclaration) {
|
|
1717
|
+
const methodDeclaration = this.findMethodDeclaration(classDeclaration, methodName);
|
|
1718
|
+
if (methodDeclaration) {
|
|
1719
|
+
return {
|
|
1720
|
+
serviceName: classDeclaration.name?.text || "Unknown",
|
|
1721
|
+
methodName,
|
|
1722
|
+
filePath: classDeclaration.getSourceFile().fileName,
|
|
1723
|
+
classDeclaration,
|
|
1724
|
+
methodDeclaration
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
if (ts9.isPropertyAccessExpression(callExpression.expression)) {
|
|
1731
|
+
const propAccess = callExpression.expression;
|
|
1732
|
+
const methodName = propAccess.name.text;
|
|
1733
|
+
if (ts9.isIdentifier(propAccess.expression)) {
|
|
1734
|
+
const className = propAccess.expression.text;
|
|
1735
|
+
const classSymbol = this.checker.getSymbolAtLocation(propAccess.expression);
|
|
1736
|
+
if (classSymbol) {
|
|
1737
|
+
const classDeclaration = this.findClassDeclaration(classSymbol);
|
|
1738
|
+
if (classDeclaration && classDeclaration.name?.text === className) {
|
|
1739
|
+
const methodDeclaration = this.findMethodDeclaration(classDeclaration, methodName);
|
|
1740
|
+
if (methodDeclaration) {
|
|
1741
|
+
return {
|
|
1742
|
+
serviceName: className,
|
|
1743
|
+
methodName,
|
|
1744
|
+
filePath: classDeclaration.getSourceFile().fileName,
|
|
1745
|
+
classDeclaration,
|
|
1746
|
+
methodDeclaration
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
return null;
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Finds class declaration from a symbol
|
|
1757
|
+
*/
|
|
1758
|
+
findClassDeclaration(symbol) {
|
|
1759
|
+
const declarations = symbol.getDeclarations();
|
|
1760
|
+
if (!declarations) return null;
|
|
1761
|
+
for (const declaration of declarations) {
|
|
1762
|
+
if (ts9.isClassDeclaration(declaration)) {
|
|
1763
|
+
return declaration;
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
return null;
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Finds method declaration in a class
|
|
1770
|
+
*/
|
|
1771
|
+
findMethodDeclaration(classDeclaration, methodName) {
|
|
1772
|
+
for (const member of classDeclaration.members) {
|
|
1773
|
+
if (ts9.isMethodDeclaration(member) && member.name) {
|
|
1774
|
+
if (ts9.isIdentifier(member.name) && member.name.text === methodName) {
|
|
1775
|
+
return member;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
return null;
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Generates cache key for a method
|
|
1783
|
+
*/
|
|
1784
|
+
getMethodCacheKey(methodDeclaration) {
|
|
1785
|
+
const sourceFile = methodDeclaration.getSourceFile();
|
|
1786
|
+
const className = this.getClassName(methodDeclaration);
|
|
1787
|
+
const methodName = methodDeclaration.name?.getText() || "unknown";
|
|
1788
|
+
const line = sourceFile.getLineAndCharacterOfPosition(methodDeclaration.getStart()).line;
|
|
1789
|
+
return `${sourceFile.fileName}:${className}:${methodName}:${line}`;
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Gets class name from method declaration
|
|
1793
|
+
*/
|
|
1794
|
+
getClassName(methodDeclaration) {
|
|
1795
|
+
let node = methodDeclaration;
|
|
1796
|
+
while (node) {
|
|
1797
|
+
if (ts9.isClassDeclaration(node)) {
|
|
1798
|
+
return node.name?.text || "Unknown";
|
|
1799
|
+
}
|
|
1800
|
+
node = node.parent;
|
|
1801
|
+
}
|
|
1802
|
+
return "Unknown";
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Clears the analysis cache
|
|
1806
|
+
*/
|
|
1807
|
+
clearCache() {
|
|
1808
|
+
this.cache.clear();
|
|
1809
|
+
this.analyzedMethods.clear();
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Gets cache statistics
|
|
1813
|
+
*/
|
|
1814
|
+
getCacheStats() {
|
|
1815
|
+
return {
|
|
1816
|
+
cached: this.cache.size,
|
|
1817
|
+
analyzed: this.analyzedMethods.size
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
function analyzeControllerWithServiceCalls(methodDeclaration, checker, program, options = {}) {
|
|
1822
|
+
if (!program) {
|
|
1823
|
+
return null;
|
|
1824
|
+
}
|
|
1825
|
+
const analyzer = new ServiceCallAnalyzer(checker, program);
|
|
1826
|
+
return analyzer.analyzeControllerMethod(methodDeclaration, options);
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
// src/compiler/schema/queryBuilderAnalyzer.ts
|
|
1616
1830
|
function analyzeQueryBuilderForSchema(methodDeclaration, checker, options = {}) {
|
|
1617
1831
|
const body = methodDeclaration.body;
|
|
1618
1832
|
if (!body) {
|
|
@@ -1632,6 +1846,24 @@ function analyzeQueryBuilderForSchema(methodDeclaration, checker, options = {})
|
|
|
1632
1846
|
}
|
|
1633
1847
|
return parseQueryBuilderChain(callChain, checker, options);
|
|
1634
1848
|
}
|
|
1849
|
+
function analyzeQueryBuilderWithServiceCalls(methodDeclaration, checker, program, options = {}, operationInfo) {
|
|
1850
|
+
let schema = analyzeQueryBuilderForSchema(methodDeclaration, checker, options);
|
|
1851
|
+
if (!schema && program) {
|
|
1852
|
+
try {
|
|
1853
|
+
schema = analyzeControllerWithServiceCalls(methodDeclaration, checker, program, {
|
|
1854
|
+
maxDepth: options.maxDepth,
|
|
1855
|
+
analyzeHelpers: options.analyzeHelpers
|
|
1856
|
+
});
|
|
1857
|
+
} catch (error) {
|
|
1858
|
+
console.warn("Service call analysis failed:", error);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
return {
|
|
1862
|
+
detected: schema !== null,
|
|
1863
|
+
schema,
|
|
1864
|
+
...operationInfo
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1635
1867
|
function analyzeWithVariableTracking(body, checker, options) {
|
|
1636
1868
|
let queryBuilderVar = null;
|
|
1637
1869
|
let entityName = null;
|
|
@@ -1640,12 +1872,12 @@ function analyzeWithVariableTracking(body, checker, options) {
|
|
|
1640
1872
|
let isPaged = false;
|
|
1641
1873
|
let hasReturn = false;
|
|
1642
1874
|
for (const statement of body.statements) {
|
|
1643
|
-
if (
|
|
1875
|
+
if (ts10.isReturnStatement(statement)) {
|
|
1644
1876
|
hasReturn = true;
|
|
1645
1877
|
const returnExpr = statement.expression;
|
|
1646
|
-
if (returnExpr &&
|
|
1878
|
+
if (returnExpr && ts10.isCallExpression(returnExpr)) {
|
|
1647
1879
|
const callExpr = returnExpr;
|
|
1648
|
-
if (
|
|
1880
|
+
if (ts10.isIdentifier(callExpr.expression) && queryBuilderVar) {
|
|
1649
1881
|
const varName = callExpr.expression.text;
|
|
1650
1882
|
if (varName === queryBuilderVar) {
|
|
1651
1883
|
const methodName = callExpr.expression.text;
|
|
@@ -1654,9 +1886,9 @@ function analyzeWithVariableTracking(body, checker, options) {
|
|
|
1654
1886
|
}
|
|
1655
1887
|
}
|
|
1656
1888
|
}
|
|
1657
|
-
if (
|
|
1889
|
+
if (ts10.isPropertyAccessExpression(callExpr.expression) && queryBuilderVar) {
|
|
1658
1890
|
const propAccess = callExpr.expression;
|
|
1659
|
-
if (
|
|
1891
|
+
if (ts10.isIdentifier(propAccess.expression) && propAccess.expression.text === queryBuilderVar) {
|
|
1660
1892
|
const methodName = propAccess.name.text;
|
|
1661
1893
|
if (methodName === "executePaged") {
|
|
1662
1894
|
isPaged = true;
|
|
@@ -1666,13 +1898,13 @@ function analyzeWithVariableTracking(body, checker, options) {
|
|
|
1666
1898
|
}
|
|
1667
1899
|
continue;
|
|
1668
1900
|
}
|
|
1669
|
-
if (!
|
|
1670
|
-
if (
|
|
1901
|
+
if (!ts10.isExpressionStatement(statement)) {
|
|
1902
|
+
if (ts10.isVariableStatement(statement)) {
|
|
1671
1903
|
for (const declaration of statement.declarationList.declarations) {
|
|
1672
|
-
if (!
|
|
1904
|
+
if (!ts10.isIdentifier(declaration.name)) continue;
|
|
1673
1905
|
const varName = declaration.name.text;
|
|
1674
1906
|
const initializer = declaration.initializer;
|
|
1675
|
-
if (!initializer || !
|
|
1907
|
+
if (!initializer || !ts10.isCallExpression(initializer)) continue;
|
|
1676
1908
|
const opInfo = extractChainedOperation(initializer);
|
|
1677
1909
|
if (opInfo && (opInfo.operation === "selectFromEntity" || opInfo.operation === "selectFrom")) {
|
|
1678
1910
|
queryBuilderVar = varName;
|
|
@@ -1685,13 +1917,13 @@ function analyzeWithVariableTracking(body, checker, options) {
|
|
|
1685
1917
|
continue;
|
|
1686
1918
|
}
|
|
1687
1919
|
const expr = statement.expression;
|
|
1688
|
-
if (
|
|
1689
|
-
if (!
|
|
1920
|
+
if (ts10.isBinaryExpression(expr) && expr.operatorToken.kind === ts10.SyntaxKind.EqualsToken) {
|
|
1921
|
+
if (!ts10.isIdentifier(expr.left)) {
|
|
1690
1922
|
continue;
|
|
1691
1923
|
}
|
|
1692
1924
|
const varName = expr.left.text;
|
|
1693
1925
|
const rightSide = expr.right;
|
|
1694
|
-
if (
|
|
1926
|
+
if (ts10.isCallExpression(rightSide)) {
|
|
1695
1927
|
const opInfo = extractChainedOperation(rightSide);
|
|
1696
1928
|
if (opInfo) {
|
|
1697
1929
|
if (opInfo.operation === "selectFromEntity" || opInfo.operation === "selectFrom") {
|
|
@@ -1729,14 +1961,14 @@ function analyzeWithVariableTracking(body, checker, options) {
|
|
|
1729
1961
|
};
|
|
1730
1962
|
}
|
|
1731
1963
|
function extractChainedOperation(callExpr) {
|
|
1732
|
-
if (
|
|
1964
|
+
if (ts10.isIdentifier(callExpr.expression)) {
|
|
1733
1965
|
const methodName2 = callExpr.expression.text;
|
|
1734
1966
|
if (methodName2 === "selectFromEntity" || methodName2 === "selectFrom") {
|
|
1735
1967
|
const entityArg = callExpr.arguments[0];
|
|
1736
1968
|
let entityName = null;
|
|
1737
|
-
if (
|
|
1969
|
+
if (ts10.isIdentifier(entityArg)) {
|
|
1738
1970
|
entityName = entityArg.text;
|
|
1739
|
-
} else if (
|
|
1971
|
+
} else if (ts10.isPropertyAccessExpression(entityArg)) {
|
|
1740
1972
|
entityName = entityArg.name.text;
|
|
1741
1973
|
}
|
|
1742
1974
|
return {
|
|
@@ -1747,7 +1979,7 @@ function extractChainedOperation(callExpr) {
|
|
|
1747
1979
|
};
|
|
1748
1980
|
}
|
|
1749
1981
|
}
|
|
1750
|
-
if (!
|
|
1982
|
+
if (!ts10.isPropertyAccessExpression(callExpr.expression)) {
|
|
1751
1983
|
return null;
|
|
1752
1984
|
}
|
|
1753
1985
|
const propAccess = callExpr.expression;
|
|
@@ -1755,7 +1987,7 @@ function extractChainedOperation(callExpr) {
|
|
|
1755
1987
|
if (methodName === "select") {
|
|
1756
1988
|
const fields = [];
|
|
1757
1989
|
for (const arg of callExpr.arguments) {
|
|
1758
|
-
if (
|
|
1990
|
+
if (ts10.isStringLiteral(arg)) {
|
|
1759
1991
|
fields.push(arg.text);
|
|
1760
1992
|
}
|
|
1761
1993
|
}
|
|
@@ -1777,19 +2009,19 @@ function extractChainedOperation(callExpr) {
|
|
|
1777
2009
|
return null;
|
|
1778
2010
|
}
|
|
1779
2011
|
function parseIncludeObjectLiteral(arg) {
|
|
1780
|
-
if (!
|
|
2012
|
+
if (!ts10.isObjectLiteralExpression(arg)) {
|
|
1781
2013
|
return null;
|
|
1782
2014
|
}
|
|
1783
2015
|
const includes = {};
|
|
1784
2016
|
for (const prop of arg.properties) {
|
|
1785
|
-
if (!
|
|
2017
|
+
if (!ts10.isPropertyAssignment(prop) || !ts10.isIdentifier(prop.name)) {
|
|
1786
2018
|
continue;
|
|
1787
2019
|
}
|
|
1788
2020
|
const relationName = prop.name.text;
|
|
1789
2021
|
const value = prop.initializer;
|
|
1790
|
-
if (value.kind ===
|
|
2022
|
+
if (value.kind === ts10.SyntaxKind.TrueKeyword) {
|
|
1791
2023
|
includes[relationName] = true;
|
|
1792
|
-
} else if (
|
|
2024
|
+
} else if (ts10.isObjectLiteralExpression(value)) {
|
|
1793
2025
|
const nestedSchema = parseNestedInclude(value, 0);
|
|
1794
2026
|
if (nestedSchema) {
|
|
1795
2027
|
includes[relationName] = nestedSchema;
|
|
@@ -1802,18 +2034,18 @@ function parseNestedInclude(obj, depth) {
|
|
|
1802
2034
|
const selectedFields = [];
|
|
1803
2035
|
const includes = {};
|
|
1804
2036
|
for (const prop of obj.properties) {
|
|
1805
|
-
if (!
|
|
2037
|
+
if (!ts10.isPropertyAssignment(prop) || !ts10.isIdentifier(prop.name)) {
|
|
1806
2038
|
continue;
|
|
1807
2039
|
}
|
|
1808
2040
|
const propName = prop.name.text;
|
|
1809
2041
|
const value = prop.initializer;
|
|
1810
|
-
if (propName === "select" &&
|
|
2042
|
+
if (propName === "select" && ts10.isArrayLiteralExpression(value)) {
|
|
1811
2043
|
for (const element of value.elements) {
|
|
1812
|
-
if (
|
|
2044
|
+
if (ts10.isStringLiteral(element)) {
|
|
1813
2045
|
selectedFields.push(element.text);
|
|
1814
2046
|
}
|
|
1815
2047
|
}
|
|
1816
|
-
} else if (propName === "include" &&
|
|
2048
|
+
} else if (propName === "include" && ts10.isObjectLiteralExpression(value)) {
|
|
1817
2049
|
const nestedIncludes = parseIncludeObjectLiteral(value);
|
|
1818
2050
|
if (nestedIncludes) {
|
|
1819
2051
|
for (const [relName, relSchema] of Object.entries(nestedIncludes)) {
|
|
@@ -1830,10 +2062,10 @@ function parseNestedInclude(obj, depth) {
|
|
|
1830
2062
|
};
|
|
1831
2063
|
}
|
|
1832
2064
|
function getMethodName(expression) {
|
|
1833
|
-
if (
|
|
2065
|
+
if (ts10.isIdentifier(expression)) {
|
|
1834
2066
|
return expression.text;
|
|
1835
2067
|
}
|
|
1836
|
-
if (
|
|
2068
|
+
if (ts10.isPropertyAccessExpression(expression)) {
|
|
1837
2069
|
return expression.name.text;
|
|
1838
2070
|
}
|
|
1839
2071
|
return null;
|
|
@@ -1841,7 +2073,7 @@ function getMethodName(expression) {
|
|
|
1841
2073
|
function findReturnStatement(body) {
|
|
1842
2074
|
let returnStatement = null;
|
|
1843
2075
|
for (const statement of body.statements) {
|
|
1844
|
-
if (
|
|
2076
|
+
if (ts10.isReturnStatement(statement)) {
|
|
1845
2077
|
if (returnStatement !== null) {
|
|
1846
2078
|
return null;
|
|
1847
2079
|
}
|
|
@@ -1854,20 +2086,20 @@ function analyzeReturnExpression(expression) {
|
|
|
1854
2086
|
if (!expression) {
|
|
1855
2087
|
return null;
|
|
1856
2088
|
}
|
|
1857
|
-
if (
|
|
2089
|
+
if (ts10.isCallExpression(expression)) {
|
|
1858
2090
|
return buildCallChain(expression, null);
|
|
1859
2091
|
}
|
|
1860
2092
|
return null;
|
|
1861
2093
|
}
|
|
1862
2094
|
function buildCallChain(node, parent) {
|
|
1863
|
-
if (
|
|
2095
|
+
if (ts10.isCallExpression(node)) {
|
|
1864
2096
|
const callNode = {
|
|
1865
2097
|
expression: node.expression,
|
|
1866
2098
|
methodName: getMethodName(node.expression),
|
|
1867
2099
|
arguments: node.arguments,
|
|
1868
2100
|
parent
|
|
1869
2101
|
};
|
|
1870
|
-
if (
|
|
2102
|
+
if (ts10.isPropertyAccessExpression(node.expression)) {
|
|
1871
2103
|
return buildCallChain(node.expression.expression, callNode);
|
|
1872
2104
|
}
|
|
1873
2105
|
return callNode;
|
|
@@ -1894,7 +2126,7 @@ function parseQueryBuilderChain(chain, checker, options) {
|
|
|
1894
2126
|
const methodName = currentNode.methodName;
|
|
1895
2127
|
if (methodName === "select") {
|
|
1896
2128
|
for (const arg of currentNode.arguments) {
|
|
1897
|
-
if (
|
|
2129
|
+
if (ts10.isStringLiteral(arg)) {
|
|
1898
2130
|
selectedFields.add(arg.text);
|
|
1899
2131
|
}
|
|
1900
2132
|
}
|
|
@@ -1929,10 +2161,10 @@ function extractEntityName(callNode, checker) {
|
|
|
1929
2161
|
return null;
|
|
1930
2162
|
}
|
|
1931
2163
|
const entityArg = callNode.arguments[0];
|
|
1932
|
-
if (
|
|
2164
|
+
if (ts10.isIdentifier(entityArg)) {
|
|
1933
2165
|
return entityArg.text;
|
|
1934
2166
|
}
|
|
1935
|
-
if (
|
|
2167
|
+
if (ts10.isPropertyAccessExpression(entityArg)) {
|
|
1936
2168
|
return entityArg.name.text;
|
|
1937
2169
|
}
|
|
1938
2170
|
return null;
|
|
@@ -1941,16 +2173,16 @@ function parseIncludeArgument(arg, includes, checker, options, depth) {
|
|
|
1941
2173
|
if (!arg) {
|
|
1942
2174
|
return;
|
|
1943
2175
|
}
|
|
1944
|
-
if (
|
|
2176
|
+
if (ts10.isObjectLiteralExpression(arg)) {
|
|
1945
2177
|
for (const prop of arg.properties) {
|
|
1946
|
-
if (!
|
|
2178
|
+
if (!ts10.isPropertyAssignment(prop) || !ts10.isIdentifier(prop.name)) {
|
|
1947
2179
|
continue;
|
|
1948
2180
|
}
|
|
1949
2181
|
const relationName = prop.name.text;
|
|
1950
2182
|
const value = prop.initializer;
|
|
1951
|
-
if (value.kind ===
|
|
2183
|
+
if (value.kind === ts10.SyntaxKind.TrueKeyword) {
|
|
1952
2184
|
includes[relationName] = true;
|
|
1953
|
-
} else if (
|
|
2185
|
+
} else if (ts10.isObjectLiteralExpression(value)) {
|
|
1954
2186
|
const maxDepth = options.maxDepth ?? 5;
|
|
1955
2187
|
if (depth < maxDepth) {
|
|
1956
2188
|
const nestedSchema = parseNestedInclude(value, depth + 1);
|
|
@@ -1993,7 +2225,12 @@ function generateOpenAPI(controllers, checker, options = {}) {
|
|
|
1993
2225
|
mode: "response"
|
|
1994
2226
|
};
|
|
1995
2227
|
const paths = {};
|
|
1996
|
-
const { onProgress } = options;
|
|
2228
|
+
const { onProgress, onQueryBuilderProgress } = options;
|
|
2229
|
+
let totalOperations = 0;
|
|
2230
|
+
for (const controller of controllers) {
|
|
2231
|
+
totalOperations += controller.operations.length;
|
|
2232
|
+
}
|
|
2233
|
+
let currentOperation = 0;
|
|
1997
2234
|
for (let i = 0; i < controllers.length; i++) {
|
|
1998
2235
|
const controller = controllers[i];
|
|
1999
2236
|
if (onProgress) {
|
|
@@ -2005,6 +2242,34 @@ function generateOpenAPI(controllers, checker, options = {}) {
|
|
|
2005
2242
|
paths[fullPath] = {};
|
|
2006
2243
|
}
|
|
2007
2244
|
const method = operation.httpMethod.toLowerCase();
|
|
2245
|
+
const analysisResult = analyzeQueryBuilderWithServiceCalls(
|
|
2246
|
+
operation.methodDeclaration,
|
|
2247
|
+
checker,
|
|
2248
|
+
null,
|
|
2249
|
+
// TODO: Pass program when available
|
|
2250
|
+
{},
|
|
2251
|
+
{
|
|
2252
|
+
methodName: operation.operationId,
|
|
2253
|
+
httpMethod: operation.httpMethod,
|
|
2254
|
+
path: operation.path,
|
|
2255
|
+
operationId: operation.operationId
|
|
2256
|
+
}
|
|
2257
|
+
);
|
|
2258
|
+
if (onQueryBuilderProgress) {
|
|
2259
|
+
currentOperation++;
|
|
2260
|
+
onQueryBuilderProgress({
|
|
2261
|
+
controller: controller.className,
|
|
2262
|
+
operation: operation.operationId,
|
|
2263
|
+
method: operation.httpMethod,
|
|
2264
|
+
path: operation.path,
|
|
2265
|
+
queryBuilderDetected: analysisResult.detected,
|
|
2266
|
+
entityName: analysisResult.schema?.entityName,
|
|
2267
|
+
selectedFields: analysisResult.schema?.selectedFields,
|
|
2268
|
+
isPaged: analysisResult.schema?.isPaged,
|
|
2269
|
+
current: currentOperation,
|
|
2270
|
+
total: totalOperations
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2008
2273
|
paths[fullPath][method] = buildOperation(operation, ctx, controller.consumes);
|
|
2009
2274
|
}
|
|
2010
2275
|
}
|
|
@@ -2284,12 +2549,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
|
2284
2549
|
const declarations = typeSymbol.getDeclarations();
|
|
2285
2550
|
if (!declarations || declarations.length === 0) return schema;
|
|
2286
2551
|
const classDecl = declarations[0];
|
|
2287
|
-
if (!
|
|
2552
|
+
if (!ts12.isClassDeclaration(classDecl)) return schema;
|
|
2288
2553
|
const result = { ...schema };
|
|
2289
2554
|
const props = { ...result.properties };
|
|
2290
2555
|
for (const member of classDecl.members) {
|
|
2291
|
-
if (!
|
|
2292
|
-
const propName =
|
|
2556
|
+
if (!ts12.isPropertyDeclaration(member) || !member.name) continue;
|
|
2557
|
+
const propName = ts12.isIdentifier(member.name) ? member.name.text : null;
|
|
2293
2558
|
if (!propName) continue;
|
|
2294
2559
|
if (!props[propName]) continue;
|
|
2295
2560
|
const frags = extractPropertySchemaFragments(ctx.checker, member);
|
|
@@ -2302,7 +2567,7 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
|
2302
2567
|
}
|
|
2303
2568
|
|
|
2304
2569
|
// src/compiler/manifest/emit.ts
|
|
2305
|
-
import
|
|
2570
|
+
import ts13 from "typescript";
|
|
2306
2571
|
function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
|
|
2307
2572
|
const components = /* @__PURE__ */ new Map();
|
|
2308
2573
|
const ctx = {
|
|
@@ -2324,7 +2589,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
|
|
|
2324
2589
|
generator: {
|
|
2325
2590
|
name: "adorn-api",
|
|
2326
2591
|
version,
|
|
2327
|
-
typescript:
|
|
2592
|
+
typescript: ts13.version
|
|
2328
2593
|
},
|
|
2329
2594
|
schemas: {
|
|
2330
2595
|
kind: "openapi-3.1",
|
|
@@ -2809,7 +3074,7 @@ async function isStale(params) {
|
|
|
2809
3074
|
// src/compiler/cache/writeCache.ts
|
|
2810
3075
|
import fs3 from "fs";
|
|
2811
3076
|
import path3 from "path";
|
|
2812
|
-
import
|
|
3077
|
+
import ts14 from "typescript";
|
|
2813
3078
|
function statMtimeMs2(p) {
|
|
2814
3079
|
return fs3.statSync(p).mtimeMs;
|
|
2815
3080
|
}
|
|
@@ -2842,7 +3107,7 @@ function writeCache(params) {
|
|
|
2842
3107
|
generator: {
|
|
2843
3108
|
name: "adorn-api",
|
|
2844
3109
|
version: params.adornVersion,
|
|
2845
|
-
typescript:
|
|
3110
|
+
typescript: ts14.version
|
|
2846
3111
|
},
|
|
2847
3112
|
project: {
|
|
2848
3113
|
tsconfigPath: params.tsconfigAbs,
|
|
@@ -3004,6 +3269,8 @@ var Spinner = class {
|
|
|
3004
3269
|
current = 0;
|
|
3005
3270
|
total = 0;
|
|
3006
3271
|
customStatus;
|
|
3272
|
+
frameIndex = 0;
|
|
3273
|
+
lastLineLength = 0;
|
|
3007
3274
|
constructor(message = "") {
|
|
3008
3275
|
this.message = message;
|
|
3009
3276
|
}
|
|
@@ -3011,9 +3278,8 @@ var Spinner = class {
|
|
|
3011
3278
|
* Start the spinner.
|
|
3012
3279
|
*/
|
|
3013
3280
|
start() {
|
|
3014
|
-
let frameIndex = 0;
|
|
3015
3281
|
this.interval = setInterval(() => {
|
|
3016
|
-
const frame = this.frames[frameIndex];
|
|
3282
|
+
const frame = this.frames[this.frameIndex];
|
|
3017
3283
|
let output;
|
|
3018
3284
|
if (this.customStatus) {
|
|
3019
3285
|
output = `\r${frame} ${this.customStatus}`;
|
|
@@ -3022,11 +3288,12 @@ var Spinner = class {
|
|
|
3022
3288
|
} else {
|
|
3023
3289
|
output = `\r${frame} ${this.message}`;
|
|
3024
3290
|
}
|
|
3291
|
+
this.lastLineLength = output.length - 1;
|
|
3025
3292
|
process.stdout.write(output);
|
|
3026
3293
|
if (process.stdout.writable) {
|
|
3027
3294
|
process.stdout.write("");
|
|
3028
3295
|
}
|
|
3029
|
-
frameIndex = (frameIndex + 1) % this.frames.length;
|
|
3296
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
3030
3297
|
}, 80);
|
|
3031
3298
|
}
|
|
3032
3299
|
/**
|
|
@@ -3041,8 +3308,12 @@ var Spinner = class {
|
|
|
3041
3308
|
*/
|
|
3042
3309
|
setStatus(status) {
|
|
3043
3310
|
this.customStatus = status;
|
|
3044
|
-
const frame = this.frames[this.
|
|
3045
|
-
|
|
3311
|
+
const frame = this.frames[this.frameIndex];
|
|
3312
|
+
const output = `\r${frame} ${status}`;
|
|
3313
|
+
const clearLength = Math.max(this.lastLineLength, output.length - 1);
|
|
3314
|
+
process.stdout.write("\r" + " ".repeat(clearLength) + "\r");
|
|
3315
|
+
this.lastLineLength = output.length - 1;
|
|
3316
|
+
process.stdout.write(output);
|
|
3046
3317
|
if (process.stdout.writable) {
|
|
3047
3318
|
process.stdout.write("");
|
|
3048
3319
|
}
|
|
@@ -3061,7 +3332,7 @@ var Spinner = class {
|
|
|
3061
3332
|
clearInterval(this.interval);
|
|
3062
3333
|
this.interval = void 0;
|
|
3063
3334
|
}
|
|
3064
|
-
process.stdout.write("\r" + " ".repeat(
|
|
3335
|
+
process.stdout.write("\r" + " ".repeat(this.lastLineLength) + "\r");
|
|
3065
3336
|
if (completedMessage) {
|
|
3066
3337
|
process.stdout.write(completedMessage + "\n");
|
|
3067
3338
|
}
|
|
@@ -3595,7 +3866,7 @@ function getEdgesByRelation(graph, relation) {
|
|
|
3595
3866
|
}
|
|
3596
3867
|
|
|
3597
3868
|
// src/compiler/graph/builder.ts
|
|
3598
|
-
import
|
|
3869
|
+
import ts15 from "typescript";
|
|
3599
3870
|
|
|
3600
3871
|
// src/compiler/graph/schemaGraph.ts
|
|
3601
3872
|
var SchemaGraph = class {
|
|
@@ -3847,7 +4118,7 @@ var SchemaGraph = class {
|
|
|
3847
4118
|
};
|
|
3848
4119
|
|
|
3849
4120
|
// src/cli.ts
|
|
3850
|
-
import
|
|
4121
|
+
import ts16 from "typescript";
|
|
3851
4122
|
import process2 from "process";
|
|
3852
4123
|
var ADORN_VERSION = (() => {
|
|
3853
4124
|
const tryReadPackageJson = (filePath) => {
|
|
@@ -3925,7 +4196,7 @@ function sanitizeForJson(obj) {
|
|
|
3925
4196
|
return result;
|
|
3926
4197
|
}
|
|
3927
4198
|
function buildControllerGraph(controllers) {
|
|
3928
|
-
const graph = createGraph(
|
|
4199
|
+
const graph = createGraph(ts16.version);
|
|
3929
4200
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
3930
4201
|
for (const ctrl of controllers) {
|
|
3931
4202
|
const nodeId = `Controller:${ctrl.className}`;
|
|
@@ -4017,6 +4288,7 @@ async function buildCommand(args) {
|
|
|
4017
4288
|
const verbose = args.includes("--verbose");
|
|
4018
4289
|
const quiet = args.includes("--quiet");
|
|
4019
4290
|
const split = args.includes("--split");
|
|
4291
|
+
const showQueryBuilder = args.includes("--show-query-builder");
|
|
4020
4292
|
const splitStrategyIndex = args.indexOf("--split-strategy");
|
|
4021
4293
|
const splitStrategy = splitStrategyIndex !== -1 ? args[splitStrategyIndex + 1] : void 0;
|
|
4022
4294
|
const splitThresholdIndex = args.indexOf("--split-threshold");
|
|
@@ -4036,7 +4308,7 @@ async function buildCommand(args) {
|
|
|
4036
4308
|
outDir: outputDir,
|
|
4037
4309
|
project: projectPath,
|
|
4038
4310
|
adornVersion: ADORN_VERSION,
|
|
4039
|
-
typescriptVersion:
|
|
4311
|
+
typescriptVersion: ts16.version
|
|
4040
4312
|
});
|
|
4041
4313
|
if (!stale.stale) {
|
|
4042
4314
|
progress.completePhase("staleness-check");
|
|
@@ -4077,6 +4349,12 @@ async function buildCommand(args) {
|
|
|
4077
4349
|
}
|
|
4078
4350
|
}
|
|
4079
4351
|
progress.startPhase("openapi", "Generating OpenAPI schema");
|
|
4352
|
+
const queryBuilderStats = {
|
|
4353
|
+
totalOperations,
|
|
4354
|
+
detected: 0,
|
|
4355
|
+
fallback: 0,
|
|
4356
|
+
operations: []
|
|
4357
|
+
};
|
|
4080
4358
|
const openapiSpinner = new Spinner("Processing schemas");
|
|
4081
4359
|
if (!quiet) openapiSpinner.start();
|
|
4082
4360
|
const openapi = generateOpenAPI(controllers, checker, {
|
|
@@ -4086,6 +4364,31 @@ async function buildCommand(args) {
|
|
|
4086
4364
|
if (!quiet) {
|
|
4087
4365
|
openapiSpinner.setStatus(`${message} (${current}/${total})`);
|
|
4088
4366
|
}
|
|
4367
|
+
},
|
|
4368
|
+
onQueryBuilderProgress: (info) => {
|
|
4369
|
+
if (info.queryBuilderDetected) {
|
|
4370
|
+
queryBuilderStats.detected++;
|
|
4371
|
+
} else {
|
|
4372
|
+
queryBuilderStats.fallback++;
|
|
4373
|
+
}
|
|
4374
|
+
queryBuilderStats.operations.push({
|
|
4375
|
+
operationId: info.operation,
|
|
4376
|
+
method: info.method,
|
|
4377
|
+
path: info.path,
|
|
4378
|
+
detected: info.queryBuilderDetected,
|
|
4379
|
+
entityName: info.entityName,
|
|
4380
|
+
selectedFields: info.selectedFields,
|
|
4381
|
+
isPaged: info.isPaged
|
|
4382
|
+
});
|
|
4383
|
+
if (showQueryBuilder || verbose) {
|
|
4384
|
+
if (info.queryBuilderDetected) {
|
|
4385
|
+
const fieldsStr = info.selectedFields && info.selectedFields.length > 0 ? ` [select: ${info.selectedFields.join(",")}]` : "";
|
|
4386
|
+
const pagedStr = info.isPaged ? " (paged)" : "";
|
|
4387
|
+
progress.verboseLog(` \u2713 Query builder: ${info.method} ${info.path} \u2192 ${info.entityName}${fieldsStr}${pagedStr}`);
|
|
4388
|
+
} else {
|
|
4389
|
+
progress.verboseLog(` \u25CB No query builder: ${info.method} ${info.path} \u2192 using full entity schema`);
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4089
4392
|
}
|
|
4090
4393
|
});
|
|
4091
4394
|
if (!quiet) {
|
|
@@ -4144,6 +4447,17 @@ async function buildCommand(args) {
|
|
|
4144
4447
|
}
|
|
4145
4448
|
}
|
|
4146
4449
|
progress.completePhase("openapi", `Generated ${schemaCount} schema(s)${splitEnabled ? " (split into groups)" : ""}`);
|
|
4450
|
+
if (showQueryBuilder && totalOperations > 0) {
|
|
4451
|
+
log("");
|
|
4452
|
+
log("Query Builder Analysis:");
|
|
4453
|
+
log(` Operations analyzed: ${totalOperations}`);
|
|
4454
|
+
log(` Patterns detected: ${queryBuilderStats.detected} (${Math.round(queryBuilderStats.detected / totalOperations * 100)}%)`);
|
|
4455
|
+
log(` Full schemas used: ${queryBuilderStats.fallback} (${Math.round(queryBuilderStats.fallback / totalOperations * 100)}%)`);
|
|
4456
|
+
if (queryBuilderStats.detected > 0) {
|
|
4457
|
+
const totalFields = queryBuilderStats.operations.filter((op) => op.detected && op.selectedFields).reduce((sum, op) => sum + (op.selectedFields?.length || 0), 0);
|
|
4458
|
+
log(` Fields selected: ${totalFields} total (avg ${Math.round(totalFields / queryBuilderStats.detected)} per query)`);
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4147
4461
|
progress.startPhase("manifest", "Generating manifest");
|
|
4148
4462
|
const manifest = generateManifest(controllers, checker, ADORN_VERSION, validationMode);
|
|
4149
4463
|
progress.completePhase("manifest");
|
|
@@ -4225,7 +4539,12 @@ async function buildCommand(args) {
|
|
|
4225
4539
|
schemas: schemaCount,
|
|
4226
4540
|
sourceFiles: projectSourceFiles.length,
|
|
4227
4541
|
artifactsWritten: artifacts.map((a) => a.name),
|
|
4228
|
-
splitEnabled
|
|
4542
|
+
splitEnabled,
|
|
4543
|
+
queryBuilder: {
|
|
4544
|
+
detected: queryBuilderStats.detected,
|
|
4545
|
+
fallback: queryBuilderStats.fallback,
|
|
4546
|
+
total: totalOperations
|
|
4547
|
+
}
|
|
4229
4548
|
};
|
|
4230
4549
|
progress.printSummary(stats);
|
|
4231
4550
|
progress.printArtifacts(artifacts);
|
|
@@ -4253,30 +4572,32 @@ if (command === "build") {
|
|
|
4253
4572
|
console.log(`
|
|
4254
4573
|
adorn-api CLI v${ADORN_VERSION}
|
|
4255
4574
|
|
|
4256
|
-
Commands:
|
|
4257
|
-
|
|
4258
|
-
|
|
4575
|
+
Commands:
|
|
4576
|
+
build Generate OpenAPI and manifest from TypeScript source
|
|
4577
|
+
clean Remove generated artifacts
|
|
4259
4578
|
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4579
|
+
Options:
|
|
4580
|
+
-p <path> Path to tsconfig.json (default: ./tsconfig.json)
|
|
4581
|
+
--output <dir> Output directory (default: .adorn)
|
|
4582
|
+
--if-stale Only rebuild if artifacts are stale
|
|
4583
|
+
--validation-mode <mode> Validation mode: none, ajv-runtime, precompiled (default: ajv-runtime)
|
|
4584
|
+
--split Enable automatic schema splitting (default: disabled)
|
|
4585
|
+
--split-strategy <mode> Override splitting strategy: controller, dependency, size, auto (default: auto)
|
|
4586
|
+
--split-threshold <num> Schema count threshold for auto-split (default: 50)
|
|
4587
|
+
--verbose Show detailed progress information
|
|
4588
|
+
--quiet Suppress non-essential output
|
|
4589
|
+
--show-query-builder Show query builder inspection details and statistics
|
|
4270
4590
|
|
|
4271
|
-
Examples:
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4591
|
+
Examples:
|
|
4592
|
+
adorn-api build -p ./tsconfig.json --output .adorn
|
|
4593
|
+
adorn-api build --if-stale
|
|
4594
|
+
adorn-api build --validation-mode precompiled
|
|
4595
|
+
adorn-api build --verbose
|
|
4596
|
+
adorn-api build --show-query-builder # Show query builder analysis details
|
|
4597
|
+
adorn-api build --split # Enable split mode
|
|
4598
|
+
adorn-api build --split-strategy controller # Force controller-based splitting
|
|
4599
|
+
adorn-api build --split-threshold 100 # Increase threshold to 100
|
|
4600
|
+
adorn-api clean
|
|
4601
|
+
`);
|
|
4281
4602
|
}
|
|
4282
4603
|
//# sourceMappingURL=cli.js.map
|