xcode-graph 0.1.0 → 0.2.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.
@@ -8,6 +8,8 @@
8
8
  */
9
9
  /**
10
10
  * Node role enum - determines positioning strategy within a cluster
11
+ *
12
+ * @public
11
13
  */
12
14
  var NodeRole;
13
15
  (function (NodeRole) {
@@ -20,6 +22,8 @@ var NodeRole;
20
22
  })(NodeRole || (NodeRole = {}));
21
23
  /**
22
24
  * Cluster type enum - distinguishes local projects from packages
25
+ *
26
+ * @public
23
27
  */
24
28
  var ClusterType;
25
29
  (function (ClusterType) {
@@ -28,6 +32,8 @@ var ClusterType;
28
32
  })(ClusterType || (ClusterType = {}));
29
33
  /**
30
34
  * ELK Hierarchy Handling strategy
35
+ *
36
+ * @public
31
37
  */
32
38
  var ElkHierarchyHandling;
33
39
  (function (ElkHierarchyHandling) {
@@ -35,12 +41,20 @@ var ElkHierarchyHandling;
35
41
  ElkHierarchyHandling["IncludeChildren"] = "INCLUDE_CHILDREN";
36
42
  ElkHierarchyHandling["SeparateChildren"] = "SEPARATE_CHILDREN";
37
43
  })(ElkHierarchyHandling || (ElkHierarchyHandling = {}));
38
- /** All node role values for iteration */
44
+ /**
45
+ * All node role values for iteration
46
+ *
47
+ * @public
48
+ */
39
49
  Object.values(NodeRole);
40
- /** All cluster type values for iteration */
50
+ /**
51
+ * All cluster type values for iteration
52
+ *
53
+ * @public
54
+ */
41
55
  Object.values(ClusterType);
42
56
 
43
- function center(x, y) {
57
+ function forceCenter(x, y) {
44
58
  var nodes, strength = 1;
45
59
 
46
60
  if (x == null) x = 0;
@@ -1144,6 +1158,12 @@ function forceY(y) {
1144
1158
  return force;
1145
1159
  }
1146
1160
 
1161
+ /** Node count above which extra congestion padding is applied */
1162
+ const CONGESTION_THRESHOLD = 15;
1163
+ /** Per-node padding multiplier for clusters exceeding CONGESTION_THRESHOLD */
1164
+ const CONGESTION_FACTOR = 2.0;
1165
+ /** Base padding added to every cluster radius (pixels) */
1166
+ const BASE_MICRO_PADDING = 50;
1147
1167
  /**
1148
1168
  * Role order for dynamic band calculation (center to outside)
1149
1169
  */
@@ -1156,7 +1176,13 @@ const ROLE_ORDER = [
1156
1176
  NodeRole.Tool,
1157
1177
  ];
1158
1178
  /**
1159
- * Compute micro-layout for a single cluster using "Solar System" physics
1179
+ * Compute micro-layout for a single cluster using "Solar System" physics.
1180
+ * Nodes are placed in concentric bands based on their role, with anchors at the center.
1181
+ * Uses D3 force simulation with orbit, collision, center gravity, and charge forces.
1182
+ *
1183
+ * @param cluster - The cluster to compute interior layout for
1184
+ * @param config - Layout configuration with force parameters
1185
+ * @returns Micro layout result with cluster dimensions and relative node positions
1160
1186
  */
1161
1187
  function computeClusterInterior(cluster, config) {
1162
1188
  const nodes = cluster.nodes;
@@ -1199,8 +1225,8 @@ function computeClusterInterior(cluster, config) {
1199
1225
  const nodeSpace = config.nodeRadius * 2 + config.clusterNodeSpacing;
1200
1226
  const baseR = n ** 0.6 * nodeSpace * spacingFactor;
1201
1227
  // Add extra padding for large clusters
1202
- const congestionPadding = Math.max(0, n - 15) * 2.0;
1203
- const padding = 50 + congestionPadding;
1228
+ const congestionPadding = Math.max(0, n - CONGESTION_THRESHOLD) * CONGESTION_FACTOR;
1229
+ const padding = BASE_MICRO_PADDING + congestionPadding;
1204
1230
  const radius = Math.max(config.minClusterSize / 2, baseR + padding);
1205
1231
  const size = radius * 2;
1206
1232
  // 3. Initialize Simulation Nodes
@@ -1247,7 +1273,7 @@ function computeClusterInterior(cluster, config) {
1247
1273
  }
1248
1274
  })
1249
1275
  // C. Center Gravity (keep things coherent but loose)
1250
- .force('center', center(0, 0).strength(0.02))
1276
+ .force('center', forceCenter(0, 0).strength(0.02))
1251
1277
  // D. Many Body (stronger repulsion to use available space)
1252
1278
  .force('charge', forceManyBody().strength(config.nodeCharge));
1253
1279
  // 5. Run Simulation
@@ -1283,8 +1309,12 @@ function computeClusterInterior(cluster, config) {
1283
1309
 
1284
1310
  /**
1285
1311
  * Apply a gentle force-directed "massage" to nodes within a cluster.
1286
- * This runs after the main "Solar System" layout to improve spacing
1287
- * and resolve any local congestions while preserving the overall structure.
1312
+ * Runs after the main "Solar System" layout to improve spacing and resolve
1313
+ * local congestions while preserving the overall band structure.
1314
+ *
1315
+ * @param micro - Micro layout result from `computeClusterInterior`
1316
+ * @param config - Layout configuration with collision and charge parameters
1317
+ * @returns Updated micro layout with refined node positions and possibly expanded dimensions
1288
1318
  */
1289
1319
  function applyNodeMassage(micro, config) {
1290
1320
  const nodes = Array.from(micro.relativePositions.values()).map((pos) => ({
@@ -1319,11 +1349,14 @@ function applyNodeMassage(micro, config) {
1319
1349
  let minX = Infinity, maxX = -Infinity;
1320
1350
  let minY = Infinity, maxY = -Infinity;
1321
1351
  for (const node of nodes) {
1322
- newPositions.set(node.id, {
1323
- ...micro.relativePositions.get(node.id),
1324
- x: node.x,
1325
- y: node.y,
1326
- });
1352
+ const existingPos = micro.relativePositions.get(node.id);
1353
+ if (existingPos) {
1354
+ newPositions.set(node.id, {
1355
+ ...existingPos,
1356
+ x: node.x,
1357
+ y: node.y,
1358
+ });
1359
+ }
1327
1360
  if (node.x < minX)
1328
1361
  minX = node.x;
1329
1362
  if (node.x > maxX)
@@ -1348,355 +1381,6 @@ function applyNodeMassage(micro, config) {
1348
1381
  };
1349
1382
  }
1350
1383
 
1351
- /**
1352
- * @license
1353
- * Copyright 2019 Google LLC
1354
- * SPDX-License-Identifier: Apache-2.0
1355
- */
1356
- const proxyMarker = Symbol("Comlink.proxy");
1357
- const createEndpoint = Symbol("Comlink.endpoint");
1358
- const releaseProxy = Symbol("Comlink.releaseProxy");
1359
- const finalizer = Symbol("Comlink.finalizer");
1360
- const throwMarker = Symbol("Comlink.thrown");
1361
- const isObject = (val) => (typeof val === "object" && val !== null) || typeof val === "function";
1362
- /**
1363
- * Internal transfer handle to handle objects marked to proxy.
1364
- */
1365
- const proxyTransferHandler = {
1366
- canHandle: (val) => isObject(val) && val[proxyMarker],
1367
- serialize(obj) {
1368
- const { port1, port2 } = new MessageChannel();
1369
- expose(obj, port1);
1370
- return [port2, [port2]];
1371
- },
1372
- deserialize(port) {
1373
- port.start();
1374
- return wrap(port);
1375
- },
1376
- };
1377
- /**
1378
- * Internal transfer handler to handle thrown exceptions.
1379
- */
1380
- const throwTransferHandler = {
1381
- canHandle: (value) => isObject(value) && throwMarker in value,
1382
- serialize({ value }) {
1383
- let serialized;
1384
- if (value instanceof Error) {
1385
- serialized = {
1386
- isError: true,
1387
- value: {
1388
- message: value.message,
1389
- name: value.name,
1390
- stack: value.stack,
1391
- },
1392
- };
1393
- }
1394
- else {
1395
- serialized = { isError: false, value };
1396
- }
1397
- return [serialized, []];
1398
- },
1399
- deserialize(serialized) {
1400
- if (serialized.isError) {
1401
- throw Object.assign(new Error(serialized.value.message), serialized.value);
1402
- }
1403
- throw serialized.value;
1404
- },
1405
- };
1406
- /**
1407
- * Allows customizing the serialization of certain values.
1408
- */
1409
- const transferHandlers = new Map([
1410
- ["proxy", proxyTransferHandler],
1411
- ["throw", throwTransferHandler],
1412
- ]);
1413
- function isAllowedOrigin(allowedOrigins, origin) {
1414
- for (const allowedOrigin of allowedOrigins) {
1415
- if (origin === allowedOrigin || allowedOrigin === "*") {
1416
- return true;
1417
- }
1418
- if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {
1419
- return true;
1420
- }
1421
- }
1422
- return false;
1423
- }
1424
- function expose(obj, ep = globalThis, allowedOrigins = ["*"]) {
1425
- ep.addEventListener("message", function callback(ev) {
1426
- if (!ev || !ev.data) {
1427
- return;
1428
- }
1429
- if (!isAllowedOrigin(allowedOrigins, ev.origin)) {
1430
- console.warn(`Invalid origin '${ev.origin}' for comlink proxy`);
1431
- return;
1432
- }
1433
- const { id, type, path } = Object.assign({ path: [] }, ev.data);
1434
- const argumentList = (ev.data.argumentList || []).map(fromWireValue);
1435
- let returnValue;
1436
- try {
1437
- const parent = path.slice(0, -1).reduce((obj, prop) => obj[prop], obj);
1438
- const rawValue = path.reduce((obj, prop) => obj[prop], obj);
1439
- switch (type) {
1440
- case "GET" /* MessageType.GET */:
1441
- {
1442
- returnValue = rawValue;
1443
- }
1444
- break;
1445
- case "SET" /* MessageType.SET */:
1446
- {
1447
- parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
1448
- returnValue = true;
1449
- }
1450
- break;
1451
- case "APPLY" /* MessageType.APPLY */:
1452
- {
1453
- returnValue = rawValue.apply(parent, argumentList);
1454
- }
1455
- break;
1456
- case "CONSTRUCT" /* MessageType.CONSTRUCT */:
1457
- {
1458
- const value = new rawValue(...argumentList);
1459
- returnValue = proxy(value);
1460
- }
1461
- break;
1462
- case "ENDPOINT" /* MessageType.ENDPOINT */:
1463
- {
1464
- const { port1, port2 } = new MessageChannel();
1465
- expose(obj, port2);
1466
- returnValue = transfer(port1, [port1]);
1467
- }
1468
- break;
1469
- case "RELEASE" /* MessageType.RELEASE */:
1470
- {
1471
- returnValue = undefined;
1472
- }
1473
- break;
1474
- default:
1475
- return;
1476
- }
1477
- }
1478
- catch (value) {
1479
- returnValue = { value, [throwMarker]: 0 };
1480
- }
1481
- Promise.resolve(returnValue)
1482
- .catch((value) => {
1483
- return { value, [throwMarker]: 0 };
1484
- })
1485
- .then((returnValue) => {
1486
- const [wireValue, transferables] = toWireValue(returnValue);
1487
- ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
1488
- if (type === "RELEASE" /* MessageType.RELEASE */) {
1489
- // detach and deactive after sending release response above.
1490
- ep.removeEventListener("message", callback);
1491
- closeEndPoint(ep);
1492
- if (finalizer in obj && typeof obj[finalizer] === "function") {
1493
- obj[finalizer]();
1494
- }
1495
- }
1496
- })
1497
- .catch((error) => {
1498
- // Send Serialization Error To Caller
1499
- const [wireValue, transferables] = toWireValue({
1500
- value: new TypeError("Unserializable return value"),
1501
- [throwMarker]: 0,
1502
- });
1503
- ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
1504
- });
1505
- });
1506
- if (ep.start) {
1507
- ep.start();
1508
- }
1509
- }
1510
- function isMessagePort(endpoint) {
1511
- return endpoint.constructor.name === "MessagePort";
1512
- }
1513
- function closeEndPoint(endpoint) {
1514
- if (isMessagePort(endpoint))
1515
- endpoint.close();
1516
- }
1517
- function wrap(ep, target) {
1518
- const pendingListeners = new Map();
1519
- ep.addEventListener("message", function handleMessage(ev) {
1520
- const { data } = ev;
1521
- if (!data || !data.id) {
1522
- return;
1523
- }
1524
- const resolver = pendingListeners.get(data.id);
1525
- if (!resolver) {
1526
- return;
1527
- }
1528
- try {
1529
- resolver(data);
1530
- }
1531
- finally {
1532
- pendingListeners.delete(data.id);
1533
- }
1534
- });
1535
- return createProxy(ep, pendingListeners, [], target);
1536
- }
1537
- function throwIfProxyReleased(isReleased) {
1538
- if (isReleased) {
1539
- throw new Error("Proxy has been released and is not useable");
1540
- }
1541
- }
1542
- function releaseEndpoint(ep) {
1543
- return requestResponseMessage(ep, new Map(), {
1544
- type: "RELEASE" /* MessageType.RELEASE */,
1545
- }).then(() => {
1546
- closeEndPoint(ep);
1547
- });
1548
- }
1549
- const proxyCounter = new WeakMap();
1550
- const proxyFinalizers = "FinalizationRegistry" in globalThis &&
1551
- new FinalizationRegistry((ep) => {
1552
- const newCount = (proxyCounter.get(ep) || 0) - 1;
1553
- proxyCounter.set(ep, newCount);
1554
- if (newCount === 0) {
1555
- releaseEndpoint(ep);
1556
- }
1557
- });
1558
- function registerProxy(proxy, ep) {
1559
- const newCount = (proxyCounter.get(ep) || 0) + 1;
1560
- proxyCounter.set(ep, newCount);
1561
- if (proxyFinalizers) {
1562
- proxyFinalizers.register(proxy, ep, proxy);
1563
- }
1564
- }
1565
- function unregisterProxy(proxy) {
1566
- if (proxyFinalizers) {
1567
- proxyFinalizers.unregister(proxy);
1568
- }
1569
- }
1570
- function createProxy(ep, pendingListeners, path = [], target = function () { }) {
1571
- let isProxyReleased = false;
1572
- const proxy = new Proxy(target, {
1573
- get(_target, prop) {
1574
- throwIfProxyReleased(isProxyReleased);
1575
- if (prop === releaseProxy) {
1576
- return () => {
1577
- unregisterProxy(proxy);
1578
- releaseEndpoint(ep);
1579
- pendingListeners.clear();
1580
- isProxyReleased = true;
1581
- };
1582
- }
1583
- if (prop === "then") {
1584
- if (path.length === 0) {
1585
- return { then: () => proxy };
1586
- }
1587
- const r = requestResponseMessage(ep, pendingListeners, {
1588
- type: "GET" /* MessageType.GET */,
1589
- path: path.map((p) => p.toString()),
1590
- }).then(fromWireValue);
1591
- return r.then.bind(r);
1592
- }
1593
- return createProxy(ep, pendingListeners, [...path, prop]);
1594
- },
1595
- set(_target, prop, rawValue) {
1596
- throwIfProxyReleased(isProxyReleased);
1597
- // FIXME: ES6 Proxy Handler `set` methods are supposed to return a
1598
- // boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯
1599
- const [value, transferables] = toWireValue(rawValue);
1600
- return requestResponseMessage(ep, pendingListeners, {
1601
- type: "SET" /* MessageType.SET */,
1602
- path: [...path, prop].map((p) => p.toString()),
1603
- value,
1604
- }, transferables).then(fromWireValue);
1605
- },
1606
- apply(_target, _thisArg, rawArgumentList) {
1607
- throwIfProxyReleased(isProxyReleased);
1608
- const last = path[path.length - 1];
1609
- if (last === createEndpoint) {
1610
- return requestResponseMessage(ep, pendingListeners, {
1611
- type: "ENDPOINT" /* MessageType.ENDPOINT */,
1612
- }).then(fromWireValue);
1613
- }
1614
- // We just pretend that `bind()` didn’t happen.
1615
- if (last === "bind") {
1616
- return createProxy(ep, pendingListeners, path.slice(0, -1));
1617
- }
1618
- const [argumentList, transferables] = processArguments(rawArgumentList);
1619
- return requestResponseMessage(ep, pendingListeners, {
1620
- type: "APPLY" /* MessageType.APPLY */,
1621
- path: path.map((p) => p.toString()),
1622
- argumentList,
1623
- }, transferables).then(fromWireValue);
1624
- },
1625
- construct(_target, rawArgumentList) {
1626
- throwIfProxyReleased(isProxyReleased);
1627
- const [argumentList, transferables] = processArguments(rawArgumentList);
1628
- return requestResponseMessage(ep, pendingListeners, {
1629
- type: "CONSTRUCT" /* MessageType.CONSTRUCT */,
1630
- path: path.map((p) => p.toString()),
1631
- argumentList,
1632
- }, transferables).then(fromWireValue);
1633
- },
1634
- });
1635
- registerProxy(proxy, ep);
1636
- return proxy;
1637
- }
1638
- function myFlat(arr) {
1639
- return Array.prototype.concat.apply([], arr);
1640
- }
1641
- function processArguments(argumentList) {
1642
- const processed = argumentList.map(toWireValue);
1643
- return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
1644
- }
1645
- const transferCache = new WeakMap();
1646
- function transfer(obj, transfers) {
1647
- transferCache.set(obj, transfers);
1648
- return obj;
1649
- }
1650
- function proxy(obj) {
1651
- return Object.assign(obj, { [proxyMarker]: true });
1652
- }
1653
- function toWireValue(value) {
1654
- for (const [name, handler] of transferHandlers) {
1655
- if (handler.canHandle(value)) {
1656
- const [serializedValue, transferables] = handler.serialize(value);
1657
- return [
1658
- {
1659
- type: "HANDLER" /* WireValueType.HANDLER */,
1660
- name,
1661
- value: serializedValue,
1662
- },
1663
- transferables,
1664
- ];
1665
- }
1666
- }
1667
- return [
1668
- {
1669
- type: "RAW" /* WireValueType.RAW */,
1670
- value,
1671
- },
1672
- transferCache.get(value) || [],
1673
- ];
1674
- }
1675
- function fromWireValue(value) {
1676
- switch (value.type) {
1677
- case "HANDLER" /* WireValueType.HANDLER */:
1678
- return transferHandlers.get(value.name).deserialize(value.value);
1679
- case "RAW" /* WireValueType.RAW */:
1680
- return value.value;
1681
- }
1682
- }
1683
- function requestResponseMessage(ep, pendingListeners, msg, transfers) {
1684
- return new Promise((resolve) => {
1685
- const id = generateUUID();
1686
- pendingListeners.set(id, resolve);
1687
- if (ep.start) {
1688
- ep.start();
1689
- }
1690
- ep.postMessage(Object.assign({ id }, msg), transfers);
1691
- });
1692
- }
1693
- function generateUUID() {
1694
- return new Array(4)
1695
- .fill(0)
1696
- .map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16))
1697
- .join("-");
1698
- }
1699
-
1700
1384
  /**
1701
1385
  * Micro-Layout Web Worker
1702
1386
  *
@@ -1704,6 +1388,11 @@ function generateUUID() {
1704
1388
  * off the main thread. Used by parallel-micro.ts to parallelize
1705
1389
  * micro-layout computation across multiple workers.
1706
1390
  */
1391
+ /**
1392
+ * Converts a serialized cluster (with arrays) back to the internal Cluster type (with Maps).
1393
+ * @param sc - Serialized cluster received from the main thread
1394
+ * @returns Deserialized Cluster instance
1395
+ */
1707
1396
  function deserializeCluster(sc) {
1708
1397
  return {
1709
1398
  ...sc,
@@ -1723,4 +1412,9 @@ const workerApi = {
1723
1412
  };
1724
1413
  },
1725
1414
  };
1726
- expose(workerApi);
1415
+ /* v8 ignore start */
1416
+ self.onmessage = (e) => {
1417
+ const result = workerApi.computeMicro(e.data.cluster, e.data.config);
1418
+ self.postMessage(result);
1419
+ };
1420
+ /* v8 ignore stop */