effect 3.21.4 → 3.22.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.
- package/dist/cjs/Cron.js +96 -52
- package/dist/cjs/Cron.js.map +1 -1
- package/dist/cjs/Graph.js +123 -66
- package/dist/cjs/Graph.js.map +1 -1
- package/dist/cjs/internal/schedule.js +3 -0
- package/dist/cjs/internal/schedule.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Cron.d.ts +1 -0
- package/dist/dts/Cron.d.ts.map +1 -1
- package/dist/dts/Graph.d.ts +22 -3
- package/dist/dts/Graph.d.ts.map +1 -1
- package/dist/esm/Cron.js +96 -52
- package/dist/esm/Cron.js.map +1 -1
- package/dist/esm/Graph.js +116 -61
- package/dist/esm/Graph.js.map +1 -1
- package/dist/esm/internal/schedule.js +3 -0
- package/dist/esm/internal/schedule.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +1 -1
- package/src/Cron.ts +107 -58
- package/src/Graph.ts +149 -77
- package/src/internal/schedule.ts +8 -0
- package/src/internal/version.ts +1 -1
package/src/Graph.ts
CHANGED
|
@@ -872,6 +872,29 @@ export const mapEdges = <N, E, T extends Kind = "directed">(
|
|
|
872
872
|
}
|
|
873
873
|
}
|
|
874
874
|
|
|
875
|
+
/** @internal */
|
|
876
|
+
const rebuildAdjacency = <N, E, T extends Kind = "directed">(
|
|
877
|
+
mutable: MutableGraph<N, E, T>
|
|
878
|
+
): void => {
|
|
879
|
+
mutable.adjacency.clear()
|
|
880
|
+
mutable.reverseAdjacency.clear()
|
|
881
|
+
|
|
882
|
+
for (const nodeIndex of mutable.nodes.keys()) {
|
|
883
|
+
mutable.adjacency.set(nodeIndex, [])
|
|
884
|
+
mutable.reverseAdjacency.set(nodeIndex, [])
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
for (const [edgeIndex, edgeData] of mutable.edges) {
|
|
888
|
+
mutable.adjacency.get(edgeData.source)!.push(edgeIndex)
|
|
889
|
+
mutable.reverseAdjacency.get(edgeData.target)!.push(edgeIndex)
|
|
890
|
+
|
|
891
|
+
if (mutable.type === "undirected") {
|
|
892
|
+
mutable.adjacency.get(edgeData.target)!.push(edgeIndex)
|
|
893
|
+
mutable.reverseAdjacency.get(edgeData.source)!.push(edgeIndex)
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
875
898
|
/**
|
|
876
899
|
* Reverses all edge directions in a mutable graph by swapping source and target nodes.
|
|
877
900
|
*
|
|
@@ -898,6 +921,10 @@ export const mapEdges = <N, E, T extends Kind = "directed">(
|
|
|
898
921
|
export const reverse = <N, E, T extends Kind = "directed">(
|
|
899
922
|
mutable: MutableGraph<N, E, T>
|
|
900
923
|
): void => {
|
|
924
|
+
if (mutable.type === "undirected") {
|
|
925
|
+
return
|
|
926
|
+
}
|
|
927
|
+
|
|
901
928
|
// Reverse all edges by swapping source and target
|
|
902
929
|
for (const [index, edgeData] of mutable.edges) {
|
|
903
930
|
mutable.edges.set(index, {
|
|
@@ -907,22 +934,7 @@ export const reverse = <N, E, T extends Kind = "directed">(
|
|
|
907
934
|
})
|
|
908
935
|
}
|
|
909
936
|
|
|
910
|
-
|
|
911
|
-
mutable.adjacency.clear()
|
|
912
|
-
mutable.reverseAdjacency.clear()
|
|
913
|
-
|
|
914
|
-
// Rebuild adjacency lists with reversed directions
|
|
915
|
-
for (const [edgeIndex, edgeData] of mutable.edges) {
|
|
916
|
-
// Add to forward adjacency (source -> target)
|
|
917
|
-
const sourceEdges = mutable.adjacency.get(edgeData.source) || []
|
|
918
|
-
sourceEdges.push(edgeIndex)
|
|
919
|
-
mutable.adjacency.set(edgeData.source, sourceEdges)
|
|
920
|
-
|
|
921
|
-
// Add to reverse adjacency (target <- source)
|
|
922
|
-
const targetEdges = mutable.reverseAdjacency.get(edgeData.target) || []
|
|
923
|
-
targetEdges.push(edgeIndex)
|
|
924
|
-
mutable.reverseAdjacency.set(edgeData.target, targetEdges)
|
|
925
|
-
}
|
|
937
|
+
rebuildAdjacency(mutable)
|
|
926
938
|
|
|
927
939
|
// Invalidate cycle flag since edge directions changed
|
|
928
940
|
mutable.isAcyclic = Option.none()
|
|
@@ -1452,8 +1464,11 @@ export const hasEdge = <N, E, T extends Kind = "directed">(
|
|
|
1452
1464
|
// Check if any edge in the adjacency list connects to the target
|
|
1453
1465
|
for (const edgeIndex of adjacencyList) {
|
|
1454
1466
|
const edge = graph.edges.get(edgeIndex)
|
|
1455
|
-
if (edge !== undefined
|
|
1456
|
-
|
|
1467
|
+
if (edge !== undefined) {
|
|
1468
|
+
const neighbor = graph.type === "undirected" && edge.target === source ? edge.source : edge.target
|
|
1469
|
+
if (neighbor === target) {
|
|
1470
|
+
return true
|
|
1471
|
+
}
|
|
1457
1472
|
}
|
|
1458
1473
|
}
|
|
1459
1474
|
|
|
@@ -1489,6 +1504,31 @@ export const edgeCount = <N, E, T extends Kind = "directed">(
|
|
|
1489
1504
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>
|
|
1490
1505
|
): number => graph.edges.size
|
|
1491
1506
|
|
|
1507
|
+
const getDirectedNeighbors = <N, E>(
|
|
1508
|
+
graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">,
|
|
1509
|
+
nodeIndex: NodeIndex,
|
|
1510
|
+
direction: Direction
|
|
1511
|
+
): Array<NodeIndex> => {
|
|
1512
|
+
const adjacencyMap = direction === "incoming"
|
|
1513
|
+
? graph.reverseAdjacency
|
|
1514
|
+
: graph.adjacency
|
|
1515
|
+
|
|
1516
|
+
const adjacencyList = adjacencyMap.get(nodeIndex)
|
|
1517
|
+
if (adjacencyList === undefined) {
|
|
1518
|
+
return []
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
const result: Array<NodeIndex> = []
|
|
1522
|
+
for (const edgeIndex of adjacencyList) {
|
|
1523
|
+
const edge = graph.edges.get(edgeIndex)
|
|
1524
|
+
if (edge !== undefined) {
|
|
1525
|
+
result.push(direction === "incoming" ? edge.source : edge.target)
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
return result
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1492
1532
|
/**
|
|
1493
1533
|
* Returns the neighboring nodes (targets of outgoing edges) for a given node.
|
|
1494
1534
|
*
|
|
@@ -1527,24 +1567,47 @@ export const neighbors = <N, E, T extends Kind = "directed">(
|
|
|
1527
1567
|
return getUndirectedNeighbors(graph as any, nodeIndex)
|
|
1528
1568
|
}
|
|
1529
1569
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
return []
|
|
1533
|
-
}
|
|
1570
|
+
return getDirectedNeighbors(graph as Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex, "outgoing")
|
|
1571
|
+
}
|
|
1534
1572
|
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1573
|
+
/**
|
|
1574
|
+
* Returns the outgoing neighbor node indices for a node in a directed graph.
|
|
1575
|
+
*
|
|
1576
|
+
* Throws a `GraphError` when used with an undirected graph.
|
|
1577
|
+
*
|
|
1578
|
+
* @since 3.22.0
|
|
1579
|
+
* @category queries
|
|
1580
|
+
*/
|
|
1581
|
+
export const successors = <N, E>(
|
|
1582
|
+
graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">,
|
|
1583
|
+
nodeIndex: NodeIndex
|
|
1584
|
+
): Array<NodeIndex> => {
|
|
1585
|
+
if ((graph as Graph<N, E, Kind> | MutableGraph<N, E, Kind>).type === "undirected") {
|
|
1586
|
+
throw new GraphError({ message: "Cannot get successors of undirected graph" })
|
|
1541
1587
|
}
|
|
1588
|
+
return getDirectedNeighbors(graph, nodeIndex, "outgoing")
|
|
1589
|
+
}
|
|
1542
1590
|
|
|
1543
|
-
|
|
1591
|
+
/**
|
|
1592
|
+
* Returns the incoming neighbor node indices for a node in a directed graph.
|
|
1593
|
+
*
|
|
1594
|
+
* Throws a `GraphError` when used with an undirected graph.
|
|
1595
|
+
*
|
|
1596
|
+
* @since 3.22.0
|
|
1597
|
+
* @category queries
|
|
1598
|
+
*/
|
|
1599
|
+
export const predecessors = <N, E>(
|
|
1600
|
+
graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">,
|
|
1601
|
+
nodeIndex: NodeIndex
|
|
1602
|
+
): Array<NodeIndex> => {
|
|
1603
|
+
if ((graph as Graph<N, E, Kind> | MutableGraph<N, E, Kind>).type === "undirected") {
|
|
1604
|
+
throw new GraphError({ message: "Cannot get predecessors of undirected graph" })
|
|
1605
|
+
}
|
|
1606
|
+
return getDirectedNeighbors(graph, nodeIndex, "incoming")
|
|
1544
1607
|
}
|
|
1545
1608
|
|
|
1546
1609
|
/**
|
|
1547
|
-
* Get neighbors of a node in a specific direction
|
|
1610
|
+
* Get directed neighbors of a node in a specific direction.
|
|
1548
1611
|
*
|
|
1549
1612
|
* @example
|
|
1550
1613
|
* ```ts
|
|
@@ -1566,36 +1629,19 @@ export const neighbors = <N, E, T extends Kind = "directed">(
|
|
|
1566
1629
|
* const incoming = Graph.neighborsDirected(graph, nodeB, "incoming")
|
|
1567
1630
|
* ```
|
|
1568
1631
|
*
|
|
1632
|
+
* @deprecated Use {@link successors} for outgoing neighbors or {@link predecessors} for incoming neighbors.
|
|
1569
1633
|
* @since 3.18.0
|
|
1570
1634
|
* @category queries
|
|
1571
1635
|
*/
|
|
1572
|
-
export const neighborsDirected = <N, E
|
|
1573
|
-
graph: Graph<N, E,
|
|
1636
|
+
export const neighborsDirected = <N, E>(
|
|
1637
|
+
graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">,
|
|
1574
1638
|
nodeIndex: NodeIndex,
|
|
1575
1639
|
direction: Direction
|
|
1576
1640
|
): Array<NodeIndex> => {
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
: graph.adjacency
|
|
1580
|
-
|
|
1581
|
-
const adjacencyList = adjacencyMap.get(nodeIndex)
|
|
1582
|
-
if (adjacencyList === undefined) {
|
|
1583
|
-
return []
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
const result: Array<NodeIndex> = []
|
|
1587
|
-
for (const edgeIndex of adjacencyList) {
|
|
1588
|
-
const edge = graph.edges.get(edgeIndex)
|
|
1589
|
-
if (edge !== undefined) {
|
|
1590
|
-
// For incoming direction, we want the source node instead of target
|
|
1591
|
-
const neighborNode = direction === "incoming"
|
|
1592
|
-
? edge.source
|
|
1593
|
-
: edge.target
|
|
1594
|
-
result.push(neighborNode)
|
|
1595
|
-
}
|
|
1641
|
+
if ((graph as Graph<N, E, Kind> | MutableGraph<N, E, Kind>).type === "undirected") {
|
|
1642
|
+
throw new GraphError({ message: "Cannot get directed neighbors of undirected graph" })
|
|
1596
1643
|
}
|
|
1597
|
-
|
|
1598
|
-
return result
|
|
1644
|
+
return getDirectedNeighbors(graph, nodeIndex, direction)
|
|
1599
1645
|
}
|
|
1600
1646
|
|
|
1601
1647
|
// =============================================================================
|
|
@@ -1990,7 +2036,11 @@ export const isAcyclic = <N, E, T extends Kind = "directed">(
|
|
|
1990
2036
|
recursionStack.add(node)
|
|
1991
2037
|
|
|
1992
2038
|
// Get neighbors for this node
|
|
1993
|
-
const nodeNeighbors =
|
|
2039
|
+
const nodeNeighbors = getDirectedNeighbors(
|
|
2040
|
+
graph as Graph<N, E, "directed"> | MutableGraph<N, E, "directed">,
|
|
2041
|
+
node,
|
|
2042
|
+
"outgoing"
|
|
2043
|
+
)
|
|
1994
2044
|
stack[stack.length - 1] = [node, nodeNeighbors, 0, false]
|
|
1995
2045
|
continue
|
|
1996
2046
|
}
|
|
@@ -2141,7 +2191,7 @@ const getTraversalNeighbors = <N, E, T extends Kind>(
|
|
|
2141
2191
|
): Array<NodeIndex> =>
|
|
2142
2192
|
graph.type === "undirected"
|
|
2143
2193
|
? getUndirectedNeighbors(graph as any, nodeIndex)
|
|
2144
|
-
:
|
|
2194
|
+
: getDirectedNeighbors(graph as Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex, direction)
|
|
2145
2195
|
|
|
2146
2196
|
const getTraversableNeighbor = <N, E, T extends Kind>(
|
|
2147
2197
|
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
@@ -2231,9 +2281,13 @@ export const connectedComponents = <N, E>(
|
|
|
2231
2281
|
* @since 3.18.0
|
|
2232
2282
|
* @category algorithms
|
|
2233
2283
|
*/
|
|
2234
|
-
export const stronglyConnectedComponents = <N, E
|
|
2235
|
-
graph: Graph<N, E,
|
|
2284
|
+
export const stronglyConnectedComponents = <N, E>(
|
|
2285
|
+
graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">
|
|
2236
2286
|
): Array<Array<NodeIndex>> => {
|
|
2287
|
+
if ((graph as Graph<N, E, Kind> | MutableGraph<N, E, Kind>).type === "undirected") {
|
|
2288
|
+
throw new GraphError({ message: "Cannot find strongly connected components of undirected graph" })
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2237
2291
|
const visited = new Set<NodeIndex>()
|
|
2238
2292
|
const finishOrder: Array<NodeIndex> = []
|
|
2239
2293
|
// Iterate directly over node keys
|
|
@@ -2259,7 +2313,7 @@ export const stronglyConnectedComponents = <N, E, T extends Kind = "directed">(
|
|
|
2259
2313
|
}
|
|
2260
2314
|
|
|
2261
2315
|
visited.add(node)
|
|
2262
|
-
const nodeNeighborsList =
|
|
2316
|
+
const nodeNeighborsList = getDirectedNeighbors(graph, node, "outgoing")
|
|
2263
2317
|
stack[stack.length - 1] = [node, nodeNeighborsList, 0, false]
|
|
2264
2318
|
continue
|
|
2265
2319
|
}
|
|
@@ -2377,6 +2431,22 @@ export interface BellmanFordConfig<E> {
|
|
|
2377
2431
|
cost: (edgeData: E) => number
|
|
2378
2432
|
}
|
|
2379
2433
|
|
|
2434
|
+
const validateNonNegativeEdgeWeights = <N, E, T extends Kind = "directed">(
|
|
2435
|
+
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
|
|
2436
|
+
cost: (edgeData: E) => number,
|
|
2437
|
+
algorithm: string
|
|
2438
|
+
): Map<EdgeIndex, number> => {
|
|
2439
|
+
const edgeWeights = new Map<EdgeIndex, number>()
|
|
2440
|
+
for (const [edgeIndex, edgeData] of graph.edges) {
|
|
2441
|
+
const weight = cost(edgeData.data)
|
|
2442
|
+
if (weight < 0 || Number.isNaN(weight)) {
|
|
2443
|
+
throw new GraphError({ message: `${algorithm} requires non-negative edge weights` })
|
|
2444
|
+
}
|
|
2445
|
+
edgeWeights.set(edgeIndex, weight)
|
|
2446
|
+
}
|
|
2447
|
+
return edgeWeights
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2380
2450
|
/**
|
|
2381
2451
|
* Find the shortest path between two nodes using Dijkstra's algorithm.
|
|
2382
2452
|
*
|
|
@@ -2419,6 +2489,8 @@ export const dijkstra = <N, E, T extends Kind = "directed">(
|
|
|
2419
2489
|
throw missingNode(target)
|
|
2420
2490
|
}
|
|
2421
2491
|
|
|
2492
|
+
const edgeWeights = validateNonNegativeEdgeWeights(graph, cost, "Dijkstra's algorithm")
|
|
2493
|
+
|
|
2422
2494
|
// Early return if source equals target
|
|
2423
2495
|
if (source === target) {
|
|
2424
2496
|
return Option.some({
|
|
@@ -2479,12 +2551,7 @@ export const dijkstra = <N, E, T extends Kind = "directed">(
|
|
|
2479
2551
|
const edge = graph.edges.get(edgeIndex)
|
|
2480
2552
|
if (edge !== undefined) {
|
|
2481
2553
|
const neighbor = getTraversableNeighbor(graph, currentNode, edge)
|
|
2482
|
-
const weight =
|
|
2483
|
-
|
|
2484
|
-
// Validate non-negative weights
|
|
2485
|
-
if (weight < 0) {
|
|
2486
|
-
throw new Error(`Dijkstra's algorithm requires non-negative edge weights, found ${weight}`)
|
|
2487
|
-
}
|
|
2554
|
+
const weight = edgeWeights.get(edgeIndex)!
|
|
2488
2555
|
|
|
2489
2556
|
const newDistance = currentDistance + weight
|
|
2490
2557
|
const neighborDistance = distances.get(neighbor)!
|
|
@@ -2738,6 +2805,8 @@ export const astar = <N, E, T extends Kind = "directed">(
|
|
|
2738
2805
|
throw missingNode(target)
|
|
2739
2806
|
}
|
|
2740
2807
|
|
|
2808
|
+
const edgeWeights = validateNonNegativeEdgeWeights(graph, cost, "A* algorithm")
|
|
2809
|
+
|
|
2741
2810
|
// Early return if source equals target
|
|
2742
2811
|
if (source === target) {
|
|
2743
2812
|
return Option.some({
|
|
@@ -2813,12 +2882,7 @@ export const astar = <N, E, T extends Kind = "directed">(
|
|
|
2813
2882
|
const edge = graph.edges.get(edgeIndex)
|
|
2814
2883
|
if (edge !== undefined) {
|
|
2815
2884
|
const neighbor = getTraversableNeighbor(graph, currentNode, edge)
|
|
2816
|
-
const weight =
|
|
2817
|
-
|
|
2818
|
-
// Validate non-negative weights
|
|
2819
|
-
if (weight < 0) {
|
|
2820
|
-
throw new Error(`A* algorithm requires non-negative edge weights, found ${weight}`)
|
|
2821
|
-
}
|
|
2885
|
+
const weight = edgeWeights.get(edgeIndex)!
|
|
2822
2886
|
|
|
2823
2887
|
const tentativeGScore = currentGScore + weight
|
|
2824
2888
|
const neighborGScore = gScore.get(neighbor)!
|
|
@@ -3497,6 +3561,7 @@ export const topo = <N, E, T extends Kind = "directed">(
|
|
|
3497
3561
|
[Symbol.iterator]: () => {
|
|
3498
3562
|
const inDegree = new Map<NodeIndex, number>()
|
|
3499
3563
|
const remaining = new Set<NodeIndex>()
|
|
3564
|
+
const initialSet = new Set(initials)
|
|
3500
3565
|
const queue = [...initials]
|
|
3501
3566
|
|
|
3502
3567
|
// Initialize in-degree counts
|
|
@@ -3511,12 +3576,15 @@ export const topo = <N, E, T extends Kind = "directed">(
|
|
|
3511
3576
|
inDegree.set(edgeData.target, currentInDegree + 1)
|
|
3512
3577
|
}
|
|
3513
3578
|
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3579
|
+
for (const nodeIndex of initials) {
|
|
3580
|
+
if (inDegree.get(nodeIndex)! !== 0) {
|
|
3581
|
+
throw new GraphError({ message: `Initial node ${nodeIndex} has incoming edges` })
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
for (const [nodeIndex, degree] of inDegree) {
|
|
3586
|
+
if (degree === 0 && !initialSet.has(nodeIndex)) {
|
|
3587
|
+
queue.push(nodeIndex)
|
|
3520
3588
|
}
|
|
3521
3589
|
}
|
|
3522
3590
|
|
|
@@ -3528,7 +3596,11 @@ export const topo = <N, E, T extends Kind = "directed">(
|
|
|
3528
3596
|
remaining.delete(current)
|
|
3529
3597
|
|
|
3530
3598
|
// Process outgoing edges, reducing in-degree of targets
|
|
3531
|
-
const neighbors =
|
|
3599
|
+
const neighbors = getDirectedNeighbors(
|
|
3600
|
+
graph as Graph<N, E, "directed"> | MutableGraph<N, E, "directed">,
|
|
3601
|
+
current,
|
|
3602
|
+
"outgoing"
|
|
3603
|
+
)
|
|
3532
3604
|
for (const neighbor of neighbors) {
|
|
3533
3605
|
if (remaining.has(neighbor)) {
|
|
3534
3606
|
const currentInDegree = inDegree.get(neighbor) || 0
|
package/src/internal/schedule.ts
CHANGED
|
@@ -482,6 +482,14 @@ export const cron: {
|
|
|
482
482
|
return makeWithState<[boolean, [number, number, number]], unknown, [number, number]>(
|
|
483
483
|
[true, [Number.MIN_SAFE_INTEGER, 0, 0]],
|
|
484
484
|
(now, _, [initial, previous]) => {
|
|
485
|
+
if (now === Number.POSITIVE_INFINITY) {
|
|
486
|
+
return core.succeed([
|
|
487
|
+
[false, previous],
|
|
488
|
+
[previous[1], previous[2]],
|
|
489
|
+
ScheduleDecision.done
|
|
490
|
+
])
|
|
491
|
+
}
|
|
492
|
+
|
|
485
493
|
if (now < previous[0]) {
|
|
486
494
|
return core.succeed([
|
|
487
495
|
[false, previous],
|
package/src/internal/version.ts
CHANGED