adorn-api 1.0.19 → 1.0.21
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 +537 -25
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +537 -25
- package/dist/cli.js.map +1 -1
- package/dist/compiler/schema/objectHandler.d.ts +40 -0
- package/dist/compiler/schema/objectHandler.d.ts.map +1 -1
- package/dist/compiler/schema/openapi.d.ts.map +1 -1
- package/dist/compiler/schema/queryBuilderAnalyzer.d.ts +43 -0
- package/dist/compiler/schema/queryBuilderAnalyzer.d.ts.map +1 -0
- package/dist/compiler/schema/queryBuilderSchemaBuilder.d.ts +13 -0
- package/dist/compiler/schema/queryBuilderSchemaBuilder.d.ts.map +1 -0
- package/dist/compiler/schema/types.d.ts +1 -0
- package/dist/compiler/schema/types.d.ts.map +1 -1
- package/dist/metal/applyListQuery.d.ts +1 -1
- package/dist/metal/applyListQuery.d.ts.map +1 -1
- package/dist/metal/index.cjs.map +1 -1
- package/dist/metal/index.js.map +1 -1
- package/package.json +2 -2
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 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
|
|
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
|
-
|
|
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
|
-
|
|
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,376 @@ 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 analyzeWithVariableTracking(body, checker, options) {
|
|
1636
|
+
let queryBuilderVar = null;
|
|
1637
|
+
let entityName = null;
|
|
1638
|
+
const selectedFields = /* @__PURE__ */ new Set();
|
|
1639
|
+
const includes = {};
|
|
1640
|
+
let isPaged = false;
|
|
1641
|
+
let hasReturn = false;
|
|
1642
|
+
for (const statement of body.statements) {
|
|
1643
|
+
if (ts9.isReturnStatement(statement)) {
|
|
1644
|
+
hasReturn = true;
|
|
1645
|
+
const returnExpr = statement.expression;
|
|
1646
|
+
if (returnExpr && ts9.isCallExpression(returnExpr)) {
|
|
1647
|
+
const callExpr = returnExpr;
|
|
1648
|
+
if (ts9.isIdentifier(callExpr.expression) && queryBuilderVar) {
|
|
1649
|
+
const varName = callExpr.expression.text;
|
|
1650
|
+
if (varName === queryBuilderVar) {
|
|
1651
|
+
const methodName = callExpr.expression.text;
|
|
1652
|
+
if (methodName === "executePaged") {
|
|
1653
|
+
isPaged = true;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
if (ts9.isPropertyAccessExpression(callExpr.expression) && queryBuilderVar) {
|
|
1658
|
+
const propAccess = callExpr.expression;
|
|
1659
|
+
if (ts9.isIdentifier(propAccess.expression) && propAccess.expression.text === queryBuilderVar) {
|
|
1660
|
+
const methodName = propAccess.name.text;
|
|
1661
|
+
if (methodName === "executePaged") {
|
|
1662
|
+
isPaged = true;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
continue;
|
|
1668
|
+
}
|
|
1669
|
+
if (!ts9.isExpressionStatement(statement)) {
|
|
1670
|
+
if (ts9.isVariableStatement(statement)) {
|
|
1671
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1672
|
+
if (!ts9.isIdentifier(declaration.name)) continue;
|
|
1673
|
+
const varName = declaration.name.text;
|
|
1674
|
+
const initializer = declaration.initializer;
|
|
1675
|
+
if (!initializer || !ts9.isCallExpression(initializer)) continue;
|
|
1676
|
+
const opInfo = extractChainedOperation(initializer);
|
|
1677
|
+
if (opInfo && (opInfo.operation === "selectFromEntity" || opInfo.operation === "selectFrom")) {
|
|
1678
|
+
queryBuilderVar = varName;
|
|
1679
|
+
if (opInfo.entityName) {
|
|
1680
|
+
entityName = opInfo.entityName;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
continue;
|
|
1686
|
+
}
|
|
1687
|
+
const expr = statement.expression;
|
|
1688
|
+
if (ts9.isBinaryExpression(expr) && expr.operatorToken.kind === ts9.SyntaxKind.EqualsToken) {
|
|
1689
|
+
if (!ts9.isIdentifier(expr.left)) {
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
const varName = expr.left.text;
|
|
1693
|
+
const rightSide = expr.right;
|
|
1694
|
+
if (ts9.isCallExpression(rightSide)) {
|
|
1695
|
+
const opInfo = extractChainedOperation(rightSide);
|
|
1696
|
+
if (opInfo) {
|
|
1697
|
+
if (opInfo.operation === "selectFromEntity" || opInfo.operation === "selectFrom") {
|
|
1698
|
+
queryBuilderVar = varName;
|
|
1699
|
+
if (opInfo.entityName) {
|
|
1700
|
+
entityName = opInfo.entityName;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
if ((opInfo.operation === "select" || opInfo.operation === "include") && queryBuilderVar === varName) {
|
|
1704
|
+
if (opInfo.operation === "select") {
|
|
1705
|
+
for (const field of opInfo.fields || []) {
|
|
1706
|
+
selectedFields.add(field);
|
|
1707
|
+
}
|
|
1708
|
+
} else if (opInfo.operation === "include" && opInfo.includeArg) {
|
|
1709
|
+
const parsedIncludes = parseIncludeObjectLiteral(opInfo.includeArg);
|
|
1710
|
+
if (parsedIncludes) {
|
|
1711
|
+
for (const [relName, relSchema] of Object.entries(parsedIncludes)) {
|
|
1712
|
+
includes[relName] = relSchema;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
if (!hasReturn || !queryBuilderVar || !entityName) {
|
|
1722
|
+
return null;
|
|
1723
|
+
}
|
|
1724
|
+
return {
|
|
1725
|
+
entityName,
|
|
1726
|
+
selectedFields: Array.from(selectedFields),
|
|
1727
|
+
includes,
|
|
1728
|
+
isPaged
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
function extractChainedOperation(callExpr) {
|
|
1732
|
+
if (ts9.isIdentifier(callExpr.expression)) {
|
|
1733
|
+
const methodName2 = callExpr.expression.text;
|
|
1734
|
+
if (methodName2 === "selectFromEntity" || methodName2 === "selectFrom") {
|
|
1735
|
+
const entityArg = callExpr.arguments[0];
|
|
1736
|
+
let entityName = null;
|
|
1737
|
+
if (ts9.isIdentifier(entityArg)) {
|
|
1738
|
+
entityName = entityArg.text;
|
|
1739
|
+
} else if (ts9.isPropertyAccessExpression(entityArg)) {
|
|
1740
|
+
entityName = entityArg.name.text;
|
|
1741
|
+
}
|
|
1742
|
+
return {
|
|
1743
|
+
operation: methodName2 === "selectFromEntity" ? "selectFromEntity" : "selectFrom",
|
|
1744
|
+
fields: null,
|
|
1745
|
+
includeArg: null,
|
|
1746
|
+
entityName
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (!ts9.isPropertyAccessExpression(callExpr.expression)) {
|
|
1751
|
+
return null;
|
|
1752
|
+
}
|
|
1753
|
+
const propAccess = callExpr.expression;
|
|
1754
|
+
const methodName = propAccess.name.text;
|
|
1755
|
+
if (methodName === "select") {
|
|
1756
|
+
const fields = [];
|
|
1757
|
+
for (const arg of callExpr.arguments) {
|
|
1758
|
+
if (ts9.isStringLiteral(arg)) {
|
|
1759
|
+
fields.push(arg.text);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
return {
|
|
1763
|
+
operation: "select",
|
|
1764
|
+
fields,
|
|
1765
|
+
includeArg: null,
|
|
1766
|
+
entityName: null
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
if (methodName === "include") {
|
|
1770
|
+
return {
|
|
1771
|
+
operation: "include",
|
|
1772
|
+
fields: null,
|
|
1773
|
+
includeArg: callExpr.arguments[0] || null,
|
|
1774
|
+
entityName: null
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
function parseIncludeObjectLiteral(arg) {
|
|
1780
|
+
if (!ts9.isObjectLiteralExpression(arg)) {
|
|
1781
|
+
return null;
|
|
1782
|
+
}
|
|
1783
|
+
const includes = {};
|
|
1784
|
+
for (const prop of arg.properties) {
|
|
1785
|
+
if (!ts9.isPropertyAssignment(prop) || !ts9.isIdentifier(prop.name)) {
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
const relationName = prop.name.text;
|
|
1789
|
+
const value = prop.initializer;
|
|
1790
|
+
if (value.kind === ts9.SyntaxKind.TrueKeyword) {
|
|
1791
|
+
includes[relationName] = true;
|
|
1792
|
+
} else if (ts9.isObjectLiteralExpression(value)) {
|
|
1793
|
+
const nestedSchema = parseNestedInclude(value, 0);
|
|
1794
|
+
if (nestedSchema) {
|
|
1795
|
+
includes[relationName] = nestedSchema;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
return includes;
|
|
1800
|
+
}
|
|
1801
|
+
function parseNestedInclude(obj, depth) {
|
|
1802
|
+
const selectedFields = [];
|
|
1803
|
+
const includes = {};
|
|
1804
|
+
for (const prop of obj.properties) {
|
|
1805
|
+
if (!ts9.isPropertyAssignment(prop) || !ts9.isIdentifier(prop.name)) {
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
const propName = prop.name.text;
|
|
1809
|
+
const value = prop.initializer;
|
|
1810
|
+
if (propName === "select" && ts9.isArrayLiteralExpression(value)) {
|
|
1811
|
+
for (const element of value.elements) {
|
|
1812
|
+
if (ts9.isStringLiteral(element)) {
|
|
1813
|
+
selectedFields.push(element.text);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
} else if (propName === "include" && ts9.isObjectLiteralExpression(value)) {
|
|
1817
|
+
const nestedIncludes = parseIncludeObjectLiteral(value);
|
|
1818
|
+
if (nestedIncludes) {
|
|
1819
|
+
for (const [relName, relSchema] of Object.entries(nestedIncludes)) {
|
|
1820
|
+
includes[relName] = relSchema;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
return {
|
|
1826
|
+
entityName: "",
|
|
1827
|
+
selectedFields,
|
|
1828
|
+
includes,
|
|
1829
|
+
isPaged: false
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
function getMethodName(expression) {
|
|
1833
|
+
if (ts9.isIdentifier(expression)) {
|
|
1834
|
+
return expression.text;
|
|
1835
|
+
}
|
|
1836
|
+
if (ts9.isPropertyAccessExpression(expression)) {
|
|
1837
|
+
return expression.name.text;
|
|
1838
|
+
}
|
|
1839
|
+
return null;
|
|
1840
|
+
}
|
|
1841
|
+
function findReturnStatement(body) {
|
|
1842
|
+
let returnStatement = null;
|
|
1843
|
+
for (const statement of body.statements) {
|
|
1844
|
+
if (ts9.isReturnStatement(statement)) {
|
|
1845
|
+
if (returnStatement !== null) {
|
|
1846
|
+
return null;
|
|
1847
|
+
}
|
|
1848
|
+
returnStatement = statement;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
return returnStatement;
|
|
1852
|
+
}
|
|
1853
|
+
function analyzeReturnExpression(expression) {
|
|
1854
|
+
if (!expression) {
|
|
1855
|
+
return null;
|
|
1856
|
+
}
|
|
1857
|
+
if (ts9.isCallExpression(expression)) {
|
|
1858
|
+
return buildCallChain(expression, null);
|
|
1859
|
+
}
|
|
1860
|
+
return null;
|
|
1861
|
+
}
|
|
1862
|
+
function buildCallChain(node, parent) {
|
|
1863
|
+
if (ts9.isCallExpression(node)) {
|
|
1864
|
+
const callNode = {
|
|
1865
|
+
expression: node.expression,
|
|
1866
|
+
methodName: getMethodName(node.expression),
|
|
1867
|
+
arguments: node.arguments,
|
|
1868
|
+
parent
|
|
1869
|
+
};
|
|
1870
|
+
if (ts9.isPropertyAccessExpression(node.expression)) {
|
|
1871
|
+
return buildCallChain(node.expression.expression, callNode);
|
|
1872
|
+
}
|
|
1873
|
+
return callNode;
|
|
1874
|
+
}
|
|
1875
|
+
return parent;
|
|
1876
|
+
}
|
|
1877
|
+
function parseQueryBuilderChain(chain, checker, options) {
|
|
1878
|
+
if (!chain) {
|
|
1879
|
+
return null;
|
|
1880
|
+
}
|
|
1881
|
+
const rootNode = findSelectFromEntityCall(chain);
|
|
1882
|
+
if (!rootNode) {
|
|
1883
|
+
return null;
|
|
1884
|
+
}
|
|
1885
|
+
const entityName = extractEntityName(rootNode, checker);
|
|
1886
|
+
if (!entityName) {
|
|
1887
|
+
return null;
|
|
1888
|
+
}
|
|
1889
|
+
const selectedFields = /* @__PURE__ */ new Set();
|
|
1890
|
+
const includes = {};
|
|
1891
|
+
let isPaged = false;
|
|
1892
|
+
let currentNode = chain;
|
|
1893
|
+
while (currentNode) {
|
|
1894
|
+
const methodName = currentNode.methodName;
|
|
1895
|
+
if (methodName === "select") {
|
|
1896
|
+
for (const arg of currentNode.arguments) {
|
|
1897
|
+
if (ts9.isStringLiteral(arg)) {
|
|
1898
|
+
selectedFields.add(arg.text);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
} else if (methodName === "include") {
|
|
1902
|
+
parseIncludeArgument(currentNode.arguments[0], includes, checker, options, 0);
|
|
1903
|
+
} else if (methodName === "executePaged") {
|
|
1904
|
+
isPaged = true;
|
|
1905
|
+
}
|
|
1906
|
+
currentNode = currentNode.parent;
|
|
1907
|
+
}
|
|
1908
|
+
return {
|
|
1909
|
+
entityName,
|
|
1910
|
+
selectedFields: Array.from(selectedFields),
|
|
1911
|
+
includes,
|
|
1912
|
+
isPaged
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
function findSelectFromEntityCall(chain) {
|
|
1916
|
+
let currentNode = chain;
|
|
1917
|
+
let lastNode = null;
|
|
1918
|
+
while (currentNode) {
|
|
1919
|
+
if (currentNode.methodName === "selectFromEntity" || currentNode.methodName === "selectFrom") {
|
|
1920
|
+
return currentNode;
|
|
1921
|
+
}
|
|
1922
|
+
lastNode = currentNode;
|
|
1923
|
+
currentNode = currentNode.parent;
|
|
1924
|
+
}
|
|
1925
|
+
return lastNode;
|
|
1926
|
+
}
|
|
1927
|
+
function extractEntityName(callNode, checker) {
|
|
1928
|
+
if (callNode.arguments.length === 0) {
|
|
1929
|
+
return null;
|
|
1930
|
+
}
|
|
1931
|
+
const entityArg = callNode.arguments[0];
|
|
1932
|
+
if (ts9.isIdentifier(entityArg)) {
|
|
1933
|
+
return entityArg.text;
|
|
1934
|
+
}
|
|
1935
|
+
if (ts9.isPropertyAccessExpression(entityArg)) {
|
|
1936
|
+
return entityArg.name.text;
|
|
1937
|
+
}
|
|
1938
|
+
return null;
|
|
1939
|
+
}
|
|
1940
|
+
function parseIncludeArgument(arg, includes, checker, options, depth) {
|
|
1941
|
+
if (!arg) {
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
if (ts9.isObjectLiteralExpression(arg)) {
|
|
1945
|
+
for (const prop of arg.properties) {
|
|
1946
|
+
if (!ts9.isPropertyAssignment(prop) || !ts9.isIdentifier(prop.name)) {
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
const relationName = prop.name.text;
|
|
1950
|
+
const value = prop.initializer;
|
|
1951
|
+
if (value.kind === ts9.SyntaxKind.TrueKeyword) {
|
|
1952
|
+
includes[relationName] = true;
|
|
1953
|
+
} else if (ts9.isObjectLiteralExpression(value)) {
|
|
1954
|
+
const maxDepth = options.maxDepth ?? 5;
|
|
1955
|
+
if (depth < maxDepth) {
|
|
1956
|
+
const nestedSchema = parseNestedInclude(value, depth + 1);
|
|
1957
|
+
if (nestedSchema) {
|
|
1958
|
+
includes[relationName] = nestedSchema;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/compiler/schema/queryBuilderSchemaBuilder.ts
|
|
1967
|
+
import "typescript";
|
|
1968
|
+
function wrapInPaginatedResult(schema) {
|
|
1969
|
+
return {
|
|
1970
|
+
type: "object",
|
|
1971
|
+
properties: {
|
|
1972
|
+
items: {
|
|
1973
|
+
type: "array",
|
|
1974
|
+
items: schema
|
|
1975
|
+
},
|
|
1976
|
+
page: { type: "integer" },
|
|
1977
|
+
pageSize: { type: "integer" },
|
|
1978
|
+
totalItems: { type: "integer" }
|
|
1979
|
+
},
|
|
1980
|
+
required: ["items", "page", "pageSize", "totalItems"]
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1565
1984
|
// src/compiler/schema/openapi.ts
|
|
1566
1985
|
var METAL_ORM_WRAPPER_NAMES2 = ["BelongsToReference", "HasOneReference", "HasManyCollection", "ManyToManyCollection"];
|
|
1567
1986
|
function generateOpenAPI(controllers, checker, options = {}) {
|
|
@@ -1716,6 +2135,88 @@ function convertToOpenApiPath(basePath, path4) {
|
|
|
1716
2135
|
}
|
|
1717
2136
|
return fullPath;
|
|
1718
2137
|
}
|
|
2138
|
+
function tryInferQueryBuilderSchema(operation, checker) {
|
|
2139
|
+
return analyzeQueryBuilderForSchema(operation.methodDeclaration, checker) ?? null;
|
|
2140
|
+
}
|
|
2141
|
+
function getEntityTypeFromReturnType(operation, checker) {
|
|
2142
|
+
const returnType = operation.returnType;
|
|
2143
|
+
const unwrapPromise2 = (type) => {
|
|
2144
|
+
const symbol2 = type.getSymbol();
|
|
2145
|
+
if (symbol2?.getName() === "Promise") {
|
|
2146
|
+
const typeArgs = type.typeArguments;
|
|
2147
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
2148
|
+
return typeArgs[0];
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
return type;
|
|
2152
|
+
};
|
|
2153
|
+
const innerType = unwrapPromise2(returnType);
|
|
2154
|
+
const symbol = innerType.getSymbol();
|
|
2155
|
+
if (symbol?.getName() === "PaginatedResult") {
|
|
2156
|
+
const typeArgs = innerType.typeArguments;
|
|
2157
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
2158
|
+
return typeArgs[0];
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
return null;
|
|
2162
|
+
}
|
|
2163
|
+
function filterSchemaByQueryBuilder(querySchema, operation, ctx) {
|
|
2164
|
+
const entityType = getEntityTypeFromReturnType(operation, ctx.checker);
|
|
2165
|
+
if (!entityType) {
|
|
2166
|
+
return {};
|
|
2167
|
+
}
|
|
2168
|
+
const entitySchema = typeToJsonSchema(entityType, ctx);
|
|
2169
|
+
let baseSchema = entitySchema;
|
|
2170
|
+
if (entitySchema.$ref && entitySchema.$ref.startsWith("#/components/schemas/")) {
|
|
2171
|
+
const schemaName = entitySchema.$ref.replace("#/components/schemas/", "");
|
|
2172
|
+
const componentSchema = ctx.components.get(schemaName);
|
|
2173
|
+
if (componentSchema) {
|
|
2174
|
+
baseSchema = componentSchema;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
if (!baseSchema.properties || Object.keys(baseSchema.properties).length === 0) {
|
|
2178
|
+
return {};
|
|
2179
|
+
}
|
|
2180
|
+
const filteredSchema = buildFilteredSchema(querySchema, baseSchema);
|
|
2181
|
+
if (querySchema.isPaged) {
|
|
2182
|
+
return wrapInPaginatedResult(filteredSchema);
|
|
2183
|
+
}
|
|
2184
|
+
return filteredSchema;
|
|
2185
|
+
}
|
|
2186
|
+
function buildFilteredSchema(querySchema, entitySchema) {
|
|
2187
|
+
const properties = {};
|
|
2188
|
+
const required = [];
|
|
2189
|
+
for (const field of querySchema.selectedFields) {
|
|
2190
|
+
if (entitySchema.properties?.[field]) {
|
|
2191
|
+
properties[field] = entitySchema.properties[field];
|
|
2192
|
+
if (entitySchema.required && entitySchema.required.includes(field)) {
|
|
2193
|
+
required.push(field);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
for (const [relationName, includeSpec] of Object.entries(querySchema.includes)) {
|
|
2198
|
+
if (entitySchema.properties?.[relationName]) {
|
|
2199
|
+
properties[relationName] = {
|
|
2200
|
+
type: "object",
|
|
2201
|
+
properties: {
|
|
2202
|
+
id: { type: "integer" }
|
|
2203
|
+
},
|
|
2204
|
+
required: ["id"]
|
|
2205
|
+
};
|
|
2206
|
+
if (entitySchema.required && entitySchema.required.includes(relationName)) {
|
|
2207
|
+
required.push(relationName);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
const schema = {
|
|
2212
|
+
type: "object",
|
|
2213
|
+
properties
|
|
2214
|
+
};
|
|
2215
|
+
if (required.length > 0) {
|
|
2216
|
+
schema.required = required;
|
|
2217
|
+
}
|
|
2218
|
+
return schema;
|
|
2219
|
+
}
|
|
1719
2220
|
function buildOperation(operation, ctx, controllerConsumes) {
|
|
1720
2221
|
const op = {
|
|
1721
2222
|
operationId: operation.operationId,
|
|
@@ -1730,7 +2231,18 @@ function buildOperation(operation, ctx, controllerConsumes) {
|
|
|
1730
2231
|
op.parameters = parameters;
|
|
1731
2232
|
}
|
|
1732
2233
|
const responseCtx = { ...ctx, mode: "response" };
|
|
1733
|
-
|
|
2234
|
+
let responseSchema;
|
|
2235
|
+
const querySchema = tryInferQueryBuilderSchema(operation, ctx.checker);
|
|
2236
|
+
if (querySchema) {
|
|
2237
|
+
const entityType = getEntityTypeFromReturnType(operation, ctx.checker);
|
|
2238
|
+
if (entityType) {
|
|
2239
|
+
responseSchema = filterSchemaByQueryBuilder(querySchema, operation, responseCtx);
|
|
2240
|
+
} else {
|
|
2241
|
+
responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
|
|
2242
|
+
}
|
|
2243
|
+
} else {
|
|
2244
|
+
responseSchema = typeToJsonSchema(operation.returnType, responseCtx, operation.returnTypeNode);
|
|
2245
|
+
}
|
|
1734
2246
|
const status = operation.httpMethod === "POST" ? 201 : 200;
|
|
1735
2247
|
op.responses[status] = {
|
|
1736
2248
|
description: status === 201 ? "Created" : "OK",
|
|
@@ -1772,12 +2284,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
|
1772
2284
|
const declarations = typeSymbol.getDeclarations();
|
|
1773
2285
|
if (!declarations || declarations.length === 0) return schema;
|
|
1774
2286
|
const classDecl = declarations[0];
|
|
1775
|
-
if (!
|
|
2287
|
+
if (!ts11.isClassDeclaration(classDecl)) return schema;
|
|
1776
2288
|
const result = { ...schema };
|
|
1777
2289
|
const props = { ...result.properties };
|
|
1778
2290
|
for (const member of classDecl.members) {
|
|
1779
|
-
if (!
|
|
1780
|
-
const propName =
|
|
2291
|
+
if (!ts11.isPropertyDeclaration(member) || !member.name) continue;
|
|
2292
|
+
const propName = ts11.isIdentifier(member.name) ? member.name.text : null;
|
|
1781
2293
|
if (!propName) continue;
|
|
1782
2294
|
if (!props[propName]) continue;
|
|
1783
2295
|
const frags = extractPropertySchemaFragments(ctx.checker, member);
|
|
@@ -1790,7 +2302,7 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
|
|
|
1790
2302
|
}
|
|
1791
2303
|
|
|
1792
2304
|
// src/compiler/manifest/emit.ts
|
|
1793
|
-
import
|
|
2305
|
+
import ts12 from "typescript";
|
|
1794
2306
|
function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
|
|
1795
2307
|
const components = /* @__PURE__ */ new Map();
|
|
1796
2308
|
const ctx = {
|
|
@@ -1812,7 +2324,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
|
|
|
1812
2324
|
generator: {
|
|
1813
2325
|
name: "adorn-api",
|
|
1814
2326
|
version,
|
|
1815
|
-
typescript:
|
|
2327
|
+
typescript: ts12.version
|
|
1816
2328
|
},
|
|
1817
2329
|
schemas: {
|
|
1818
2330
|
kind: "openapi-3.1",
|
|
@@ -2297,7 +2809,7 @@ async function isStale(params) {
|
|
|
2297
2809
|
// src/compiler/cache/writeCache.ts
|
|
2298
2810
|
import fs3 from "fs";
|
|
2299
2811
|
import path3 from "path";
|
|
2300
|
-
import
|
|
2812
|
+
import ts13 from "typescript";
|
|
2301
2813
|
function statMtimeMs2(p) {
|
|
2302
2814
|
return fs3.statSync(p).mtimeMs;
|
|
2303
2815
|
}
|
|
@@ -2330,7 +2842,7 @@ function writeCache(params) {
|
|
|
2330
2842
|
generator: {
|
|
2331
2843
|
name: "adorn-api",
|
|
2332
2844
|
version: params.adornVersion,
|
|
2333
|
-
typescript:
|
|
2845
|
+
typescript: ts13.version
|
|
2334
2846
|
},
|
|
2335
2847
|
project: {
|
|
2336
2848
|
tsconfigPath: params.tsconfigAbs,
|
|
@@ -2850,7 +3362,7 @@ function partitionSchemas(schemas, graph, schemaGraph, config = {}) {
|
|
|
2850
3362
|
complexity: totalComplexity,
|
|
2851
3363
|
dependencies: []
|
|
2852
3364
|
}];
|
|
2853
|
-
recommendation = recommendation || "Single file mode (--
|
|
3365
|
+
recommendation = recommendation || "Single file mode (--split not specified)";
|
|
2854
3366
|
} else if (strategy === "controller") {
|
|
2855
3367
|
groups = partitionByController(schemas, graph, finalConfig);
|
|
2856
3368
|
} else if (strategy === "dependency") {
|
|
@@ -3083,7 +3595,7 @@ function getEdgesByRelation(graph, relation) {
|
|
|
3083
3595
|
}
|
|
3084
3596
|
|
|
3085
3597
|
// src/compiler/graph/builder.ts
|
|
3086
|
-
import
|
|
3598
|
+
import ts14 from "typescript";
|
|
3087
3599
|
|
|
3088
3600
|
// src/compiler/graph/schemaGraph.ts
|
|
3089
3601
|
var SchemaGraph = class {
|
|
@@ -3335,7 +3847,7 @@ var SchemaGraph = class {
|
|
|
3335
3847
|
};
|
|
3336
3848
|
|
|
3337
3849
|
// src/cli.ts
|
|
3338
|
-
import
|
|
3850
|
+
import ts15 from "typescript";
|
|
3339
3851
|
import process2 from "process";
|
|
3340
3852
|
var ADORN_VERSION = (() => {
|
|
3341
3853
|
const tryReadPackageJson = (filePath) => {
|
|
@@ -3413,7 +3925,7 @@ function sanitizeForJson(obj) {
|
|
|
3413
3925
|
return result;
|
|
3414
3926
|
}
|
|
3415
3927
|
function buildControllerGraph(controllers) {
|
|
3416
|
-
const graph = createGraph(
|
|
3928
|
+
const graph = createGraph(ts15.version);
|
|
3417
3929
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
3418
3930
|
for (const ctrl of controllers) {
|
|
3419
3931
|
const nodeId = `Controller:${ctrl.className}`;
|
|
@@ -3504,7 +4016,7 @@ async function buildCommand(args) {
|
|
|
3504
4016
|
const validationMode = validationModeIndex !== -1 ? args[validationModeIndex + 1] : "ajv-runtime";
|
|
3505
4017
|
const verbose = args.includes("--verbose");
|
|
3506
4018
|
const quiet = args.includes("--quiet");
|
|
3507
|
-
const
|
|
4019
|
+
const split = args.includes("--split");
|
|
3508
4020
|
const splitStrategyIndex = args.indexOf("--split-strategy");
|
|
3509
4021
|
const splitStrategy = splitStrategyIndex !== -1 ? args[splitStrategyIndex + 1] : void 0;
|
|
3510
4022
|
const splitThresholdIndex = args.indexOf("--split-threshold");
|
|
@@ -3524,7 +4036,7 @@ async function buildCommand(args) {
|
|
|
3524
4036
|
outDir: outputDir,
|
|
3525
4037
|
project: projectPath,
|
|
3526
4038
|
adornVersion: ADORN_VERSION,
|
|
3527
|
-
typescriptVersion:
|
|
4039
|
+
typescriptVersion: ts15.version
|
|
3528
4040
|
});
|
|
3529
4041
|
if (!stale.stale) {
|
|
3530
4042
|
progress.completePhase("staleness-check");
|
|
@@ -3582,7 +4094,7 @@ async function buildCommand(args) {
|
|
|
3582
4094
|
if (!quiet) openapiSpinner.stop();
|
|
3583
4095
|
const schemaCount = Object.keys(openapi.components?.schemas || {}).length;
|
|
3584
4096
|
let splitEnabled = false;
|
|
3585
|
-
if (
|
|
4097
|
+
if (split && schemaCount >= splitThreshold) {
|
|
3586
4098
|
progress.verboseLog(`Schema count (${schemaCount}) >= threshold (${splitThreshold}), analyzing for auto-split...`);
|
|
3587
4099
|
const graph = buildControllerGraph(controllers);
|
|
3588
4100
|
const schemaGraph = new SchemaGraph(graph);
|
|
@@ -3622,9 +4134,9 @@ async function buildCommand(args) {
|
|
|
3622
4134
|
log(` Auto-split not needed: ${partitioning.recommendation}`);
|
|
3623
4135
|
}
|
|
3624
4136
|
}
|
|
3625
|
-
} else if (
|
|
4137
|
+
} else if (!split) {
|
|
3626
4138
|
if (!quiet) {
|
|
3627
|
-
log(` Splitting disabled (--
|
|
4139
|
+
log(` Splitting disabled (--split not specified)`);
|
|
3628
4140
|
}
|
|
3629
4141
|
} else {
|
|
3630
4142
|
if (!quiet) {
|
|
@@ -3745,12 +4257,12 @@ Commands:
|
|
|
3745
4257
|
build Generate OpenAPI and manifest from TypeScript source
|
|
3746
4258
|
clean Remove generated artifacts
|
|
3747
4259
|
|
|
3748
|
-
Options:
|
|
4260
|
+
Options:
|
|
3749
4261
|
-p <path> Path to tsconfig.json (default: ./tsconfig.json)
|
|
3750
4262
|
--output <dir> Output directory (default: .adorn)
|
|
3751
4263
|
--if-stale Only rebuild if artifacts are stale
|
|
3752
4264
|
--validation-mode <mode> Validation mode: none, ajv-runtime, precompiled (default: ajv-runtime)
|
|
3753
|
-
--
|
|
4265
|
+
--split Enable automatic schema splitting (default: disabled)
|
|
3754
4266
|
--split-strategy <mode> Override splitting strategy: controller, dependency, size, auto (default: auto)
|
|
3755
4267
|
--split-threshold <num> Schema count threshold for auto-split (default: 50)
|
|
3756
4268
|
--verbose Show detailed progress information
|
|
@@ -3761,7 +4273,7 @@ Examples:
|
|
|
3761
4273
|
adorn-api build --if-stale
|
|
3762
4274
|
adorn-api build --validation-mode precompiled
|
|
3763
4275
|
adorn-api build --verbose
|
|
3764
|
-
adorn-api build --
|
|
4276
|
+
adorn-api build --split # Enable split mode
|
|
3765
4277
|
adorn-api build --split-strategy controller # Force controller-based splitting
|
|
3766
4278
|
adorn-api build --split-threshold 100 # Increase threshold to 100
|
|
3767
4279
|
adorn-api clean
|