@fairfox/polly 0.4.1 → 0.5.0

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.
@@ -1314,16 +1314,250 @@ class IntegrationAnalyzer {
1314
1314
  }
1315
1315
 
1316
1316
  // vendor/analysis/src/extract/handlers.ts
1317
- import { Project as Project4, SyntaxKind as SyntaxKind2, Node as Node4 } from "ts-morph";
1317
+ import { Project as Project4, SyntaxKind as SyntaxKind3, Node as Node5 } from "ts-morph";
1318
1318
 
1319
+ // vendor/analysis/src/extract/relationships.ts
1320
+ import { Node as Node4 } from "ts-morph";
1321
+
1322
+ class RelationshipExtractor {
1323
+ extractFromHandler(handlerNode, sourceFile, handlerName) {
1324
+ const relationships = [];
1325
+ const visited = new Set;
1326
+ this.extractFromNode(handlerNode, sourceFile, handlerName, relationships, visited);
1327
+ return this.deduplicateRelationships(relationships);
1328
+ }
1329
+ extractFromNode(node, sourceFile, handlerName, relationships, visited) {
1330
+ node.forEachDescendant((descendant) => {
1331
+ if (Node4.isCallExpression(descendant)) {
1332
+ const expr = descendant.getExpression();
1333
+ if (Node4.isIdentifier(expr)) {
1334
+ const functionName = expr.getText();
1335
+ const functionDecl = sourceFile.getFunction(functionName);
1336
+ if (functionDecl && !visited.has(functionName)) {
1337
+ visited.add(functionName);
1338
+ const body = functionDecl.getBody();
1339
+ if (body) {
1340
+ this.extractFromNode(body, sourceFile, handlerName, relationships, visited);
1341
+ }
1342
+ return;
1343
+ }
1344
+ }
1345
+ if (Node4.isPropertyAccessExpression(expr)) {
1346
+ const rel2 = this.extractFromPropertyAccess(expr, handlerName);
1347
+ if (rel2) {
1348
+ relationships.push(rel2);
1349
+ return;
1350
+ }
1351
+ }
1352
+ const rel = this.extractFromFunctionCall(descendant, handlerName, sourceFile);
1353
+ if (rel) {
1354
+ relationships.push(rel);
1355
+ }
1356
+ }
1357
+ if (Node4.isAwaitExpression(descendant)) {
1358
+ const rel = this.extractFromDatabaseCall(descendant, handlerName);
1359
+ if (rel) {
1360
+ relationships.push(rel);
1361
+ }
1362
+ }
1363
+ if (Node4.isCallExpression(descendant) && descendant.getExpression().getText() === "fetch") {
1364
+ const rel = this.extractFromFetchCall(descendant, handlerName);
1365
+ if (rel) {
1366
+ relationships.push(rel);
1367
+ }
1368
+ }
1369
+ });
1370
+ }
1371
+ extractFromFunctionCall(callExpr, handlerName, sourceFile) {
1372
+ const expr = callExpr.getExpression();
1373
+ const exprText = expr.getText();
1374
+ const skipList = [
1375
+ "console.",
1376
+ "JSON.",
1377
+ "Math.",
1378
+ "Object.",
1379
+ "Array.",
1380
+ "String.",
1381
+ "Number.",
1382
+ "Date.",
1383
+ "Promise.",
1384
+ "setTimeout",
1385
+ "setInterval",
1386
+ "clearTimeout",
1387
+ "clearInterval"
1388
+ ];
1389
+ if (skipList.some((skip) => exprText.startsWith(skip))) {
1390
+ return null;
1391
+ }
1392
+ let functionName = exprText;
1393
+ let targetComponent = null;
1394
+ if (Node4.isPropertyAccessExpression(expr)) {
1395
+ const objectExpr = expr.getExpression();
1396
+ const objectName = objectExpr.getText();
1397
+ const methodName = expr.getName();
1398
+ targetComponent = this.inferComponentFromCall(objectName, methodName);
1399
+ if (!targetComponent) {
1400
+ return null;
1401
+ }
1402
+ functionName = methodName;
1403
+ } else {
1404
+ targetComponent = this.resolveComponentFromImport(exprText, sourceFile);
1405
+ if (!targetComponent) {
1406
+ return null;
1407
+ }
1408
+ }
1409
+ return {
1410
+ from: this.toComponentId(handlerName),
1411
+ to: targetComponent,
1412
+ description: `Calls ${functionName}()`,
1413
+ technology: "Function Call",
1414
+ confidence: "high",
1415
+ evidence: [`Function call: ${exprText}`]
1416
+ };
1417
+ }
1418
+ extractFromPropertyAccess(propAccess, handlerName) {
1419
+ if (!Node4.isPropertyAccessExpression(propAccess)) {
1420
+ return null;
1421
+ }
1422
+ const fullChain = propAccess.getText();
1423
+ const objectExpr = propAccess.getExpression();
1424
+ const objectName = objectExpr.getText();
1425
+ const methodName = propAccess.getName();
1426
+ const targetComponent = this.inferComponentFromCall(objectName, methodName);
1427
+ if (!targetComponent) {
1428
+ return null;
1429
+ }
1430
+ return {
1431
+ from: this.toComponentId(handlerName),
1432
+ to: targetComponent,
1433
+ description: `Calls ${methodName}()`,
1434
+ technology: "Function Call",
1435
+ confidence: "high",
1436
+ evidence: [`Property access: ${fullChain}`]
1437
+ };
1438
+ }
1439
+ extractFromDatabaseCall(awaitExpr, handlerName) {
1440
+ if (!Node4.isAwaitExpression(awaitExpr)) {
1441
+ return null;
1442
+ }
1443
+ const innerExpr = awaitExpr.getExpression();
1444
+ if (!Node4.isCallExpression(innerExpr)) {
1445
+ return null;
1446
+ }
1447
+ const callExpr = innerExpr.getExpression().getText();
1448
+ if (callExpr.includes("db.query") || callExpr.includes("db.execute") || callExpr.includes("db.select") || callExpr.includes("db.insert") || callExpr.includes("db.update") || callExpr.includes("db.delete")) {
1449
+ const operation = this.inferDatabaseOperation(callExpr);
1450
+ return {
1451
+ from: this.toComponentId(handlerName),
1452
+ to: "database",
1453
+ description: operation,
1454
+ technology: "SQL",
1455
+ confidence: "high",
1456
+ evidence: [`Database call: ${callExpr}`]
1457
+ };
1458
+ }
1459
+ return null;
1460
+ }
1461
+ extractFromFetchCall(callExpr, handlerName) {
1462
+ const args = callExpr.getArguments();
1463
+ if (args.length === 0) {
1464
+ return null;
1465
+ }
1466
+ const urlArg = args[0].getText();
1467
+ let apiName = "external_api";
1468
+ if (urlArg.includes("openai")) {
1469
+ apiName = "openai_api";
1470
+ } else if (urlArg.includes("anthropic")) {
1471
+ apiName = "anthropic_api";
1472
+ }
1473
+ return {
1474
+ from: this.toComponentId(handlerName),
1475
+ to: apiName,
1476
+ description: "Calls external API",
1477
+ technology: "HTTP/REST",
1478
+ confidence: "high",
1479
+ evidence: [`fetch() call to: ${urlArg}`]
1480
+ };
1481
+ }
1482
+ inferComponentFromCall(objectName, methodName) {
1483
+ const mappings = {
1484
+ db: "db_client",
1485
+ database: "database",
1486
+ repos: "repositories",
1487
+ repository: "repositories",
1488
+ cache: "cache",
1489
+ storage: "storage",
1490
+ ai: "ai_service",
1491
+ auth: "auth_service",
1492
+ authservice: "auth_service",
1493
+ user: "user_service",
1494
+ userservice: "user_service",
1495
+ logger: "logger",
1496
+ queue: "queue_service"
1497
+ };
1498
+ const normalized = objectName.toLowerCase();
1499
+ return mappings[normalized] || null;
1500
+ }
1501
+ resolveComponentFromImport(functionName, sourceFile) {
1502
+ for (const importDecl of sourceFile.getImportDeclarations()) {
1503
+ const namedImports = importDecl.getNamedImports();
1504
+ for (const namedImport of namedImports) {
1505
+ if (namedImport.getName() === functionName) {
1506
+ const modulePath = importDecl.getModuleSpecifierValue();
1507
+ if (modulePath.includes("/db/") || modulePath.includes("/database/")) {
1508
+ return "db_client";
1509
+ }
1510
+ if (modulePath.includes("/repos") || modulePath.includes("/repositories")) {
1511
+ return "repositories";
1512
+ }
1513
+ if (modulePath.includes("/service") || modulePath.includes("/services")) {
1514
+ const match = modulePath.match(/\/([^/]+)\.ts$/);
1515
+ if (match) {
1516
+ return this.toComponentId(match[1]);
1517
+ }
1518
+ }
1519
+ }
1520
+ }
1521
+ }
1522
+ return null;
1523
+ }
1524
+ inferDatabaseOperation(callExpr) {
1525
+ if (callExpr.includes("query") || callExpr.includes("select")) {
1526
+ return "Reads from database";
1527
+ }
1528
+ if (callExpr.includes("execute") || callExpr.includes("insert") || callExpr.includes("update") || callExpr.includes("delete")) {
1529
+ return "Writes to database";
1530
+ }
1531
+ return "Accesses database";
1532
+ }
1533
+ toComponentId(name) {
1534
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
1535
+ }
1536
+ deduplicateRelationships(relationships) {
1537
+ const seen = new Set;
1538
+ const unique = [];
1539
+ for (const rel of relationships) {
1540
+ const key = `${rel.from}->${rel.to}`;
1541
+ if (!seen.has(key)) {
1542
+ seen.add(key);
1543
+ unique.push(rel);
1544
+ }
1545
+ }
1546
+ return unique;
1547
+ }
1548
+ }
1549
+
1550
+ // vendor/analysis/src/extract/handlers.ts
1319
1551
  class HandlerExtractor {
1320
1552
  project;
1321
1553
  typeGuardCache;
1554
+ relationshipExtractor;
1322
1555
  constructor(tsConfigPath) {
1323
1556
  this.project = new Project4({
1324
1557
  tsConfigFilePath: tsConfigPath
1325
1558
  });
1326
1559
  this.typeGuardCache = new WeakMap;
1560
+ this.relationshipExtractor = new RelationshipExtractor;
1327
1561
  }
1328
1562
  extractHandlers() {
1329
1563
  const handlers = [];
@@ -1357,9 +1591,9 @@ class HandlerExtractor {
1357
1591
  const filePath = sourceFile.getFilePath();
1358
1592
  const context = this.inferContext(filePath);
1359
1593
  sourceFile.forEachDescendant((node) => {
1360
- if (Node4.isCallExpression(node)) {
1594
+ if (Node5.isCallExpression(node)) {
1361
1595
  const expression = node.getExpression();
1362
- if (Node4.isPropertyAccessExpression(expression)) {
1596
+ if (Node5.isPropertyAccessExpression(expression)) {
1363
1597
  const methodName = expression.getName();
1364
1598
  if (methodName === "on" || methodName === "addEventListener") {
1365
1599
  const handler = this.extractHandler(node, context, filePath);
@@ -1369,17 +1603,17 @@ class HandlerExtractor {
1369
1603
  }
1370
1604
  }
1371
1605
  }
1372
- if (Node4.isSwitchStatement(node)) {
1606
+ if (Node5.isSwitchStatement(node)) {
1373
1607
  const switchHandlers = this.extractSwitchCaseHandlers(node, context, filePath);
1374
1608
  handlers.push(...switchHandlers);
1375
1609
  }
1376
- if (Node4.isVariableDeclaration(node)) {
1610
+ if (Node5.isVariableDeclaration(node)) {
1377
1611
  const mapHandlers = this.extractHandlerMapPattern(node, context, filePath);
1378
1612
  handlers.push(...mapHandlers);
1379
1613
  }
1380
- if (Node4.isIfStatement(node)) {
1614
+ if (Node5.isIfStatement(node)) {
1381
1615
  const parent = node.getParent();
1382
- const isElseIf = parent && Node4.isIfStatement(parent);
1616
+ const isElseIf = parent && Node5.isIfStatement(parent);
1383
1617
  if (!isElseIf) {
1384
1618
  const typeGuardHandlers = this.extractTypeGuardHandlers(node, context, filePath);
1385
1619
  handlers.push(...typeGuardHandlers);
@@ -1395,9 +1629,9 @@ class HandlerExtractor {
1395
1629
  }
1396
1630
  const messageTypeArg = args[0];
1397
1631
  let messageType = null;
1398
- if (Node4.isStringLiteral(messageTypeArg)) {
1632
+ if (Node5.isStringLiteral(messageTypeArg)) {
1399
1633
  messageType = messageTypeArg.getLiteralValue();
1400
- } else if (Node4.isTemplateExpression(messageTypeArg)) {
1634
+ } else if (Node5.isTemplateExpression(messageTypeArg)) {
1401
1635
  messageType = messageTypeArg.getText().replace(/[`'"]/g, "");
1402
1636
  }
1403
1637
  if (!messageType) {
@@ -1407,11 +1641,20 @@ class HandlerExtractor {
1407
1641
  const assignments = [];
1408
1642
  const preconditions = [];
1409
1643
  const postconditions = [];
1410
- if (Node4.isArrowFunction(handlerArg) || Node4.isFunctionExpression(handlerArg)) {
1644
+ if (Node5.isArrowFunction(handlerArg) || Node5.isFunctionExpression(handlerArg)) {
1411
1645
  this.extractAssignments(handlerArg, assignments);
1412
1646
  this.extractVerificationConditions(handlerArg, preconditions, postconditions);
1413
1647
  }
1414
1648
  const line = callExpr.getStartLineNumber();
1649
+ const sourceFile = callExpr.getSourceFile();
1650
+ const handlerName = `${messageType}_handler`;
1651
+ let relationships = undefined;
1652
+ if (Node5.isArrowFunction(handlerArg) || Node5.isFunctionExpression(handlerArg)) {
1653
+ const detectedRelationships = this.relationshipExtractor.extractFromHandler(handlerArg, sourceFile, handlerName);
1654
+ if (detectedRelationships.length > 0) {
1655
+ relationships = detectedRelationships;
1656
+ }
1657
+ }
1415
1658
  return {
1416
1659
  messageType,
1417
1660
  node: context,
@@ -1421,17 +1664,18 @@ class HandlerExtractor {
1421
1664
  location: {
1422
1665
  file: filePath,
1423
1666
  line
1424
- }
1667
+ },
1668
+ relationships
1425
1669
  };
1426
1670
  }
1427
1671
  extractAssignments(funcNode, assignments) {
1428
1672
  funcNode.forEachDescendant((node) => {
1429
- if (Node4.isBinaryExpression(node)) {
1673
+ if (Node5.isBinaryExpression(node)) {
1430
1674
  const operator = node.getOperatorToken().getText();
1431
1675
  if (operator === "=") {
1432
1676
  const left = node.getLeft();
1433
1677
  const right = node.getRight();
1434
- if (Node4.isPropertyAccessExpression(left)) {
1678
+ if (Node5.isPropertyAccessExpression(left)) {
1435
1679
  const fieldPath = this.getPropertyPath(left);
1436
1680
  if (fieldPath.startsWith("state.")) {
1437
1681
  const field = fieldPath.substring(6);
@@ -1450,13 +1694,13 @@ class HandlerExtractor {
1450
1694
  }
1451
1695
  extractVerificationConditions(funcNode, preconditions, postconditions) {
1452
1696
  const body = funcNode.getBody();
1453
- const statements = Node4.isBlock(body) ? body.getStatements() : [body];
1697
+ const statements = Node5.isBlock(body) ? body.getStatements() : [body];
1454
1698
  statements.forEach((statement, index) => {
1455
- if (Node4.isExpressionStatement(statement)) {
1699
+ if (Node5.isExpressionStatement(statement)) {
1456
1700
  const expr = statement.getExpression();
1457
- if (Node4.isCallExpression(expr)) {
1701
+ if (Node5.isCallExpression(expr)) {
1458
1702
  const callee = expr.getExpression();
1459
- if (Node4.isIdentifier(callee)) {
1703
+ if (Node5.isIdentifier(callee)) {
1460
1704
  const functionName = callee.getText();
1461
1705
  if (functionName === "requires") {
1462
1706
  const condition = this.extractCondition(expr);
@@ -1482,7 +1726,7 @@ class HandlerExtractor {
1482
1726
  const conditionArg = args[0];
1483
1727
  const expression = conditionArg.getText();
1484
1728
  let message;
1485
- if (args.length >= 2 && Node4.isStringLiteral(args[1])) {
1729
+ if (args.length >= 2 && Node5.isStringLiteral(args[1])) {
1486
1730
  message = args[1].getLiteralValue();
1487
1731
  }
1488
1732
  const line = callExpr.getStartLineNumber();
@@ -1499,29 +1743,29 @@ class HandlerExtractor {
1499
1743
  getPropertyPath(node) {
1500
1744
  const parts = [];
1501
1745
  let current = node;
1502
- while (Node4.isPropertyAccessExpression(current)) {
1746
+ while (Node5.isPropertyAccessExpression(current)) {
1503
1747
  parts.unshift(current.getName());
1504
1748
  current = current.getExpression();
1505
1749
  }
1506
- if (Node4.isIdentifier(current)) {
1750
+ if (Node5.isIdentifier(current)) {
1507
1751
  parts.unshift(current.getText());
1508
1752
  }
1509
1753
  return parts.join(".");
1510
1754
  }
1511
1755
  extractValue(node) {
1512
- if (Node4.isStringLiteral(node)) {
1756
+ if (Node5.isStringLiteral(node)) {
1513
1757
  return node.getLiteralValue();
1514
1758
  }
1515
- if (Node4.isNumericLiteral(node)) {
1759
+ if (Node5.isNumericLiteral(node)) {
1516
1760
  return node.getLiteralValue();
1517
1761
  }
1518
- if (node.getKind() === SyntaxKind2.TrueKeyword) {
1762
+ if (node.getKind() === SyntaxKind3.TrueKeyword) {
1519
1763
  return true;
1520
1764
  }
1521
- if (node.getKind() === SyntaxKind2.FalseKeyword) {
1765
+ if (node.getKind() === SyntaxKind3.FalseKeyword) {
1522
1766
  return false;
1523
1767
  }
1524
- if (node.getKind() === SyntaxKind2.NullKeyword) {
1768
+ if (node.getKind() === SyntaxKind3.NullKeyword) {
1525
1769
  return null;
1526
1770
  }
1527
1771
  return;
@@ -1536,10 +1780,10 @@ class HandlerExtractor {
1536
1780
  }
1537
1781
  const caseClauses = switchNode.getClauses();
1538
1782
  for (const clause of caseClauses) {
1539
- if (Node4.isCaseClause(clause)) {
1783
+ if (Node5.isCaseClause(clause)) {
1540
1784
  const caseExpr = clause.getExpression();
1541
1785
  let messageType = null;
1542
- if (Node4.isStringLiteral(caseExpr)) {
1786
+ if (Node5.isStringLiteral(caseExpr)) {
1543
1787
  messageType = caseExpr.getLiteralValue();
1544
1788
  }
1545
1789
  if (messageType) {
@@ -1562,7 +1806,7 @@ class HandlerExtractor {
1562
1806
  const handlers = [];
1563
1807
  try {
1564
1808
  const initializer = varDecl.getInitializer();
1565
- if (!initializer || !Node4.isObjectLiteralExpression(initializer)) {
1809
+ if (!initializer || !Node5.isObjectLiteralExpression(initializer)) {
1566
1810
  return handlers;
1567
1811
  }
1568
1812
  const varName = varDecl.getName().toLowerCase();
@@ -1571,12 +1815,12 @@ class HandlerExtractor {
1571
1815
  }
1572
1816
  const properties = initializer.getProperties();
1573
1817
  for (const prop of properties) {
1574
- if (Node4.isPropertyAssignment(prop)) {
1818
+ if (Node5.isPropertyAssignment(prop)) {
1575
1819
  const nameNode = prop.getNameNode();
1576
1820
  let messageType = null;
1577
- if (Node4.isStringLiteral(nameNode)) {
1821
+ if (Node5.isStringLiteral(nameNode)) {
1578
1822
  messageType = nameNode.getLiteralValue();
1579
- } else if (Node4.isIdentifier(nameNode)) {
1823
+ } else if (Node5.isIdentifier(nameNode)) {
1580
1824
  messageType = nameNode.getText();
1581
1825
  }
1582
1826
  if (messageType) {
@@ -1623,7 +1867,7 @@ class HandlerExtractor {
1623
1867
  }
1624
1868
  }
1625
1869
  const elseStatement = currentIf.getElseStatement();
1626
- if (elseStatement && Node4.isIfStatement(elseStatement)) {
1870
+ if (elseStatement && Node5.isIfStatement(elseStatement)) {
1627
1871
  currentIf = elseStatement;
1628
1872
  } else {
1629
1873
  break;
@@ -1639,12 +1883,12 @@ class HandlerExtractor {
1639
1883
  extractHandlerFromIfClause(ifNode, typeGuards, context, filePath) {
1640
1884
  try {
1641
1885
  const condition = ifNode.getExpression();
1642
- if (!Node4.isCallExpression(condition)) {
1886
+ if (!Node5.isCallExpression(condition)) {
1643
1887
  return null;
1644
1888
  }
1645
1889
  const funcExpr = condition.getExpression();
1646
1890
  let funcName;
1647
- if (Node4.isIdentifier(funcExpr)) {
1891
+ if (Node5.isIdentifier(funcExpr)) {
1648
1892
  funcName = funcExpr.getText();
1649
1893
  }
1650
1894
  if (process.env.POLLY_DEBUG && funcName) {
@@ -1656,7 +1900,7 @@ class HandlerExtractor {
1656
1900
  if (process.env.POLLY_DEBUG) {
1657
1901
  console.log(`[DEBUG] Found in local type guards: ${funcName} → ${messageType}`);
1658
1902
  }
1659
- } else if (Node4.isIdentifier(funcExpr)) {
1903
+ } else if (Node5.isIdentifier(funcExpr)) {
1660
1904
  if (process.env.POLLY_DEBUG) {
1661
1905
  console.log(`[DEBUG] Not found locally, trying import resolution for: ${funcName}`);
1662
1906
  }
@@ -1669,13 +1913,24 @@ class HandlerExtractor {
1669
1913
  return null;
1670
1914
  }
1671
1915
  const line = ifNode.getStartLineNumber();
1916
+ const sourceFile = ifNode.getSourceFile();
1917
+ const handlerName = `${messageType}_handler`;
1918
+ let relationships = undefined;
1919
+ const thenStatement = ifNode.getThenStatement();
1920
+ if (thenStatement) {
1921
+ const detectedRelationships = this.relationshipExtractor.extractFromHandler(thenStatement, sourceFile, handlerName);
1922
+ if (detectedRelationships.length > 0) {
1923
+ relationships = detectedRelationships;
1924
+ }
1925
+ }
1672
1926
  return {
1673
1927
  messageType,
1674
1928
  node: context,
1675
1929
  assignments: [],
1676
1930
  preconditions: [],
1677
1931
  postconditions: [],
1678
- location: { file: filePath, line }
1932
+ location: { file: filePath, line },
1933
+ relationships
1679
1934
  };
1680
1935
  } catch (error) {
1681
1936
  return null;
@@ -1684,20 +1939,20 @@ class HandlerExtractor {
1684
1939
  findTypePredicateFunctions(sourceFile) {
1685
1940
  const typeGuards = new Map;
1686
1941
  sourceFile.forEachDescendant((node) => {
1687
- if (Node4.isFunctionDeclaration(node) || Node4.isFunctionExpression(node) || Node4.isArrowFunction(node)) {
1942
+ if (Node5.isFunctionDeclaration(node) || Node5.isFunctionExpression(node) || Node5.isArrowFunction(node)) {
1688
1943
  const returnTypeNode = node.getReturnTypeNode();
1689
- if (returnTypeNode && Node4.isTypePredicate(returnTypeNode)) {
1944
+ if (returnTypeNode && Node5.isTypePredicate(returnTypeNode)) {
1690
1945
  let functionName;
1691
- if (Node4.isFunctionDeclaration(node)) {
1946
+ if (Node5.isFunctionDeclaration(node)) {
1692
1947
  functionName = node.getName();
1693
- } else if (Node4.isFunctionExpression(node)) {
1948
+ } else if (Node5.isFunctionExpression(node)) {
1694
1949
  const parent = node.getParent();
1695
- if (Node4.isVariableDeclaration(parent)) {
1950
+ if (Node5.isVariableDeclaration(parent)) {
1696
1951
  functionName = parent.getName();
1697
1952
  }
1698
- } else if (Node4.isArrowFunction(node)) {
1953
+ } else if (Node5.isArrowFunction(node)) {
1699
1954
  const parent = node.getParent();
1700
- if (Node4.isVariableDeclaration(parent)) {
1955
+ if (Node5.isVariableDeclaration(parent)) {
1701
1956
  functionName = parent.getName();
1702
1957
  }
1703
1958
  }
@@ -1738,15 +1993,15 @@ class HandlerExtractor {
1738
1993
  return null;
1739
1994
  }
1740
1995
  for (const def of definitions) {
1741
- if (Node4.isFunctionDeclaration(def) || Node4.isFunctionExpression(def) || Node4.isArrowFunction(def)) {
1996
+ if (Node5.isFunctionDeclaration(def) || Node5.isFunctionExpression(def) || Node5.isArrowFunction(def)) {
1742
1997
  const returnTypeNode = def.getReturnTypeNode();
1743
1998
  if (process.env.POLLY_DEBUG) {
1744
1999
  const returnType = def.getReturnType().getText();
1745
2000
  console.log(`[DEBUG] Function ${funcName} return type (resolved): ${returnType}`);
1746
2001
  console.log(`[DEBUG] Has return type node: ${!!returnTypeNode}`);
1747
- console.log(`[DEBUG] Is type predicate node: ${returnTypeNode && Node4.isTypePredicate(returnTypeNode)}`);
2002
+ console.log(`[DEBUG] Is type predicate node: ${returnTypeNode && Node5.isTypePredicate(returnTypeNode)}`);
1748
2003
  }
1749
- if (returnTypeNode && Node4.isTypePredicate(returnTypeNode)) {
2004
+ if (returnTypeNode && Node5.isTypePredicate(returnTypeNode)) {
1750
2005
  const typeNode = returnTypeNode.getTypeNode();
1751
2006
  if (typeNode) {
1752
2007
  const typeName = typeNode.getText();
@@ -2288,10 +2543,33 @@ class StructurizrDSLGenerator {
2288
2543
  if (this.options.groups && this.options.groups.length > 0) {
2289
2544
  parts.push(...this.generateGroupedComponents(componentDefs, this.options.groups));
2290
2545
  } else {
2291
- for (const comp of componentDefs) {
2292
- parts.push(this.generateComponentDefinition(comp, " "));
2546
+ const autoGroups = this.generateAutomaticGroups(componentDefs);
2547
+ if (autoGroups.length > 0) {
2548
+ parts.push(...this.generateGroupedComponents(componentDefs, autoGroups));
2549
+ } else {
2550
+ for (const comp of componentDefs) {
2551
+ parts.push(this.generateComponentDefinition(comp, " "));
2552
+ }
2293
2553
  }
2294
2554
  }
2555
+ const serviceComponents = new Set;
2556
+ for (const handler of contextInfo.handlers) {
2557
+ if (handler.relationships) {
2558
+ for (const rel of handler.relationships) {
2559
+ const targetId = this.toId(rel.to);
2560
+ const isHandler = componentDefs.some((c) => c.id === targetId);
2561
+ if (!isHandler) {
2562
+ serviceComponents.add(rel.to);
2563
+ }
2564
+ }
2565
+ }
2566
+ }
2567
+ for (const serviceId of serviceComponents) {
2568
+ const serviceName = serviceId.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
2569
+ parts.push(` ${this.toId(serviceId)} = component "${serviceName}" "Business logic service" {`);
2570
+ parts.push(` tags "Service" "Auto-detected"`);
2571
+ parts.push(` }`);
2572
+ }
2295
2573
  if (contextInfo.components) {
2296
2574
  for (const comp of contextInfo.components) {
2297
2575
  parts.push(` ${this.toId(comp.name)} = component "${comp.name}" "${this.escape(comp.description || "UI component")}" {`);
@@ -2444,6 +2722,21 @@ class StructurizrDSLGenerator {
2444
2722
  parts.push(` }`);
2445
2723
  }
2446
2724
  }
2725
+ for (const handler of contextInfo.handlers) {
2726
+ if (handler.relationships && handler.relationships.length > 0) {
2727
+ for (const rel of handler.relationships) {
2728
+ const fromId = this.toId(rel.from);
2729
+ const toId = this.toId(rel.to);
2730
+ const description = this.escape(rel.description);
2731
+ parts.push(` ${fromId} -> ${toId} "${description}" {`);
2732
+ if (rel.technology) {
2733
+ parts.push(` technology "${this.escape(rel.technology)}"`);
2734
+ }
2735
+ parts.push(` tags "Auto-detected"`);
2736
+ parts.push(` }`);
2737
+ }
2738
+ }
2739
+ }
2447
2740
  const stateHandlers = [];
2448
2741
  const queryHandlers = [];
2449
2742
  for (const [messageType, handlers] of handlersByType) {
@@ -2568,6 +2861,10 @@ class StructurizrDSLGenerator {
2568
2861
  parts.push(this.generateUserDynamicDiagram(diagram));
2569
2862
  }
2570
2863
  }
2864
+ const autoDiagrams = this.generateAutomaticDynamicDiagrams();
2865
+ if (autoDiagrams.length > 0) {
2866
+ parts.push(...autoDiagrams);
2867
+ }
2571
2868
  const flowsByDomain = new Map;
2572
2869
  for (const flow of this.analysis.messageFlows) {
2573
2870
  const messageType = flow.messageType.toLowerCase();
@@ -2596,6 +2893,111 @@ class StructurizrDSLGenerator {
2596
2893
 
2597
2894
  `);
2598
2895
  }
2896
+ generateAutomaticDynamicDiagrams() {
2897
+ const diagrams = [];
2898
+ const processedHandlers = new Set;
2899
+ const handlersWithRelationships = [];
2900
+ for (const [contextType, contextInfo] of Object.entries(this.analysis.contexts)) {
2901
+ for (const handler of contextInfo.handlers) {
2902
+ if (handler.relationships && handler.relationships.length > 0) {
2903
+ handlersWithRelationships.push({
2904
+ handler,
2905
+ contextType,
2906
+ contextName: this.toId(contextType)
2907
+ });
2908
+ }
2909
+ }
2910
+ }
2911
+ const handlerGroups = new Map;
2912
+ for (const hwc of handlersWithRelationships) {
2913
+ const messageType = hwc.handler.messageType.toLowerCase();
2914
+ let category = "general";
2915
+ if (messageType.includes("auth") || messageType.includes("login") || messageType.includes("logout") || messageType.includes("verify") || messageType.includes("register")) {
2916
+ category = "authentication";
2917
+ } else if (messageType.includes("user")) {
2918
+ category = "user";
2919
+ } else if (messageType.includes("todo")) {
2920
+ category = "todo";
2921
+ } else if (messageType.includes("query") || messageType.includes("get") || messageType.includes("fetch") || messageType.includes("list")) {
2922
+ category = "query";
2923
+ } else if (messageType.includes("command") || messageType.includes("create") || messageType.includes("update") || messageType.includes("delete") || messageType.includes("add") || messageType.includes("remove")) {
2924
+ category = "command";
2925
+ }
2926
+ if (!handlerGroups.has(category)) {
2927
+ handlerGroups.set(category, []);
2928
+ }
2929
+ handlerGroups.get(category).push(hwc);
2930
+ }
2931
+ let diagramCount = 0;
2932
+ const maxDiagrams = 5;
2933
+ const totalHandlers = handlersWithRelationships.length;
2934
+ const categoriesWithHandlers = handlerGroups.size;
2935
+ if (totalHandlers <= 5 && categoriesWithHandlers <= 3) {
2936
+ const allHandlers = Array.from(handlerGroups.values()).flat();
2937
+ const diagram = this.generateHandlerFlowDiagram("general", allHandlers);
2938
+ if (diagram) {
2939
+ diagrams.push(diagram);
2940
+ }
2941
+ } else {
2942
+ for (const [category, handlers] of handlerGroups) {
2943
+ if (diagramCount >= maxDiagrams)
2944
+ break;
2945
+ if (handlers.length === 0)
2946
+ continue;
2947
+ const diagram = this.generateHandlerFlowDiagram(category, handlers);
2948
+ if (diagram) {
2949
+ diagrams.push(diagram);
2950
+ diagramCount++;
2951
+ }
2952
+ }
2953
+ }
2954
+ return diagrams;
2955
+ }
2956
+ generateHandlerFlowDiagram(category, handlers) {
2957
+ const parts = [];
2958
+ const title = this.getCategoryTitle(category);
2959
+ const description = this.getCategoryDescription(category);
2960
+ const scope = handlers[0]?.contextName ? `extension.${handlers[0].contextName}` : "extension";
2961
+ parts.push(` dynamic ${scope} "${title}" "${description}" {`);
2962
+ let stepCount = 0;
2963
+ for (const { handler, contextName } of handlers) {
2964
+ const handlerComponentId = this.toId(`${handler.messageType}_handler`);
2965
+ for (const rel of handler.relationships) {
2966
+ const toComponent = this.toId(rel.to);
2967
+ parts.push(` ${handlerComponentId} -> ${toComponent} "${rel.description}"`);
2968
+ stepCount++;
2969
+ }
2970
+ }
2971
+ if (stepCount === 0) {
2972
+ return null;
2973
+ }
2974
+ parts.push(" autoLayout lr");
2975
+ parts.push(" }");
2976
+ return parts.join(`
2977
+ `);
2978
+ }
2979
+ getCategoryTitle(category) {
2980
+ const titles = {
2981
+ authentication: "Authentication Flow",
2982
+ user: "User Management Flow",
2983
+ todo: "Todo Management Flow",
2984
+ query: "Query Processing Flow",
2985
+ command: "Command Processing Flow",
2986
+ general: "Message Processing Flow"
2987
+ };
2988
+ return titles[category] || `${this.capitalize(category)} Flow`;
2989
+ }
2990
+ getCategoryDescription(category) {
2991
+ const descriptions = {
2992
+ authentication: "Shows authentication request processing and service interactions",
2993
+ user: "Shows user management operations and service dependencies",
2994
+ todo: "Shows todo item operations and service dependencies",
2995
+ query: "Shows read operations flow through query handlers and services",
2996
+ command: "Shows write operations flow through command handlers and services",
2997
+ general: "Shows message processing flow through handlers and services"
2998
+ };
2999
+ return descriptions[category] || "Shows message flow through system components";
3000
+ }
2599
3001
  generateUserDynamicDiagram(diagram) {
2600
3002
  const parts = [];
2601
3003
  parts.push(` dynamic ${diagram.scope || "extension"} "${this.escape(diagram.title)}" "${this.escape(diagram.description || "")}" {`);
@@ -2892,6 +3294,105 @@ class StructurizrDSLGenerator {
2892
3294
  return parts.join(`
2893
3295
  `);
2894
3296
  }
3297
+ generateAutomaticGroups(componentDefs) {
3298
+ const groups = [];
3299
+ const assigned = new Set;
3300
+ const authHandlers = componentDefs.filter((comp) => {
3301
+ const type = comp.messageType.toLowerCase();
3302
+ return type.includes("login") || type.includes("logout") || type.includes("auth") || type.includes("verify") || type.includes("register") || type.includes("signup");
3303
+ });
3304
+ if (authHandlers.length > 0) {
3305
+ groups.push({
3306
+ name: "Authentication",
3307
+ components: authHandlers.map((c) => c.id)
3308
+ });
3309
+ authHandlers.forEach((h) => assigned.add(h.id));
3310
+ }
3311
+ const subscriptionHandlers = componentDefs.filter((comp) => {
3312
+ const type = comp.messageType.toLowerCase();
3313
+ return type.includes("subscribe") || type.includes("unsubscribe");
3314
+ });
3315
+ if (subscriptionHandlers.length > 0) {
3316
+ groups.push({
3317
+ name: "Subscriptions",
3318
+ components: subscriptionHandlers.map((c) => c.id)
3319
+ });
3320
+ subscriptionHandlers.forEach((h) => assigned.add(h.id));
3321
+ }
3322
+ const entityGroups = new Map;
3323
+ for (const comp of componentDefs) {
3324
+ if (assigned.has(comp.id))
3325
+ continue;
3326
+ const type = comp.messageType.toLowerCase();
3327
+ if (type === "connection" || type === "message" || type === "close" || type === "error") {
3328
+ continue;
3329
+ }
3330
+ let entity = null;
3331
+ const underscoreMatch = type.match(/^([a-z]+)_(add|create|update|delete|remove|get|fetch|load|list|query)/);
3332
+ if (underscoreMatch) {
3333
+ entity = underscoreMatch[1];
3334
+ }
3335
+ if (!entity) {
3336
+ const camelMatch = type.match(/(add|create|update|delete|remove|get|fetch|load|list|query)([a-z]+)/i);
3337
+ if (camelMatch) {
3338
+ entity = camelMatch[2].toLowerCase();
3339
+ }
3340
+ }
3341
+ if (!entity && type.match(/^[a-z]+$/)) {
3342
+ entity = type;
3343
+ }
3344
+ if (entity) {
3345
+ if (!entityGroups.has(entity)) {
3346
+ entityGroups.set(entity, []);
3347
+ }
3348
+ entityGroups.get(entity).push(comp.id);
3349
+ assigned.add(comp.id);
3350
+ }
3351
+ }
3352
+ for (const [entity, componentIds] of entityGroups) {
3353
+ if (componentIds.length >= 2) {
3354
+ const entityName = entity.charAt(0).toUpperCase() + entity.slice(1);
3355
+ groups.push({
3356
+ name: `${entityName} Management`,
3357
+ components: componentIds
3358
+ });
3359
+ } else {
3360
+ componentIds.forEach((id) => assigned.delete(id));
3361
+ }
3362
+ }
3363
+ const queryHandlers = componentDefs.filter((comp) => {
3364
+ if (assigned.has(comp.id))
3365
+ return false;
3366
+ const type = comp.messageType.toLowerCase();
3367
+ return type.includes("query") || type.includes("get") || type.includes("fetch") || type.includes("load") || type.includes("list") || type.includes("read");
3368
+ });
3369
+ const commandHandlers = componentDefs.filter((comp) => {
3370
+ if (assigned.has(comp.id))
3371
+ return false;
3372
+ const type = comp.messageType.toLowerCase();
3373
+ return type.includes("command") || type.includes("add") || type.includes("create") || type.includes("update") || type.includes("delete") || type.includes("remove") || type.includes("set") || type.includes("clear");
3374
+ });
3375
+ if (queryHandlers.length >= 2) {
3376
+ groups.push({
3377
+ name: "Query Handlers",
3378
+ components: queryHandlers.map((c) => c.id)
3379
+ });
3380
+ queryHandlers.forEach((h) => assigned.add(h.id));
3381
+ }
3382
+ if (commandHandlers.length >= 2) {
3383
+ groups.push({
3384
+ name: "Command Handlers",
3385
+ components: commandHandlers.map((c) => c.id)
3386
+ });
3387
+ commandHandlers.forEach((h) => assigned.add(h.id));
3388
+ }
3389
+ const groupedCount = assigned.size;
3390
+ const totalCount = componentDefs.length;
3391
+ if (groupedCount >= totalCount * 0.5 || groups.length >= 2) {
3392
+ return groups;
3393
+ }
3394
+ return [];
3395
+ }
2895
3396
  generateGroupedComponents(componentDefs, groups) {
2896
3397
  const parts = [];
2897
3398
  const assignedComponents = new Set;
@@ -3399,4 +3900,4 @@ Stack trace:`, COLORS.gray));
3399
3900
  process.exit(1);
3400
3901
  });
3401
3902
 
3402
- //# debugId=85D3947587AAAF0E64756E2164756E21
3903
+ //# debugId=59C09397E2AB6BFF64756E2164756E21