graphwise 1.7.0 → 1.8.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/README.md +81 -30
- package/dist/async/index.cjs +243 -0
- package/dist/async/index.cjs.map +1 -0
- package/dist/async/index.js +230 -0
- package/dist/async/index.js.map +1 -0
- package/dist/expansion/dfs-priority.d.ts +11 -0
- package/dist/expansion/dfs-priority.d.ts.map +1 -1
- package/dist/expansion/dome.d.ts +20 -0
- package/dist/expansion/dome.d.ts.map +1 -1
- package/dist/expansion/edge.d.ts +18 -0
- package/dist/expansion/edge.d.ts.map +1 -1
- package/dist/expansion/flux.d.ts +16 -0
- package/dist/expansion/flux.d.ts.map +1 -1
- package/dist/expansion/frontier-balanced.d.ts +11 -0
- package/dist/expansion/frontier-balanced.d.ts.map +1 -1
- package/dist/expansion/fuse.d.ts +16 -0
- package/dist/expansion/fuse.d.ts.map +1 -1
- package/dist/expansion/hae.d.ts +16 -0
- package/dist/expansion/hae.d.ts.map +1 -1
- package/dist/expansion/lace.d.ts +16 -0
- package/dist/expansion/lace.d.ts.map +1 -1
- package/dist/expansion/maze.d.ts +17 -0
- package/dist/expansion/maze.d.ts.map +1 -1
- package/dist/expansion/pipe.d.ts +16 -0
- package/dist/expansion/pipe.d.ts.map +1 -1
- package/dist/expansion/random-priority.d.ts +18 -0
- package/dist/expansion/random-priority.d.ts.map +1 -1
- package/dist/expansion/reach.d.ts +17 -0
- package/dist/expansion/reach.d.ts.map +1 -1
- package/dist/expansion/sage.d.ts +15 -0
- package/dist/expansion/sage.d.ts.map +1 -1
- package/dist/expansion/sift.d.ts +16 -0
- package/dist/expansion/sift.d.ts.map +1 -1
- package/dist/expansion/standard-bfs.d.ts +11 -0
- package/dist/expansion/standard-bfs.d.ts.map +1 -1
- package/dist/expansion/tide.d.ts +16 -0
- package/dist/expansion/tide.d.ts.map +1 -1
- package/dist/expansion/warp.d.ts +16 -0
- package/dist/expansion/warp.d.ts.map +1 -1
- package/dist/index/index.cjs +842 -215
- package/dist/index/index.cjs.map +1 -1
- package/dist/index/index.js +793 -211
- package/dist/index/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/ranking/mi/adamic-adar.d.ts +8 -0
- package/dist/ranking/mi/adamic-adar.d.ts.map +1 -1
- package/dist/ranking/mi/adaptive.d.ts +8 -0
- package/dist/ranking/mi/adaptive.d.ts.map +1 -1
- package/dist/ranking/mi/cosine.d.ts +7 -0
- package/dist/ranking/mi/cosine.d.ts.map +1 -1
- package/dist/ranking/mi/etch.d.ts +8 -0
- package/dist/ranking/mi/etch.d.ts.map +1 -1
- package/dist/ranking/mi/hub-promoted.d.ts +7 -0
- package/dist/ranking/mi/hub-promoted.d.ts.map +1 -1
- package/dist/ranking/mi/jaccard.d.ts +7 -0
- package/dist/ranking/mi/jaccard.d.ts.map +1 -1
- package/dist/ranking/mi/notch.d.ts +8 -0
- package/dist/ranking/mi/notch.d.ts.map +1 -1
- package/dist/ranking/mi/overlap-coefficient.d.ts +7 -0
- package/dist/ranking/mi/overlap-coefficient.d.ts.map +1 -1
- package/dist/ranking/mi/resource-allocation.d.ts +8 -0
- package/dist/ranking/mi/resource-allocation.d.ts.map +1 -1
- package/dist/ranking/mi/scale.d.ts +7 -0
- package/dist/ranking/mi/scale.d.ts.map +1 -1
- package/dist/ranking/mi/skew.d.ts +7 -0
- package/dist/ranking/mi/skew.d.ts.map +1 -1
- package/dist/ranking/mi/sorensen.d.ts +7 -0
- package/dist/ranking/mi/sorensen.d.ts.map +1 -1
- package/dist/ranking/mi/span.d.ts +8 -0
- package/dist/ranking/mi/span.d.ts.map +1 -1
- package/dist/ranking/mi/types.d.ts +12 -0
- package/dist/ranking/mi/types.d.ts.map +1 -1
- package/dist/ranking/parse.d.ts +24 -1
- package/dist/ranking/parse.d.ts.map +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Low-dependency TypeScript graph algorithms for citation network analysis: novel
|
|
|
14
14
|
- **Seed selection**: GRASP, Stratified
|
|
15
15
|
- **Subgraph extraction**: ego-network, k-core, k-truss, motif, induced, filter
|
|
16
16
|
- **Optional WebGPU acceleration**
|
|
17
|
+
- **Async support**: Generator coroutine protocol, sync/async runners, all algorithms available as `*Async` variants
|
|
17
18
|
|
|
18
19
|
## Installation
|
|
19
20
|
|
|
@@ -37,7 +38,7 @@ const ranked = parse(graph, result.paths, { mi: jaccard });
|
|
|
37
38
|
|
|
38
39
|
### Expansion: BASE Framework
|
|
39
40
|
|
|
40
|
-
**Boundary-free Adaptive Seeded Expansion** (BASE)
|
|
41
|
+
**Boundary-free Adaptive Seeded Expansion** (BASE) discovers the neighbourhood around seed nodes without any configuration. You provide seeds and a priority function; BASE expands outward, visiting the most interesting nodes first and recording paths when search frontiers from different seeds collide. It stops naturally when there is nothing left to explore — no depth limits, no size thresholds, no parameters to tune.
|
|
41
42
|
|
|
42
43
|
$$G_S = (V_S, E_S) \quad \text{where} \quad V_S = \bigcup_{v \in S} \text{Expand}(v, \pi)$$
|
|
43
44
|
|
|
@@ -51,7 +52,7 @@ Three key properties:
|
|
|
51
52
|
|
|
52
53
|
#### DOME: Degree-Ordered Multi-seed Expansion
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
Explores low-connectivity nodes before hubs. In a social network, DOME visits niche specialists before reaching the well-connected influencers, discovering the quiet corners of the graph before the busy crossroads.
|
|
55
56
|
|
|
56
57
|
$$\pi(v) = \frac{\deg^{+}(v) + \deg^{-}(v)}{w_V(v) + \varepsilon}$$
|
|
57
58
|
|
|
@@ -81,99 +82,117 @@ where $\deg^{+}(v)$ is weighted out-degree, $\deg^{-}(v)$ is weighted in-degree,
|
|
|
81
82
|
|
|
82
83
|
#### EDGE: Entropy-Driven Graph Expansion
|
|
83
84
|
|
|
85
|
+
Finds nodes that sit at the boundary between different kinds of things. If a person's friends include scientists, artists, and engineers (high type diversity), EDGE visits them early — they are likely bridges between communities.
|
|
86
|
+
|
|
84
87
|
$$\pi_{\text{EDGE}}(v) = \frac{1}{H_{\text{local}}(v) + \varepsilon} \times \log(\deg(v) + 1)$$
|
|
85
88
|
|
|
86
|
-
where $H_{\text{local}}(v) = -\sum_{\tau} p(\tau) \log p(\tau)$ is the Shannon entropy of the neighbour type distribution.
|
|
89
|
+
where $H_{\text{local}}(v) = -\sum_{\tau} p(\tau) \log p(\tau)$ is the Shannon entropy of the neighbour type distribution.
|
|
87
90
|
|
|
88
91
|
---
|
|
89
92
|
|
|
90
93
|
#### PIPE: Path-potential Informed Priority Expansion
|
|
91
94
|
|
|
95
|
+
Rushes towards nodes that are about to connect two search frontiers. When expanding from multiple seeds, PIPE detects that a node's neighbours have already been reached by another seed's frontier — meaning a connecting path is one step away.
|
|
96
|
+
|
|
92
97
|
$$\pi_{\text{PIPE}}(v) = \frac{\deg(v)}{1 + \mathrm{pathPotential}(v)}$$
|
|
93
98
|
|
|
94
|
-
where $\mathrm{pathPotential}(v) = \lvert N(v) \cap \bigcup_{j \neq i} V_j \rvert$ counts neighbours already visited by other seed frontiers.
|
|
99
|
+
where $\mathrm{pathPotential}(v) = \lvert N(v) \cap \bigcup_{j \neq i} V_j \rvert$ counts neighbours already visited by other seed frontiers.
|
|
95
100
|
|
|
96
101
|
---
|
|
97
102
|
|
|
98
103
|
#### SAGE: Salience-Accumulation Guided Expansion
|
|
99
104
|
|
|
105
|
+
Learns from its own discoveries. Phase 1 explores by degree (like DOME). Once the first path is found, SAGE switches to Phase 2: nodes that appear in many discovered paths get top priority, guiding expansion towards structurally rich regions.
|
|
106
|
+
|
|
100
107
|
$$
|
|
101
108
|
\pi_{\text{SAGE}}(v) = \begin{cases} \log(\deg(v) + 1) & \text{Phase 1 (before first path)} \\ -(\text{salience}(v) \times 1000 - \deg(v)) & \text{Phase 2 (after first path)} \end{cases}
|
|
102
109
|
$$
|
|
103
110
|
|
|
104
|
-
where $\text{salience}(v)$ counts discovered paths containing $v$.
|
|
111
|
+
where $\text{salience}(v)$ counts discovered paths containing $v$.
|
|
105
112
|
|
|
106
113
|
---
|
|
107
114
|
|
|
108
115
|
#### REACH: Retrospective Expansion with Adaptive Convergence
|
|
109
116
|
|
|
117
|
+
Uses the quality of already-discovered paths to steer future exploration. Phase 1 explores by degree. Once paths are found, REACH asks "which unexplored nodes look structurally similar to the endpoints of my best paths?" and prioritises those — seeking more of what already worked.
|
|
118
|
+
|
|
110
119
|
$$
|
|
111
120
|
\pi_{\text{REACH}}(v) = \begin{cases} \log(\deg(v) + 1) & \text{Phase 1} \\ \log(\deg(v) + 1) \times (1 - \widehat{\text{MI}}(v)) & \text{Phase 2} \end{cases}
|
|
112
121
|
$$
|
|
113
122
|
|
|
114
|
-
where $\widehat{\text{MI}}(v)$ estimates MI via Jaccard similarity to discovered path endpoints
|
|
115
|
-
|
|
116
|
-
$$
|
|
117
|
-
\widehat{\text{MI}}(v) = \frac{1}{\lvert \mathcal{P}\_{\text{top}} \rvert} \sum\_{p} J(N(v), N(p\_{\text{endpoint}}))
|
|
118
|
-
$$
|
|
123
|
+
where $\widehat{\text{MI}}(v) = \frac{1}{\lvert \mathcal{P}\_{\text{top}} \rvert} \sum\_{p} J(N(v), N(p\_{\text{endpoint}}))$ estimates MI via Jaccard similarity to discovered path endpoints.
|
|
119
124
|
|
|
120
125
|
---
|
|
121
126
|
|
|
122
127
|
#### MAZE: Multi-frontier Adaptive Zone Expansion
|
|
123
128
|
|
|
129
|
+
Combines the best of PIPE and SAGE across three phases. First, it races to find initial paths using path potential (like PIPE). Then it refines exploration using salience feedback (like SAGE). Finally, it decides when to stop based on whether it's still discovering diverse, high-quality paths.
|
|
130
|
+
|
|
124
131
|
$$
|
|
125
132
|
\pi^{(1)}(v) = \frac{\deg(v)}{1 + \mathrm{pathPotential}(v)} \qquad \pi^{(2)}(v) = \pi^{(1)}(v) \times \frac{1}{1 + \lambda \cdot \text{salience}(v)}
|
|
126
133
|
$$
|
|
127
134
|
|
|
128
|
-
Phase 1 uses
|
|
135
|
+
Phase 1 uses path potential until $M$ paths found. Phase 2 adds salience feedback. Phase 3 evaluates diversity, path count, and salience plateau for termination.
|
|
129
136
|
|
|
130
137
|
---
|
|
131
138
|
|
|
132
139
|
#### TIDE: Total Interconnected Degree Expansion
|
|
133
140
|
|
|
141
|
+
Avoids dense clusters by looking at total neighbourhood connectivity. A node surrounded by other well-connected nodes gets deferred; a node in a quiet corner of the graph gets explored first.
|
|
142
|
+
|
|
134
143
|
$$\pi_{\text{TIDE}}(v) = \deg(v) + \sum_{w \in N(v)} \deg(w)$$
|
|
135
144
|
|
|
136
|
-
|
|
145
|
+
Related to EDGE but uses raw degree sums rather than entropy.
|
|
137
146
|
|
|
138
147
|
---
|
|
139
148
|
|
|
140
149
|
#### LACE: Local Affinity-Computed Expansion
|
|
141
150
|
|
|
151
|
+
Explores towards nodes that are most similar to what the frontier has already seen. If a candidate node shares many neighbours with the explored region, it gets priority — building outward from a coherent core.
|
|
152
|
+
|
|
142
153
|
$$\pi_{\text{LACE}}(v) = 1 - \overline{\text{MI}}(v, \text{frontier})$$
|
|
143
154
|
|
|
144
|
-
|
|
155
|
+
Related to HAE but uses MI to visited nodes rather than type entropy.
|
|
145
156
|
|
|
146
157
|
---
|
|
147
158
|
|
|
148
159
|
#### WARP: Weighted Adjacent Reachability Priority
|
|
149
160
|
|
|
161
|
+
Aggressively prioritises nodes that look like they will connect two search frontiers, regardless of their degree. If a node's neighbours have been visited by another seed's search, it gets top priority.
|
|
162
|
+
|
|
150
163
|
$$\pi_{\text{WARP}}(v) = \frac{1}{1 + \text{bridge}(v)}$$
|
|
151
164
|
|
|
152
|
-
|
|
165
|
+
Related to PIPE but omits the degree numerator, making it more aggressive at prioritising bridge nodes.
|
|
153
166
|
|
|
154
167
|
---
|
|
155
168
|
|
|
156
169
|
#### FUSE: Fused Utility-Salience Expansion
|
|
157
170
|
|
|
171
|
+
Balances two signals simultaneously: how connected a node is (degree) and how strongly it relates to the explored region (MI). The weight $w$ controls the trade-off — at $w=0$ it behaves like DOME, at $w=1$ it behaves like LACE.
|
|
172
|
+
|
|
158
173
|
$$\pi_{\text{FUSE}}(v) = (1 - w) \cdot \deg(v) + w \cdot (1 - \overline{\text{MI}})$$
|
|
159
174
|
|
|
160
|
-
|
|
175
|
+
Related to SAGE but uses continuous blending rather than two-phase transition.
|
|
161
176
|
|
|
162
177
|
---
|
|
163
178
|
|
|
164
179
|
#### SIFT: Salience-Informed Frontier Threshold
|
|
165
180
|
|
|
181
|
+
Acts as a gate: nodes with MI above a threshold get MI-based priority (explore the promising ones); nodes below the threshold get deferred with a large degree-based penalty (ignore the unpromising ones). A binary version of REACH's continuous approach.
|
|
182
|
+
|
|
166
183
|
$$
|
|
167
184
|
\pi_{\text{SIFT}}(v) = \begin{cases} 1 - \overline{\text{MI}} & \text{if } \overline{\text{MI}} \geq \tau \\ \deg(v) + 100 & \text{otherwise} \end{cases}
|
|
168
185
|
$$
|
|
169
186
|
|
|
170
|
-
|
|
187
|
+
Related to REACH but uses a hard threshold instead of continuous MI-weighted priority.
|
|
171
188
|
|
|
172
189
|
---
|
|
173
190
|
|
|
174
191
|
#### FLUX: Flexible Local Utility Crossover
|
|
175
192
|
|
|
176
|
-
|
|
193
|
+
Adapts its strategy to the local topology of each node. In dense regions it uses low-degree-first exploration (like EDGE); near frontier boundaries it uses bridge detection (like PIPE); in sparse regions it falls back to degree ordering (like DOME). Different parts of the graph are explored with different strategies simultaneously.
|
|
194
|
+
|
|
195
|
+
Related to MAZE but adapts spatially (per-node) rather than temporally (per-phase).
|
|
177
196
|
|
|
178
197
|
---
|
|
179
198
|
|
|
@@ -192,73 +211,83 @@ Density-adaptive strategy switching. Selects between DOME, EDGE, and PIPE modes
|
|
|
192
211
|
|
|
193
212
|
### Path Ranking: PARSE
|
|
194
213
|
|
|
195
|
-
**Path Aggregation Ranked by Salience Estimation** (PARSE)
|
|
214
|
+
**Path Aggregation Ranked by Salience Estimation** (PARSE) ranks discovered paths by asking "how consistently strong is every edge along this path?" It uses the geometric mean of per-edge MI scores, which means one weak link drags down the entire path — unlike arithmetic mean where a strong edge can compensate for a weak one. A 10-hop path with consistently good edges scores the same as a 2-hop path with equally good edges.
|
|
196
215
|
|
|
197
216
|
$$M(P) = \exp\left( \frac{1}{k} \sum_{i=1}^{k} \log I(u_i, v_i) \right)$$
|
|
198
217
|
|
|
199
|
-
where $k$ is path length (number of edges) and $I(u_i, v_i)$ is the per-edge MI score from any variant below.
|
|
218
|
+
where $k$ is path length (number of edges) and $I(u_i, v_i)$ is the per-edge MI score from any variant below.
|
|
200
219
|
|
|
201
220
|
---
|
|
202
221
|
|
|
203
222
|
### MI Variants
|
|
204
223
|
|
|
205
|
-
|
|
224
|
+
MI variants answer the question "how strongly are two connected nodes related?" Each measures the overlap between their neighbourhoods, then optionally weights by structural properties like density, degree rarity, clustering, or entity type. PARSE uses these as per-edge scores in its geometric mean.
|
|
206
225
|
|
|
207
226
|
---
|
|
208
227
|
|
|
209
228
|
#### Jaccard (baseline)
|
|
210
229
|
|
|
211
|
-
|
|
230
|
+
What fraction of combined neighbours do two nodes share? If Alice and Bob know 3 of the same people out of 10 total acquaintances between them, their Jaccard score is 0.3.
|
|
212
231
|
|
|
213
|
-
|
|
232
|
+
$$I_{\text{Jac}}(u, v) = \frac{|N(u) \cap N(v)|}{|N(u) \cup N(v)|}$$
|
|
214
233
|
|
|
215
234
|
---
|
|
216
235
|
|
|
217
236
|
#### Adamic-Adar
|
|
218
237
|
|
|
219
|
-
|
|
238
|
+
Counts shared neighbours, but recognises that sharing a rare connection is more meaningful than sharing a popular one. If two researchers both cite a niche paper, that says more about their relationship than both citing a famous textbook.
|
|
220
239
|
|
|
221
|
-
|
|
240
|
+
$$I_{\text{AA}}(u, v) = \sum_{w \in N(u) \cap N(v)} \frac{1}{\log(\deg(w) + 1)}$$
|
|
222
241
|
|
|
223
242
|
---
|
|
224
243
|
|
|
225
244
|
#### SCALE: Structural Correction via Adjusted Local Estimation
|
|
226
245
|
|
|
246
|
+
Adjusts for graph density. In a dense network where everyone knows everyone, sharing neighbours is expected and less meaningful. In a sparse network, the same overlap is rare and significant. SCALE divides Jaccard by density to make scores comparable across differently-dense regions.
|
|
247
|
+
|
|
227
248
|
$$I_{\text{SCALE}}(u, v) = \frac{J(N(u), N(v))}{\rho(G)}$$
|
|
228
249
|
|
|
229
|
-
where $\rho(G) = \frac{2|E|}{|V|(|V|-1)}$ is graph density.
|
|
250
|
+
where $\rho(G) = \frac{2|E|}{|V|(|V|-1)}$ is graph density.
|
|
230
251
|
|
|
231
252
|
---
|
|
232
253
|
|
|
233
254
|
#### SKEW: Sparse-weighted Knowledge Emphasis Weighting
|
|
234
255
|
|
|
256
|
+
Rewards edges between rare (low-degree) nodes and penalises edges involving hubs. Like TF-IDF in search engines: a connection between two niche nodes is more informative than a connection between two mega-hubs that connect to everything.
|
|
257
|
+
|
|
235
258
|
$$I_{\text{SKEW}}(u, v) = J(N(u), N(v)) \cdot \log\!\left(\frac{N}{\deg(u) + 1}\right) \cdot \log\!\left(\frac{N}{\deg(v) + 1}\right)$$
|
|
236
259
|
|
|
237
|
-
where $N = |V|$.
|
|
260
|
+
where $N = |V|$.
|
|
238
261
|
|
|
239
262
|
---
|
|
240
263
|
|
|
241
264
|
#### SPAN: Spanning-community Penalty for Adjacent Nodes
|
|
242
265
|
|
|
266
|
+
Rewards edges that bridge separate communities and penalises edges within tight-knit groups. If both endpoints sit in dense clusters where everyone knows everyone (high clustering coefficient), the edge is probably redundant. If at least one endpoint is a bridge between groups, the edge is structurally interesting.
|
|
267
|
+
|
|
243
268
|
$$I_{\text{SPAN}}(u, v) = J(N(u), N(v)) \cdot \bigl(1 - \max(C(u), C(v))\bigr)$$
|
|
244
269
|
|
|
245
|
-
where $C(v)$ is the local clustering coefficient.
|
|
270
|
+
where $C(v)$ is the local clustering coefficient.
|
|
246
271
|
|
|
247
272
|
---
|
|
248
273
|
|
|
249
274
|
#### ETCH: Edge Type Contrast Heuristic
|
|
250
275
|
|
|
276
|
+
Boosts edges of rare types. If a graph has 1000 "knows" edges but only 5 "mentors" edges, a mentoring relationship is worth more than an acquaintanceship. ETCH multiplies Jaccard by the log-rarity of the edge type.
|
|
277
|
+
|
|
251
278
|
$$I_{\text{ETCH}}(u, v) = J(N(u), N(v)) \cdot \log\!\left(\frac{|E|}{\text{count}(\text{edges with type}(u,v))}\right)$$
|
|
252
279
|
|
|
253
|
-
|
|
280
|
+
Requires edge-type annotations; falls back to Jaccard when unavailable.
|
|
254
281
|
|
|
255
282
|
---
|
|
256
283
|
|
|
257
284
|
#### NOTCH: Node Type Contrast Heuristic
|
|
258
285
|
|
|
286
|
+
Boosts edges connecting rare node types. In a graph with 500 people but only 10 organisations, an edge involving an organisation is more distinctive. NOTCH multiplies Jaccard by the log-rarity of both endpoint types.
|
|
287
|
+
|
|
259
288
|
$$I_{\text{NOTCH}}(u, v) = J(N(u), N(v)) \cdot \log\!\left(\frac{|V|}{c(\tau_u)}\right) \cdot \log\!\left(\frac{|V|}{c(\tau_v)}\right)$$
|
|
260
289
|
|
|
261
|
-
where $c(\tau_u)$ is the count of nodes with the same type as $u$.
|
|
290
|
+
where $c(\tau_u)$ is the count of nodes with the same type as $u$.
|
|
262
291
|
|
|
263
292
|
---
|
|
264
293
|
|
|
@@ -296,7 +325,7 @@ where $c(\tau_u)$ is the count of nodes with the same type as $u$. Weights Jacca
|
|
|
296
325
|
|
|
297
326
|
### Seed Selection: GRASP
|
|
298
327
|
|
|
299
|
-
**Graph-agnostic Representative seed pAir Sampling
|
|
328
|
+
**Graph-agnostic Representative seed pAir Sampling** picks starting points for expansion algorithms. Given a graph you have never seen before, GRASP streams through its edges, samples a representative set of nodes, clusters them by structural role (hubs, bridges, peripherals), and returns seed pairs that cover the full range of structural diversity — without loading the entire graph into memory.
|
|
300
329
|
|
|
301
330
|
Three phases:
|
|
302
331
|
|
|
@@ -323,6 +352,28 @@ import { ... } from 'graphwise/extraction'; // Subgraph extraction
|
|
|
323
352
|
import { ... } from 'graphwise/utils'; // Utilities
|
|
324
353
|
import { ... } from 'graphwise/gpu'; // WebGPU acceleration
|
|
325
354
|
import { ... } from 'graphwise/schemas'; // Zod schemas
|
|
355
|
+
import { ... } from 'graphwise/async'; // Async runners & protocol
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Async Usage
|
|
359
|
+
|
|
360
|
+
All algorithms are available as `*Async` variants for use with remote or lazy graph data sources:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { domeAsync, parseAsync, jaccardAsync } from "graphwise";
|
|
364
|
+
import type { AsyncReadableGraph } from "graphwise/graph";
|
|
365
|
+
|
|
366
|
+
// Your async graph implementation
|
|
367
|
+
const remoteGraph: AsyncReadableGraph = createRemoteGraph();
|
|
368
|
+
|
|
369
|
+
const result = await domeAsync(remoteGraph, seeds, {
|
|
370
|
+
signal: controller.signal,
|
|
371
|
+
onProgress: (stats) => console.log(stats),
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const ranked = await parseAsync(remoteGraph, result.paths, {
|
|
375
|
+
mi: jaccardAsync,
|
|
376
|
+
});
|
|
326
377
|
```
|
|
327
378
|
|
|
328
379
|
## Commands
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/async/utils.ts
|
|
3
|
+
/**
|
|
4
|
+
* Async utility functions.
|
|
5
|
+
*
|
|
6
|
+
* @module async/utils
|
|
7
|
+
*/
|
|
8
|
+
/** Collect an AsyncIterable into a readonly array. */
|
|
9
|
+
async function collectAsyncIterable(iter) {
|
|
10
|
+
const result = [];
|
|
11
|
+
for await (const item of iter) result.push(item);
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
/** Default yield strategy: setTimeout(0) to yield to the event loop. */
|
|
15
|
+
function defaultYieldStrategy() {
|
|
16
|
+
return new Promise((r) => {
|
|
17
|
+
setTimeout(r, 0);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/async/runners.ts
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a single GraphOp against a synchronous ReadableGraph.
|
|
24
|
+
*
|
|
25
|
+
* Returns a tagged GraphOpResponse so the receiving generator can narrow
|
|
26
|
+
* the result type without type assertions.
|
|
27
|
+
*
|
|
28
|
+
* @param graph - The synchronous graph to query
|
|
29
|
+
* @param op - The operation to resolve
|
|
30
|
+
* @returns The tagged response
|
|
31
|
+
*/
|
|
32
|
+
function resolveSyncOp(graph, op) {
|
|
33
|
+
switch (op.tag) {
|
|
34
|
+
case "neighbours": return {
|
|
35
|
+
tag: "neighbours",
|
|
36
|
+
value: Array.from(graph.neighbours(op.id, op.direction))
|
|
37
|
+
};
|
|
38
|
+
case "degree": return {
|
|
39
|
+
tag: "degree",
|
|
40
|
+
value: graph.degree(op.id, op.direction)
|
|
41
|
+
};
|
|
42
|
+
case "getNode": return {
|
|
43
|
+
tag: "getNode",
|
|
44
|
+
value: graph.getNode(op.id)
|
|
45
|
+
};
|
|
46
|
+
case "getEdge": return {
|
|
47
|
+
tag: "getEdge",
|
|
48
|
+
value: graph.getEdge(op.source, op.target)
|
|
49
|
+
};
|
|
50
|
+
case "hasNode": return {
|
|
51
|
+
tag: "hasNode",
|
|
52
|
+
value: graph.hasNode(op.id)
|
|
53
|
+
};
|
|
54
|
+
case "yield": return { tag: "yield" };
|
|
55
|
+
case "progress": return { tag: "progress" };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Drive a generator to completion using a synchronous graph.
|
|
60
|
+
*
|
|
61
|
+
* The generator yields GraphOp requests; each is resolved immediately
|
|
62
|
+
* against the graph and the tagged response is fed back via gen.next().
|
|
63
|
+
*
|
|
64
|
+
* @param gen - The generator to drive
|
|
65
|
+
* @param graph - The graph to resolve ops against
|
|
66
|
+
* @returns The generator's return value
|
|
67
|
+
*/
|
|
68
|
+
function runSync(gen, graph) {
|
|
69
|
+
let step = gen.next();
|
|
70
|
+
while (step.done !== true) {
|
|
71
|
+
const response = resolveSyncOp(graph, step.value);
|
|
72
|
+
step = gen.next(response);
|
|
73
|
+
}
|
|
74
|
+
return step.value;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Resolve a single GraphOp against an async ReadableGraph.
|
|
78
|
+
*
|
|
79
|
+
* AsyncIterables (neighbours) are collected into readonly arrays so the
|
|
80
|
+
* generator receives the same value type as in sync mode. Returns a tagged
|
|
81
|
+
* GraphOpResponse for type-safe narrowing without assertions.
|
|
82
|
+
*
|
|
83
|
+
* @param graph - The async graph to query
|
|
84
|
+
* @param op - The operation to resolve
|
|
85
|
+
* @returns A promise resolving to the tagged response
|
|
86
|
+
*/
|
|
87
|
+
async function resolveAsyncOp(graph, op) {
|
|
88
|
+
switch (op.tag) {
|
|
89
|
+
case "neighbours": return {
|
|
90
|
+
tag: "neighbours",
|
|
91
|
+
value: await collectAsyncIterable(graph.neighbours(op.id, op.direction))
|
|
92
|
+
};
|
|
93
|
+
case "degree": return {
|
|
94
|
+
tag: "degree",
|
|
95
|
+
value: await graph.degree(op.id, op.direction)
|
|
96
|
+
};
|
|
97
|
+
case "getNode": return {
|
|
98
|
+
tag: "getNode",
|
|
99
|
+
value: await graph.getNode(op.id)
|
|
100
|
+
};
|
|
101
|
+
case "getEdge": return {
|
|
102
|
+
tag: "getEdge",
|
|
103
|
+
value: await graph.getEdge(op.source, op.target)
|
|
104
|
+
};
|
|
105
|
+
case "hasNode": return {
|
|
106
|
+
tag: "hasNode",
|
|
107
|
+
value: await graph.hasNode(op.id)
|
|
108
|
+
};
|
|
109
|
+
case "yield": return { tag: "yield" };
|
|
110
|
+
case "progress": return { tag: "progress" };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Drive a generator to completion using an async graph.
|
|
115
|
+
*
|
|
116
|
+
* Extends sync semantics with:
|
|
117
|
+
* - Cancellation via AbortSignal (throws DOMException "AbortError")
|
|
118
|
+
* - Cooperative yielding at `yield` ops (calls yieldStrategy)
|
|
119
|
+
* - Progress callbacks at `progress` ops (may be async for backpressure)
|
|
120
|
+
* - Error propagation: graph errors are forwarded via gen.throw(); if the
|
|
121
|
+
* generator does not handle them, they propagate to the caller
|
|
122
|
+
*
|
|
123
|
+
* @param gen - The generator to drive
|
|
124
|
+
* @param graph - The async graph to resolve ops against
|
|
125
|
+
* @param options - Runner configuration
|
|
126
|
+
* @returns A promise resolving to the generator's return value
|
|
127
|
+
*/
|
|
128
|
+
async function runAsync(gen, graph, options) {
|
|
129
|
+
const signal = options?.signal;
|
|
130
|
+
const onProgress = options?.onProgress;
|
|
131
|
+
const yieldStrategy = options?.yieldStrategy ?? defaultYieldStrategy;
|
|
132
|
+
let step = gen.next();
|
|
133
|
+
while (step.done !== true) {
|
|
134
|
+
if (signal?.aborted === true) {
|
|
135
|
+
const abortError = new DOMException("Aborted", "AbortError");
|
|
136
|
+
try {
|
|
137
|
+
gen.throw(abortError);
|
|
138
|
+
} catch {
|
|
139
|
+
throw abortError;
|
|
140
|
+
}
|
|
141
|
+
throw abortError;
|
|
142
|
+
}
|
|
143
|
+
const op = step.value;
|
|
144
|
+
if (op.tag === "yield") {
|
|
145
|
+
await yieldStrategy();
|
|
146
|
+
step = gen.next({ tag: "yield" });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (op.tag === "progress") {
|
|
150
|
+
if (onProgress !== void 0) {
|
|
151
|
+
const maybePromise = onProgress(op.stats);
|
|
152
|
+
if (maybePromise instanceof Promise) await maybePromise;
|
|
153
|
+
}
|
|
154
|
+
step = gen.next({ tag: "progress" });
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
let response;
|
|
158
|
+
try {
|
|
159
|
+
response = await resolveAsyncOp(graph, op);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
step = gen.throw(error);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
step = gen.next(response);
|
|
165
|
+
}
|
|
166
|
+
return step.value;
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/async/ops.ts
|
|
170
|
+
function* opNeighbours(id, direction) {
|
|
171
|
+
const response = yield direction !== void 0 ? {
|
|
172
|
+
tag: "neighbours",
|
|
173
|
+
id,
|
|
174
|
+
direction
|
|
175
|
+
} : {
|
|
176
|
+
tag: "neighbours",
|
|
177
|
+
id
|
|
178
|
+
};
|
|
179
|
+
if (response.tag !== "neighbours") throw new TypeError(`Expected neighbours response, got ${response.tag}`);
|
|
180
|
+
return response.value;
|
|
181
|
+
}
|
|
182
|
+
function* opDegree(id, direction) {
|
|
183
|
+
const response = yield direction !== void 0 ? {
|
|
184
|
+
tag: "degree",
|
|
185
|
+
id,
|
|
186
|
+
direction
|
|
187
|
+
} : {
|
|
188
|
+
tag: "degree",
|
|
189
|
+
id
|
|
190
|
+
};
|
|
191
|
+
if (response.tag !== "degree") throw new TypeError(`Expected degree response, got ${response.tag}`);
|
|
192
|
+
return response.value;
|
|
193
|
+
}
|
|
194
|
+
function* opGetNode(id) {
|
|
195
|
+
const response = yield {
|
|
196
|
+
tag: "getNode",
|
|
197
|
+
id
|
|
198
|
+
};
|
|
199
|
+
if (response.tag !== "getNode") throw new TypeError(`Expected getNode response, got ${response.tag}`);
|
|
200
|
+
return response.value;
|
|
201
|
+
}
|
|
202
|
+
function* opGetEdge(source, target) {
|
|
203
|
+
const response = yield {
|
|
204
|
+
tag: "getEdge",
|
|
205
|
+
source,
|
|
206
|
+
target
|
|
207
|
+
};
|
|
208
|
+
if (response.tag !== "getEdge") throw new TypeError(`Expected getEdge response, got ${response.tag}`);
|
|
209
|
+
return response.value;
|
|
210
|
+
}
|
|
211
|
+
function* opHasNode(id) {
|
|
212
|
+
const response = yield {
|
|
213
|
+
tag: "hasNode",
|
|
214
|
+
id
|
|
215
|
+
};
|
|
216
|
+
if (response.tag !== "hasNode") throw new TypeError(`Expected hasNode response, got ${response.tag}`);
|
|
217
|
+
return response.value;
|
|
218
|
+
}
|
|
219
|
+
function* opYield() {
|
|
220
|
+
yield { tag: "yield" };
|
|
221
|
+
}
|
|
222
|
+
function* opProgress(stats) {
|
|
223
|
+
yield {
|
|
224
|
+
tag: "progress",
|
|
225
|
+
stats
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
exports.collectAsyncIterable = collectAsyncIterable;
|
|
230
|
+
exports.defaultYieldStrategy = defaultYieldStrategy;
|
|
231
|
+
exports.opDegree = opDegree;
|
|
232
|
+
exports.opGetEdge = opGetEdge;
|
|
233
|
+
exports.opGetNode = opGetNode;
|
|
234
|
+
exports.opHasNode = opHasNode;
|
|
235
|
+
exports.opNeighbours = opNeighbours;
|
|
236
|
+
exports.opProgress = opProgress;
|
|
237
|
+
exports.opYield = opYield;
|
|
238
|
+
exports.resolveAsyncOp = resolveAsyncOp;
|
|
239
|
+
exports.resolveSyncOp = resolveSyncOp;
|
|
240
|
+
exports.runAsync = runAsync;
|
|
241
|
+
exports.runSync = runSync;
|
|
242
|
+
|
|
243
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/async/utils.ts","../../src/async/runners.ts","../../src/async/ops.ts"],"sourcesContent":["/**\n * Async utility functions.\n *\n * @module async/utils\n */\n\n/** Collect an AsyncIterable into a readonly array. */\nexport async function collectAsyncIterable<T>(\n\titer: AsyncIterable<T>,\n): Promise<readonly T[]> {\n\tconst result: T[] = [];\n\tfor await (const item of iter) result.push(item);\n\treturn result;\n}\n\n/** Default yield strategy: setTimeout(0) to yield to the event loop. */\nexport function defaultYieldStrategy(): Promise<void> {\n\treturn new Promise((r) => {\n\t\tsetTimeout(r, 0);\n\t});\n}\n","/**\n * Sync and async runners for generator-based graph algorithms.\n *\n * The runner drives a generator that yields GraphOp objects, resolves each op\n * against the graph, and feeds the result back via gen.next(response). This\n * allows algorithm logic to be written once as a generator and executed\n * synchronously or asynchronously depending on the graph backing.\n *\n * @module async/runners\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type { GraphOp, GraphOpResponse } from \"./protocol\";\nimport type { AsyncRunnerOptions } from \"./types\";\nimport { collectAsyncIterable, defaultYieldStrategy } from \"./utils\";\n\n// ---------------------------------------------------------------------------\n// Sync runner\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a single GraphOp against a synchronous ReadableGraph.\n *\n * Returns a tagged GraphOpResponse so the receiving generator can narrow\n * the result type without type assertions.\n *\n * @param graph - The synchronous graph to query\n * @param op - The operation to resolve\n * @returns The tagged response\n */\nexport function resolveSyncOp<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\top: GraphOp,\n): GraphOpResponse<N, E> {\n\tswitch (op.tag) {\n\t\tcase \"neighbours\":\n\t\t\treturn {\n\t\t\t\ttag: \"neighbours\",\n\t\t\t\tvalue: Array.from(graph.neighbours(op.id, op.direction)),\n\t\t\t};\n\t\tcase \"degree\":\n\t\t\treturn { tag: \"degree\", value: graph.degree(op.id, op.direction) };\n\t\tcase \"getNode\":\n\t\t\treturn { tag: \"getNode\", value: graph.getNode(op.id) };\n\t\tcase \"getEdge\":\n\t\t\treturn { tag: \"getEdge\", value: graph.getEdge(op.source, op.target) };\n\t\tcase \"hasNode\":\n\t\t\treturn { tag: \"hasNode\", value: graph.hasNode(op.id) };\n\t\tcase \"yield\":\n\t\t\treturn { tag: \"yield\" };\n\t\tcase \"progress\":\n\t\t\treturn { tag: \"progress\" };\n\t}\n}\n\n/**\n * Drive a generator to completion using a synchronous graph.\n *\n * The generator yields GraphOp requests; each is resolved immediately\n * against the graph and the tagged response is fed back via gen.next().\n *\n * @param gen - The generator to drive\n * @param graph - The graph to resolve ops against\n * @returns The generator's return value\n */\nexport function runSync<N extends NodeData, E extends EdgeData, R>(\n\tgen: Generator<GraphOp, R, GraphOpResponse<N, E>>,\n\tgraph: ReadableGraph<N, E>,\n): R {\n\tlet step = gen.next();\n\twhile (step.done !== true) {\n\t\tconst response = resolveSyncOp(graph, step.value);\n\t\tstep = gen.next(response);\n\t}\n\treturn step.value;\n}\n\n// ---------------------------------------------------------------------------\n// Async runner\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a single GraphOp against an async ReadableGraph.\n *\n * AsyncIterables (neighbours) are collected into readonly arrays so the\n * generator receives the same value type as in sync mode. Returns a tagged\n * GraphOpResponse for type-safe narrowing without assertions.\n *\n * @param graph - The async graph to query\n * @param op - The operation to resolve\n * @returns A promise resolving to the tagged response\n */\nexport async function resolveAsyncOp<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\top: GraphOp,\n): Promise<GraphOpResponse<N, E>> {\n\tswitch (op.tag) {\n\t\tcase \"neighbours\":\n\t\t\treturn {\n\t\t\t\ttag: \"neighbours\",\n\t\t\t\tvalue: await collectAsyncIterable(\n\t\t\t\t\tgraph.neighbours(op.id, op.direction),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"degree\":\n\t\t\treturn { tag: \"degree\", value: await graph.degree(op.id, op.direction) };\n\t\tcase \"getNode\":\n\t\t\treturn { tag: \"getNode\", value: await graph.getNode(op.id) };\n\t\tcase \"getEdge\":\n\t\t\treturn {\n\t\t\t\ttag: \"getEdge\",\n\t\t\t\tvalue: await graph.getEdge(op.source, op.target),\n\t\t\t};\n\t\tcase \"hasNode\":\n\t\t\treturn { tag: \"hasNode\", value: await graph.hasNode(op.id) };\n\t\tcase \"yield\":\n\t\t\treturn { tag: \"yield\" };\n\t\tcase \"progress\":\n\t\t\treturn { tag: \"progress\" };\n\t}\n}\n\n/**\n * Drive a generator to completion using an async graph.\n *\n * Extends sync semantics with:\n * - Cancellation via AbortSignal (throws DOMException \"AbortError\")\n * - Cooperative yielding at `yield` ops (calls yieldStrategy)\n * - Progress callbacks at `progress` ops (may be async for backpressure)\n * - Error propagation: graph errors are forwarded via gen.throw(); if the\n * generator does not handle them, they propagate to the caller\n *\n * @param gen - The generator to drive\n * @param graph - The async graph to resolve ops against\n * @param options - Runner configuration\n * @returns A promise resolving to the generator's return value\n */\nexport async function runAsync<N extends NodeData, E extends EdgeData, R>(\n\tgen: Generator<GraphOp, R, GraphOpResponse<N, E>>,\n\tgraph: AsyncReadableGraph<N, E>,\n\toptions?: AsyncRunnerOptions,\n): Promise<R> {\n\tconst signal = options?.signal;\n\tconst onProgress = options?.onProgress;\n\tconst yieldStrategy = options?.yieldStrategy ?? defaultYieldStrategy;\n\n\tlet step = gen.next();\n\n\twhile (step.done !== true) {\n\t\t// Check for cancellation before processing each op. Throw the error\n\t\t// into the generator so that any finally blocks in the algorithm run\n\t\t// before the error propagates to the caller.\n\t\tif (signal?.aborted === true) {\n\t\t\tconst abortError = new DOMException(\"Aborted\", \"AbortError\");\n\t\t\ttry {\n\t\t\t\tgen.throw(abortError);\n\t\t\t} catch {\n\t\t\t\t// Generator did not handle the error — propagate it\n\t\t\t\tthrow abortError;\n\t\t\t}\n\t\t\t// Generator handled the error but we still honour cancellation\n\t\t\tthrow abortError;\n\t\t}\n\n\t\tconst op = step.value;\n\n\t\t// Handle cooperative yield ops without hitting the graph\n\t\tif (op.tag === \"yield\") {\n\t\t\tawait yieldStrategy();\n\t\t\tstep = gen.next({ tag: \"yield\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Handle progress ops: call the callback (awaiting if async)\n\t\tif (op.tag === \"progress\") {\n\t\t\tif (onProgress !== undefined) {\n\t\t\t\tconst maybePromise = onProgress(op.stats);\n\t\t\t\tif (maybePromise instanceof Promise) {\n\t\t\t\t\tawait maybePromise;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstep = gen.next({ tag: \"progress\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Resolve graph ops, forwarding any errors into the generator\n\t\tlet response: GraphOpResponse<N, E>;\n\t\ttry {\n\t\t\tresponse = await resolveAsyncOp(graph, op);\n\t\t} catch (error) {\n\t\t\t// Forward the error into the generator; if unhandled, it propagates\n\t\t\tstep = gen.throw(error);\n\t\t\tcontinue;\n\t\t}\n\n\t\tstep = gen.next(response);\n\t}\n\n\treturn step.value;\n}\n","/**\n * Type-safe yield helpers for graph operations.\n *\n * Each function is a sub-generator that yields one GraphOp and returns\n * the correctly-typed result. Narrowing is done via the tagged discriminated\n * union in GraphOpResponse — no type assertions needed.\n *\n * Use with `yield*` inside algorithm generators.\n *\n * @module async/ops\n */\n\nimport type { NodeId, NodeData, EdgeData, Direction } from \"../graph\";\nimport type { GraphOp, GraphOpResponse, ProgressStats } from \"./protocol\";\n\nexport function* opNeighbours<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(\n\tid: NodeId,\n\tdirection?: Direction,\n): Generator<GraphOp, readonly NodeId[], GraphOpResponse<N, E>> {\n\tconst op: GraphOp =\n\t\tdirection !== undefined\n\t\t\t? { tag: \"neighbours\", id, direction }\n\t\t\t: { tag: \"neighbours\", id };\n\tconst response: GraphOpResponse<N, E> = yield op;\n\tif (response.tag !== \"neighbours\") {\n\t\tthrow new TypeError(`Expected neighbours response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opDegree<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(\n\tid: NodeId,\n\tdirection?: Direction,\n): Generator<GraphOp, number, GraphOpResponse<N, E>> {\n\tconst op: GraphOp =\n\t\tdirection !== undefined\n\t\t\t? { tag: \"degree\", id, direction }\n\t\t\t: { tag: \"degree\", id };\n\tconst response: GraphOpResponse<N, E> = yield op;\n\tif (response.tag !== \"degree\") {\n\t\tthrow new TypeError(`Expected degree response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opGetNode<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(id: NodeId): Generator<GraphOp, N | undefined, GraphOpResponse<N, E>> {\n\tconst response: GraphOpResponse<N, E> = yield { tag: \"getNode\", id };\n\tif (response.tag !== \"getNode\") {\n\t\tthrow new TypeError(`Expected getNode response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opGetEdge<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(\n\tsource: NodeId,\n\ttarget: NodeId,\n): Generator<GraphOp, E | undefined, GraphOpResponse<N, E>> {\n\tconst response: GraphOpResponse<N, E> = yield {\n\t\ttag: \"getEdge\",\n\t\tsource,\n\t\ttarget,\n\t};\n\tif (response.tag !== \"getEdge\") {\n\t\tthrow new TypeError(`Expected getEdge response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opHasNode<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(id: NodeId): Generator<GraphOp, boolean, GraphOpResponse<N, E>> {\n\tconst response: GraphOpResponse<N, E> = yield { tag: \"hasNode\", id };\n\tif (response.tag !== \"hasNode\") {\n\t\tthrow new TypeError(`Expected hasNode response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opYield<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(): Generator<GraphOp, void, GraphOpResponse<N, E>> {\n\tyield { tag: \"yield\" };\n}\n\nexport function* opProgress<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(stats: ProgressStats): Generator<GraphOp, void, GraphOpResponse<N, E>> {\n\tyield { tag: \"progress\", stats };\n}\n"],"mappings":"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"}
|