@vltpkg/graph-run 1.0.0-rc.23 → 1.0.0-rc.25

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.
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Codes indicating the type of error that was encountered.
3
+ * These are found on the `Error.cause.code` field.
4
+ *
5
+ * They are:
6
+ *
7
+ * - `'GRAPHRUN_TRAVERSAL'` The command run on a given node has failed, either
8
+ * by throwing an error, or by returning a rejected promise.
9
+ * - `'GRAPHRUN_NO_NODES'` An empty list of initial nodes was provided to the
10
+ * graph run operation. At least one starting node must be present in the
11
+ * list.
12
+ * - `'GRAPHRUN_CYCLE_WITHOUT_PATH'` - A cycle in the graph was detected, but
13
+ * the path *to* the node where the cycle was detected could not be
14
+ * determined. This is impossible, and cannot ever happen.
15
+ */
16
+ export type ErrorCode = 'GRAPHRUN_NO_NODES' | 'GRAPHRUN_CYCLE_WITHOUT_PATH' | 'GRAPHRUN_TRAVERSAL';
17
+ export type ErrorCause<Node> = {
18
+ code: ErrorCode;
19
+ } & ({
20
+ code: 'GRAPHRUN_NO_NODES';
21
+ found: unknown;
22
+ wanted: string;
23
+ } | {
24
+ code: 'GRAPHRUN_CYCLE_WITHOUT_PATH';
25
+ } | {
26
+ code: 'GRAPHRUN_TRAVERSAL';
27
+ cause: Error;
28
+ node: Node;
29
+ path: Node[];
30
+ });
31
+ export declare const isGraphRunError: <Node>(er: unknown) => er is Error & {
32
+ cause: ErrorCause<Node>;
33
+ };
34
+ /**
35
+ * Options that define the graph and how to traverse it
36
+ */
37
+ export interface RunnerOptions<Node, Result = void> {
38
+ /** Array of one or more entry nodes. */
39
+ graph: [node: Node, ...rest: Node[]];
40
+ /** get the dependencies of a given node */
41
+ getDeps: (node: Node) => Node[] | Promise<Node[]>;
42
+ /** action to take on each node */
43
+ visit: (node: Node, signal: AbortSignal, path: Node[], depResults: DepResults<Node, Result>) => Result | Promise<Result>;
44
+ /**
45
+ * Called when a cycle is encountered.
46
+ * Throw in this method to enforce a DAG graph.
47
+ * If left undefined, then cycles are silently ignored and skipped.
48
+ *
49
+ * `node` parameter is the dependency that is being skipped.
50
+ *
51
+ * `cycle` is the route from the dependent back to itself via
52
+ * the parent.
53
+ *
54
+ * `path` is the path to the dependent who wanted this dep to be
55
+ * loaded.
56
+ */
57
+ onCycle?: (node: Node, cycle: Node[], path: Node[]) => void | Promise<void>;
58
+ /**
59
+ * Set to `false` to continue operations even if errors occur.
60
+ * If set to false, then an AggregateError will be raised on failure
61
+ * containing all failures (even if only one). If true, then a normal
62
+ * Error will be raised on failure.
63
+ * @default true
64
+ */
65
+ failFast?: boolean;
66
+ /** a signal that will trigger the graph traversal to end prematurely */
67
+ signal?: AbortSignal;
68
+ }
69
+ export type DepResults<Node, Result> = Map<Node, Result | undefined>;
70
+ /**
71
+ * Options that can define a synchronous graph traversal.
72
+ *
73
+ * Note that if the visit() method is async, then the *promises themselves*
74
+ * will be used as the `Result` type, which is likely not what you want!
75
+ */
76
+ export interface RunnerOptionsSync<Node, Result = void> extends RunnerOptions<Node, Result> {
77
+ /** Get a set of dependency nodes synchronously */
78
+ getDeps: (node: Node) => Node[];
79
+ /** Visit a node synchronously */
80
+ visit: (node: Node, signal: AbortSignal, path: Node[], depResults: DepResults<Node, Result>) => Result;
81
+ /** Handle cycles synchronously */
82
+ onCycle?: (node: Node, cycle: Node[], path: Node[]) => void;
83
+ }
84
+ /** A map of nodes to their PromiseSettledResult value */
85
+ export type SettledMap<Node, Result = void> = Map<Node, PromiseSettledResult<Result>>;
86
+ /** Any function or class. Used for Error.captureStackTrace */
87
+ export type Callable = Function | ((...a: unknown[]) => unknown) | (new (...a: unknown[]) => unknown);
88
+ /** Base class of Runner and RunnerSync */
89
+ export declare abstract class RunnerBase<
90
+ /** The type of thing to be found in this graph */
91
+ Node,
92
+ /** Type returned by the visit() method */
93
+ Result = void, Sync extends boolean = false, O extends Sync extends true ? RunnerOptionsSync<Node, Result> : RunnerOptions<Node, Result> = Sync extends true ? RunnerOptionsSync<Node, Result> : RunnerOptions<Node, Result>> {
94
+ /** The map of traversal results */
95
+ readonly results: Map<Node, Result>;
96
+ /** The map of PromiseSettledResult objects */
97
+ readonly settled: SettledMap<Node, Result>;
98
+ /** Set of dependents (direct & transitive) on each node */
99
+ readonly dependents: Map<Node, Set<Node>>;
100
+ /** Set of direct dependents on each node */
101
+ readonly directDependents: Map<Node, Set<Node>>;
102
+ /** Options provided to constructor */
103
+ readonly options: O;
104
+ /**
105
+ * AbortController used internally to abort the process.
106
+ *
107
+ * This is internal only, and triggering it at the wrong time may cause
108
+ * undefined and unsupported behavior. Do not use!
109
+ *
110
+ * Instead, if you want to be able to abort the walk at any time, provide
111
+ * your own AbortSignal in the opions.
112
+ * @internal
113
+ */
114
+ readonly abortController: AbortController;
115
+ /** True if we are in failFast mode */
116
+ readonly failFast: boolean;
117
+ /** Rejections and Errors encountered in the traversal */
118
+ readonly errors: unknown[];
119
+ /**
120
+ * Function defining the callsite where the traversal was initiated,
121
+ * used for Error.captureStackTrace.
122
+ */
123
+ readonly from: Callable;
124
+ constructor(options: O, from?: Callable);
125
+ /** Initiate the graph traversal, resolving/returning when complete */
126
+ abstract run(): Sync extends true ? void : Promise<void>;
127
+ /** Get the dependencies of a given node */
128
+ abstract getDeps(n: Node): Sync extends true ? Node[] : Promise<Node[]>;
129
+ /** Visit a node. Calls `options.visit()` */
130
+ abstract visit(n: Node, path: Node[], depResults: DepResults<Node, Result>): Sync extends true ? Result : Promise<Result>;
131
+ /**
132
+ * Calls the `options.onCycle()` method when a cycle is detected.
133
+ */
134
+ abstract onCycle(n: Node, cycle: Node[], path: Node[]): Sync extends true ? void : void | Promise<void>;
135
+ /**
136
+ * For a Node `n` that depends directly or transitively on Node `d`, find the
137
+ * shortest known dependency path from `n` to `d`. This is done by walking
138
+ * backwards breadth-first up the dependency relations from `d` until `n` is
139
+ * found.
140
+ *
141
+ * If no known path can be found, then `undefined` is returned. Otherwise,
142
+ * a path array is returned that starts with `n` and ends with `d`.
143
+ *
144
+ * Note that self-referential links are never considered, since they're
145
+ * by definition cyclical.
146
+ */
147
+ route(n: Node, d: Node): undefined | [n: Node, ...path: Node[]];
148
+ /**
149
+ * If the dependency from `n -> d` at the specified path would
150
+ * cause a cycle, then call the onCycle method and return true.
151
+ * Oherwise, assign the appropriate entries in the dependency
152
+ * tracking sets, and return false.
153
+ * @internal
154
+ */
155
+ cycleCheck(n: Node, path: Node[], d: Node): boolean;
156
+ /**
157
+ * Method that handles errors raised by visits.
158
+ * @internal
159
+ */
160
+ handleError(er: unknown, n: Node, path: Node[]): void;
161
+ /**
162
+ * Method that handles successful visit results
163
+ * @internal
164
+ */
165
+ handleValue(value: Result, n: Node): void;
166
+ }
167
+ /** Asynchronous graph runner */
168
+ export declare class Runner<Node, Result> extends RunnerBase<Node, Result, false, RunnerOptions<Node, Result>> {
169
+ #private;
170
+ /** Map of nodes currently awaiting completion */
171
+ readonly running: Map<Node, Promise<void>>;
172
+ /** Track which node's promise is waiting for which other nodes */
173
+ readonly promiseWaiting: Map<Node, Set<Node>>;
174
+ getDeps(n: Node): Promise<Node[]>;
175
+ visit(n: Node, path: Node[], depResults: DepResults<Node, Result>): Promise<Result>;
176
+ onCycle(n: Node, cycle: Node[], path: Node[]): Promise<void>;
177
+ run(): Promise<void>;
178
+ }
179
+ /** Synchronous graph runner */
180
+ export declare class RunnerSync<Node, Result> extends RunnerBase<Node, Result, true, RunnerOptionsSync<Node, Result>> {
181
+ #private;
182
+ getDeps(n: Node): Node[];
183
+ visit(n: Node, path: Node[], depResults: DepResults<Node, Result>): Result;
184
+ onCycle(n: Node, cycle: Node[], path: Node[]): void;
185
+ run(): Map<Node, Result>;
186
+ }
187
+ /**
188
+ * Asynchronous graph traversal method
189
+ *
190
+ * If `failFast:false` is set in the options, then an AggregateError
191
+ * will be raised if there were any failures. Otherwise, a normal Error
192
+ * is raised on failure.
193
+ */
194
+ export declare const graphRun: <Node, Result>(options: RunnerOptions<Node, Result>) => Promise<Map<Node, Result>>;
195
+ /**
196
+ * Synchronous graph traversal method
197
+ *
198
+ * If `failFast:false` is set in the options, then an AggregateError
199
+ * will be thrown if there were any failures. Otherwise, a normal Error
200
+ * is thrown on failure.
201
+ */
202
+ export declare const graphRunSync: <Node, Result>(options: RunnerOptionsSync<Node, Result>) => Map<Node, Result>;
203
+ /**
204
+ * Asynchronous graph traversal, capturing all error/result
205
+ * statuses.
206
+ */
207
+ export declare const allSettled: <Node, Result>(options: RunnerOptions<Node, Result>) => Promise<SettledMap<Node, Result>>;
208
+ /**
209
+ * Synchronous graph traversal, capturing all error/result
210
+ * statuses.
211
+ */
212
+ export declare const allSettledSync: <Node, Result>(options: RunnerOptionsSync<Node, Result>) => SettledMap<Node, Result>;
213
+ /**
214
+ * Asynchronous graph traversal, returning the first successful visit.
215
+ * If all visits fail, then an AggregateError is raised with all errors
216
+ * encountered.
217
+ */
218
+ export declare const any: <Node, Result>(options: RunnerOptions<Node, Result>) => Promise<Result>;
219
+ /**
220
+ * Synchronous graph traversal, returning the first successful visit.
221
+ * If all visits fail, then an AggregateError is thrown with all errors
222
+ * encountered.
223
+ */
224
+ export declare const anySync: <Node, Result>(options: RunnerOptionsSync<Node, Result>) => Result;
225
+ /**
226
+ * Asynchronous graph traversal, resolving or rejecting when the first visit
227
+ * resolves or rejects.
228
+ */
229
+ export declare const race: <Node, Result>(options: RunnerOptions<Node, Result>) => Promise<Result>;
230
+ /**
231
+ * Synchronous graph traversal, returning or throwing when the first visit
232
+ * is completed.
233
+ */
234
+ export declare const raceSync: <Node, Result>(options: RunnerOptionsSync<Node, Result>) => Result;
package/dist/index.js ADDED
@@ -0,0 +1,539 @@
1
+ import { setMaxListeners } from 'node:events';
2
+ export const isGraphRunError = (er) => !!er &&
3
+ typeof er === 'object' &&
4
+ 'cause' in er &&
5
+ !!er.cause &&
6
+ typeof er.cause === 'object' &&
7
+ 'code' in er.cause &&
8
+ ((er.cause.code === 'GRAPHRUN_NO_NODES' &&
9
+ 'found' in er.cause &&
10
+ 'wanted' in er.cause &&
11
+ typeof er.cause.wanted === 'string') ||
12
+ (er.cause.code === 'GRAPHRUN_TRAVERSAL' &&
13
+ 'path' in er.cause &&
14
+ Array.isArray(er.cause.path) &&
15
+ 'node' in er.cause &&
16
+ 'cause' in er.cause) ||
17
+ er.cause.code === 'GRAPHRUN_CYCLE_WITHOUT_PATH');
18
+ /** Base class of Runner and RunnerSync */
19
+ export class RunnerBase {
20
+ /** The map of traversal results */
21
+ results = new Map();
22
+ /** The map of PromiseSettledResult objects */
23
+ settled = new Map();
24
+ /** Set of dependents (direct & transitive) on each node */
25
+ dependents = new Map();
26
+ /** Set of direct dependents on each node */
27
+ directDependents = new Map();
28
+ /** Options provided to constructor */
29
+ options;
30
+ /**
31
+ * AbortController used internally to abort the process.
32
+ *
33
+ * This is internal only, and triggering it at the wrong time may cause
34
+ * undefined and unsupported behavior. Do not use!
35
+ *
36
+ * Instead, if you want to be able to abort the walk at any time, provide
37
+ * your own AbortSignal in the opions.
38
+ * @internal
39
+ */
40
+ abortController;
41
+ /** True if we are in failFast mode */
42
+ failFast;
43
+ /** Rejections and Errors encountered in the traversal */
44
+ errors = [];
45
+ /**
46
+ * Function defining the callsite where the traversal was initiated,
47
+ * used for Error.captureStackTrace.
48
+ */
49
+ from;
50
+ constructor(options, from) {
51
+ const ac = new AbortController();
52
+ this.from = from ?? this.constructor;
53
+ this.abortController = ac;
54
+ setMaxListeners(Infinity, ac.signal);
55
+ this.options = options;
56
+ if (!options.graph.length) {
57
+ const er = new Error('no nodes provided to graph traversal', {
58
+ cause: {
59
+ code: 'GRAPHRUN_NO_NODES',
60
+ found: options.graph,
61
+ wanted: '[first: Node, ...rest: Node[]]',
62
+ },
63
+ });
64
+ Error.captureStackTrace(er, from);
65
+ throw er;
66
+ }
67
+ this.failFast = options.failFast !== false;
68
+ const { signal } = options;
69
+ if (signal !== undefined) {
70
+ signal.addEventListener('abort', reason => ac.abort(reason), {
71
+ once: true,
72
+ signal: ac.signal,
73
+ });
74
+ }
75
+ }
76
+ /**
77
+ * For a Node `n` that depends directly or transitively on Node `d`, find the
78
+ * shortest known dependency path from `n` to `d`. This is done by walking
79
+ * backwards breadth-first up the dependency relations from `d` until `n` is
80
+ * found.
81
+ *
82
+ * If no known path can be found, then `undefined` is returned. Otherwise,
83
+ * a path array is returned that starts with `n` and ends with `d`.
84
+ *
85
+ * Note that self-referential links are never considered, since they're
86
+ * by definition cyclical.
87
+ */
88
+ route(n, d) {
89
+ const dependents = this.dependents.get(d);
90
+ if (!dependents?.has(n))
91
+ return undefined;
92
+ const directDependents = this.directDependents.get(d);
93
+ /* c8 ignore next */
94
+ if (!directDependents)
95
+ return undefined;
96
+ if (directDependents.has(n)) {
97
+ return [n, d];
98
+ }
99
+ const queue = [
100
+ ...directDependents,
101
+ ].map(dd => [dd, d]);
102
+ let step = undefined;
103
+ while (undefined !== (step = queue.shift())) {
104
+ /* c8 ignore next */
105
+ if (!dependents.has(step[0]))
106
+ continue;
107
+ if (step[0] === n) {
108
+ return step;
109
+ }
110
+ const ddd = this.directDependents.get(step[0]);
111
+ if (ddd) {
112
+ for (const d of ddd) {
113
+ queue.push([d, ...step]);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ /**
119
+ * If the dependency from `n -> d` at the specified path would
120
+ * cause a cycle, then call the onCycle method and return true.
121
+ * Oherwise, assign the appropriate entries in the dependency
122
+ * tracking sets, and return false.
123
+ * @internal
124
+ */
125
+ cycleCheck(n, path, d) {
126
+ /* c8 ignore next */
127
+ const dependents = this.dependents.get(n) ?? new Set();
128
+ this.dependents.set(n, dependents);
129
+ const isCycle = dependents.has(d);
130
+ if (isCycle) {
131
+ const cycle = this.route(d, n);
132
+ /* c8 ignore start - impossible */
133
+ if (!cycle) {
134
+ throw new Error('cycle detected, but cycle route not found', {
135
+ cause: { code: 'GRAPHRUN_CYCLE_WITHOUT_PATH' },
136
+ });
137
+ }
138
+ /* c8 ignore stop */
139
+ cycle.unshift(n);
140
+ void this.onCycle(d, cycle, path);
141
+ return true;
142
+ }
143
+ const depDD = this.directDependents.get(d) ?? new Set();
144
+ this.directDependents.set(d, depDD);
145
+ depDD.add(n);
146
+ const depDependents = this.dependents.get(d) ?? new Set();
147
+ this.dependents.set(d, depDependents);
148
+ for (const n of dependents) {
149
+ depDependents.add(n);
150
+ }
151
+ depDependents.add(n);
152
+ return false;
153
+ }
154
+ /**
155
+ * Method that handles errors raised by visits.
156
+ * @internal
157
+ */
158
+ handleError(er, n, path) {
159
+ this.errors.push(er);
160
+ this.settled.set(n, {
161
+ status: 'rejected',
162
+ reason: er,
163
+ });
164
+ if (this.failFast) {
165
+ this.abortController.abort(er);
166
+ const e = new Error('failed graph traversal', {
167
+ cause: {
168
+ code: 'GRAPHRUN_TRAVERSAL',
169
+ node: n,
170
+ path,
171
+ cause: er,
172
+ },
173
+ });
174
+ Error.captureStackTrace(e, this.from);
175
+ throw e;
176
+ }
177
+ }
178
+ /**
179
+ * Method that handles successful visit results
180
+ * @internal
181
+ */
182
+ handleValue(value, n) {
183
+ this.results.set(n, value);
184
+ this.settled.set(n, {
185
+ status: 'fulfilled',
186
+ value,
187
+ });
188
+ }
189
+ }
190
+ /** Asynchronous graph runner */
191
+ export class Runner extends RunnerBase {
192
+ /** Map of nodes currently awaiting completion */
193
+ running = new Map();
194
+ /** Track which node's promise is waiting for which other nodes */
195
+ promiseWaiting = new Map();
196
+ async getDeps(n) {
197
+ /* c8 ignore next */
198
+ if (this.abortController.signal.aborted)
199
+ return [];
200
+ const deps = await this.options.getDeps(n);
201
+ for (const d of deps) {
202
+ const dependents = this.dependents.get(d) ?? new Set();
203
+ this.dependents.set(d, dependents);
204
+ dependents.add(n);
205
+ const depDD = this.directDependents.get(d) ?? new Set();
206
+ this.directDependents.set(d, depDD);
207
+ depDD.add(n);
208
+ }
209
+ return deps;
210
+ }
211
+ async visit(n, path, depResults) {
212
+ const { signal } = this.abortController;
213
+ return this.options.visit(n, signal, path, depResults);
214
+ }
215
+ async onCycle(n, cycle, path) {
216
+ /* c8 ignore next */
217
+ if (this.abortController.signal.aborted)
218
+ return;
219
+ await this.options.onCycle?.(n, cycle, path);
220
+ }
221
+ /**
222
+ * Check if node `target` is waiting (directly or transitively) for node `n`.
223
+ * If so, making `n` wait for `target` would create a deadlock.
224
+ */
225
+ #isWaitingFor(target, n, visited = new Set()) {
226
+ if (visited.has(target))
227
+ return false;
228
+ visited.add(target);
229
+ const waiting = this.promiseWaiting.get(target);
230
+ if (!waiting)
231
+ return false;
232
+ if (waiting.has(n))
233
+ return true;
234
+ for (const w of waiting) {
235
+ if (this.#isWaitingFor(w, n, visited))
236
+ return true;
237
+ }
238
+ return false;
239
+ }
240
+ async #walk(n, path) {
241
+ const r = this.running.get(n);
242
+ /* c8 ignore next */
243
+ if (r)
244
+ return r;
245
+ /* c8 ignore start */
246
+ if (this.settled.get(n))
247
+ return;
248
+ /* c8 ignore stop */
249
+ const p = this.#step(n, path).then(() => {
250
+ this.running.delete(n);
251
+ this.promiseWaiting.delete(n);
252
+ },
253
+ /* c8 ignore start - handled deeper in the chain */
254
+ (er) => {
255
+ this.running.delete(n);
256
+ this.promiseWaiting.delete(n);
257
+ throw er;
258
+ });
259
+ this.running.set(n, p);
260
+ return p;
261
+ }
262
+ async #step(n, path) {
263
+ const dependents = this.dependents.get(n) ?? new Set();
264
+ this.dependents.set(n, dependents);
265
+ const deps = await this.getDeps(n);
266
+ const awaiting = [];
267
+ const depPath = [...path, n];
268
+ // Initialize waiting set for this node
269
+ const waitingOn = new Set();
270
+ this.promiseWaiting.set(n, waitingOn);
271
+ for (const d of deps) {
272
+ /* c8 ignore next */
273
+ if (this.abortController.signal.aborted)
274
+ return;
275
+ // self-link, skip
276
+ if (d === n)
277
+ continue;
278
+ if (this.cycleCheck(n, depPath, d))
279
+ continue;
280
+ /* c8 ignore next */
281
+ if (this.settled.get(d))
282
+ continue;
283
+ const runningPromise = this.running.get(d);
284
+ if (runningPromise) {
285
+ // Check if d is already waiting for n (promise-level cycle)
286
+ if (this.#isWaitingFor(d, n)) {
287
+ // Treat as cycle to prevent deadlock
288
+ void this.onCycle(d, [n, d], depPath);
289
+ continue;
290
+ }
291
+ }
292
+ // Record that n is waiting for d
293
+ waitingOn.add(d);
294
+ awaiting.push(runningPromise ?? this.#walk(d, depPath));
295
+ }
296
+ /* c8 ignore next */
297
+ if (this.abortController.signal.aborted)
298
+ return;
299
+ await Promise.all(awaiting);
300
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
301
+ if (this.abortController.signal.aborted)
302
+ return;
303
+ const depRes = new Map(deps.map(d => [d, this.results.get(d)]));
304
+ try {
305
+ this.handleValue(await this.visit(n, path, depRes), n);
306
+ }
307
+ catch (er) {
308
+ this.handleError(er, n, path);
309
+ }
310
+ }
311
+ async run() {
312
+ const promises = [];
313
+ for (const n of this.options.graph) {
314
+ promises.push(this.#walk(n, []));
315
+ }
316
+ await Promise.all(promises);
317
+ }
318
+ }
319
+ /** Synchronous graph runner */
320
+ export class RunnerSync extends RunnerBase {
321
+ getDeps(n) {
322
+ if (this.abortController.signal.aborted)
323
+ return [];
324
+ return this.options.getDeps(n);
325
+ }
326
+ visit(n, path, depResults) {
327
+ const { signal } = this.abortController;
328
+ return this.options.visit(n, signal, path, depResults);
329
+ }
330
+ onCycle(n, cycle, path) {
331
+ /* c8 ignore next */
332
+ if (this.abortController.signal.aborted)
333
+ return;
334
+ this.options.onCycle?.(n, cycle, path);
335
+ }
336
+ #walk(n, path) {
337
+ /* c8 ignore start */
338
+ if (this.settled.get(n))
339
+ return;
340
+ /* c8 ignore stop */
341
+ this.#step(n, path);
342
+ }
343
+ #step(n, path) {
344
+ const dependents = this.dependents.get(n) ?? new Set();
345
+ this.dependents.set(n, dependents);
346
+ const deps = this.getDeps(n);
347
+ const depPath = [...path, n];
348
+ for (const d of deps) {
349
+ if (this.abortController.signal.aborted)
350
+ return;
351
+ /* c8 ignore next */
352
+ if (d === n)
353
+ continue;
354
+ if (this.cycleCheck(n, depPath, d))
355
+ continue;
356
+ if (!this.settled.get(d))
357
+ this.#walk(d, depPath);
358
+ }
359
+ if (this.abortController.signal.aborted)
360
+ return;
361
+ const depRes = new Map(deps.map(d => [d, this.results.get(d)]));
362
+ try {
363
+ this.handleValue(this.visit(n, path, depRes), n);
364
+ }
365
+ catch (er) {
366
+ this.handleError(er, n, path);
367
+ }
368
+ }
369
+ run() {
370
+ for (const n of this.options.graph) {
371
+ this.#walk(n, []);
372
+ }
373
+ return this.results;
374
+ }
375
+ }
376
+ /**
377
+ * Asynchronous graph traversal method
378
+ *
379
+ * If `failFast:false` is set in the options, then an AggregateError
380
+ * will be raised if there were any failures. Otherwise, a normal Error
381
+ * is raised on failure.
382
+ */
383
+ export const graphRun = async (options) => {
384
+ const runner = new Runner(options, graphRun);
385
+ await runner.run();
386
+ if (runner.errors.length) {
387
+ const e = new AggregateError(runner.errors, 'failed graph traversal');
388
+ Error.captureStackTrace(e, graphRun);
389
+ throw e;
390
+ }
391
+ return runner.results;
392
+ };
393
+ /**
394
+ * Synchronous graph traversal method
395
+ *
396
+ * If `failFast:false` is set in the options, then an AggregateError
397
+ * will be thrown if there were any failures. Otherwise, a normal Error
398
+ * is thrown on failure.
399
+ */
400
+ export const graphRunSync = (options) => {
401
+ const runner = new RunnerSync(options, graphRunSync);
402
+ runner.run();
403
+ if (runner.errors.length) {
404
+ const e = new AggregateError(runner.errors, 'failed graph traversal');
405
+ Error.captureStackTrace(e, graphRunSync);
406
+ throw e;
407
+ }
408
+ return runner.results;
409
+ };
410
+ /**
411
+ * Asynchronous graph traversal, capturing all error/result
412
+ * statuses.
413
+ */
414
+ export const allSettled = async (options) => {
415
+ const runner = new Runner({ ...options, failFast: false }, allSettled);
416
+ await runner.run();
417
+ return runner.settled;
418
+ };
419
+ /**
420
+ * Synchronous graph traversal, capturing all error/result
421
+ * statuses.
422
+ */
423
+ export const allSettledSync = (options) => {
424
+ const runner = new RunnerSync({ ...options, failFast: false }, allSettledSync);
425
+ runner.run();
426
+ return runner.settled;
427
+ };
428
+ /**
429
+ * Asynchronous graph traversal, returning the first successful visit.
430
+ * If all visits fail, then an AggregateError is raised with all errors
431
+ * encountered.
432
+ */
433
+ export const any = async (options) => {
434
+ const ac = new AbortController();
435
+ let result;
436
+ let found = false;
437
+ const runner = new Runner({
438
+ ...options,
439
+ failFast: false,
440
+ signal: ac.signal,
441
+ visit: async (node, signal, path, depResults) => {
442
+ try {
443
+ result = await options.visit(node, signal, path, depResults);
444
+ found = true;
445
+ ac.abort('found');
446
+ }
447
+ catch { }
448
+ return result;
449
+ },
450
+ }, any);
451
+ await runner.run();
452
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
453
+ if (!found) {
454
+ const e = new AggregateError(runner.errors, 'failed graph traversal');
455
+ Error.captureStackTrace(e, any);
456
+ throw e;
457
+ }
458
+ return result;
459
+ };
460
+ /**
461
+ * Synchronous graph traversal, returning the first successful visit.
462
+ * If all visits fail, then an AggregateError is thrown with all errors
463
+ * encountered.
464
+ */
465
+ export const anySync = (options) => {
466
+ const ac = new AbortController();
467
+ let result;
468
+ let found = false;
469
+ const runner = new RunnerSync({
470
+ ...options,
471
+ failFast: false,
472
+ signal: ac.signal,
473
+ visit: (node, signal, path, depResults) => {
474
+ try {
475
+ result = options.visit(node, signal, path, depResults);
476
+ found = true;
477
+ ac.abort('found');
478
+ }
479
+ catch { }
480
+ return result;
481
+ },
482
+ }, anySync);
483
+ runner.run();
484
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
485
+ if (!found) {
486
+ const e = new AggregateError(runner.errors, 'failed graph traversal');
487
+ Error.captureStackTrace(e, anySync);
488
+ throw e;
489
+ }
490
+ return result;
491
+ };
492
+ /**
493
+ * Asynchronous graph traversal, resolving or rejecting when the first visit
494
+ * resolves or rejects.
495
+ */
496
+ export const race = async (options) => {
497
+ const ac = new AbortController();
498
+ let result;
499
+ const runner = new Runner({
500
+ ...options,
501
+ failFast: true,
502
+ signal: ac.signal,
503
+ visit: async (node, signal, path, depResults) => {
504
+ try {
505
+ result = await options.visit(node, signal, path, depResults);
506
+ ac.abort('found');
507
+ /* c8 ignore next */
508
+ }
509
+ catch { }
510
+ return result;
511
+ },
512
+ }, race);
513
+ await runner.run();
514
+ return result;
515
+ };
516
+ /**
517
+ * Synchronous graph traversal, returning or throwing when the first visit
518
+ * is completed.
519
+ */
520
+ export const raceSync = (options) => {
521
+ const ac = new AbortController();
522
+ let result;
523
+ const runner = new RunnerSync({
524
+ ...options,
525
+ failFast: true,
526
+ signal: ac.signal,
527
+ visit: (node, signal, path, depResults) => {
528
+ try {
529
+ result = options.visit(node, signal, path, depResults);
530
+ ac.abort('found');
531
+ /* c8 ignore next */
532
+ }
533
+ catch { }
534
+ return result;
535
+ },
536
+ }, race);
537
+ runner.run();
538
+ return result;
539
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vltpkg/graph-run",
3
3
  "description": "Run operations on a graph, maximizing parallelism",
4
- "version": "1.0.0-rc.23",
4
+ "version": "1.0.0-rc.25",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/vltpkg/vltpkg.git",