@travetto/worker 3.4.1 → 4.0.0-rc.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 CHANGED
@@ -16,16 +16,14 @@ yarn add @travetto/worker
16
16
  This module provides the necessary primitives for handling dependent workers. A worker can be an individual actor or could be a pool of workers. Node provides ipc (inter-process communication) functionality out of the box. This module builds upon that by providing enhanced event management, richer process management, as well as constructs for orchestrating a conversation between two processes.
17
17
 
18
18
  ## Execution Pools
19
- With respect to managing multiple executions, [WorkPool](https://github.com/travetto/travetto/tree/main/module/worker/src/pool.ts#L31) is provided to allow for concurrent operation, and processing of jobs concurrently. To manage the flow of jobs, there are various [WorkSet](https://github.com/travetto/travetto/tree/main/module/worker/src/input/types.ts#L4) implementation that allow for a wide range of use cases.
20
-
21
- The only provided [WorkSet](https://github.com/travetto/travetto/tree/main/module/worker/src/input/types.ts#L4) is the [IterableWorkSet](https://github.com/travetto/travetto/tree/main/module/worker/src/input/iterable.ts#L11) which supports all `Iterable` and `Iterator` sources. Additionally, the module provides [ManualAsyncIterator](https://github.com/travetto/travetto/tree/main/module/worker/src/input/async-iterator.ts#L6) which allows for manual control of iteration, which is useful for event driven work loads.
19
+ With respect to managing multiple executions, [WorkPool](https://github.com/travetto/travetto/tree/main/module/worker/src/pool.ts#L35) is provided to allow for concurrent operation, and processing of jobs concurrently. To manage the flow of jobs, [WorkQueue](https://github.com/travetto/travetto/tree/main/module/worker/src/queue.ts#L6) is provided to support a wide range of use cases. [WorkQueue](https://github.com/travetto/travetto/tree/main/module/worker/src/queue.ts#L6) allows for manual control of iteration, which is useful for event driven work loads.
22
20
 
23
21
  Below is a pool that will convert images on demand, while queuing as needed.
24
22
 
25
23
  **Code: Image processing queue, with a fixed batch/pool size**
26
24
  ```typescript
27
25
  import { ExecUtil, ExecutionState } from '@travetto/base';
28
- import { Worker, WorkPool, IterableWorkSet, ManualAsyncIterator } from '@travetto/worker';
26
+ import { Worker, WorkPool, WorkQueue } from '@travetto/worker';
29
27
 
30
28
  class ImageProcessor implements Worker<string> {
31
29
  active = false;
@@ -51,20 +49,17 @@ class ImageProcessor implements Worker<string> {
51
49
  }
52
50
  }
53
51
 
54
- export class ImageCompressor extends WorkPool<string> {
55
-
56
- pendingImages = new ManualAsyncIterator<string>();
52
+ export class ImageCompressor {
57
53
 
58
- constructor() {
59
- super(async () => new ImageProcessor());
60
- }
54
+ changes: AsyncIterable<unknown>;
55
+ pendingImages = new WorkQueue<string>();
61
56
 
62
57
  begin(): void {
63
- this.process(new IterableWorkSet(this.pendingImages));
58
+ this.changes ??= WorkPool.runStream(() => new ImageProcessor(), this.pendingImages);
64
59
  }
65
60
 
66
61
  convert(...images: string[]): void {
67
- this.pendingImages.add(images);
62
+ this.pendingImages.addAll(images);
68
63
  }
69
64
  }
70
65
  ```
@@ -75,7 +70,7 @@ Once a pool is constructed, it can be shutdown by calling the `.shutdown()` meth
75
70
  Within the `comm` package, there is support for two primary communication elements: [ChildCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/child.ts#L6) and [ParentCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/parent.ts#L10). Usually [ParentCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/parent.ts#L10) indicates it is the owner of the sub process. [ChildCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/child.ts#L6) indicates that it has been created/spawned/forked by the parent and will communicate back to it's parent. This generally means that a [ParentCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/parent.ts#L10) can be destroyed (i.e. killing the subprocess) where a [ChildCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/child.ts#L6) can only exit the process, but the channel cannot be destroyed.
76
71
 
77
72
  ### IPC as a Worker
78
- A common pattern is to want to model a sub process as a worker, to be a valid candidate in a [WorkPool](https://github.com/travetto/travetto/tree/main/module/worker/src/pool.ts#L31). The [WorkUtil](https://github.com/travetto/travetto/tree/main/module/worker/src/util.ts#L14) class provides a utility to facilitate this desire.
73
+ A common pattern is to want to model a sub process as a worker, to be a valid candidate in a [WorkPool](https://github.com/travetto/travetto/tree/main/module/worker/src/pool.ts#L35). The [WorkUtil](https://github.com/travetto/travetto/tree/main/module/worker/src/util.ts#L14) class provides a utility to facilitate this desire.
79
74
 
80
75
  **Code: Spawned Worker**
81
76
  ```typescript
@@ -121,11 +116,11 @@ When creating your work, via process spawning, you will need to provide the scri
121
116
  **Code: Spawning Pool**
122
117
  ```typescript
123
118
  import { ExecUtil } from '@travetto/base';
124
- import { WorkPool, WorkUtil, IterableWorkSet } from '@travetto/worker';
119
+ import { WorkPool, WorkUtil } from '@travetto/worker';
125
120
 
126
121
  export async function main(): Promise<void> {
127
- const pool = new WorkPool(() =>
128
- WorkUtil.spawnedWorker<{ data: number }, number>(
122
+ await WorkPool.run(
123
+ () => WorkUtil.spawnedWorker<{ data: number }, number>(
129
124
  () => ExecUtil.spawn('trv', ['main', '@travetto/worker/doc/spawned.ts']),
130
125
  ch => ch.once('ready'), // Wait for child to indicate it is ready
131
126
  async (channel, inp) => {
@@ -140,15 +135,13 @@ export async function main(): Promise<void> {
140
135
  throw new Error(`Did not get the double: inp=${inp}, data=${data}`);
141
136
  }
142
137
  }
143
- )
144
- );
145
- await pool.process(new IterableWorkSet([1, 2, 3, 4, 5]));
138
+ ), [1, 2, 3, 4, 5]);
146
139
  }
147
140
  ```
148
141
 
149
142
  **Code: Spawned Worker**
150
143
  ```typescript
151
- import timers from 'timers/promises';
144
+ import timers from 'node:timers/promises';
152
145
  import { ChildCommChannel } from '@travetto/worker';
153
146
 
154
147
  export async function main(): Promise<void> {
package/__index__.ts CHANGED
@@ -2,11 +2,9 @@ export * from './src/comm/channel';
2
2
  export * from './src/comm/child';
3
3
  export * from './src/comm/parent';
4
4
  export * from './src/comm/types';
5
- export * from './src/input/iterable';
6
- export * from './src/input/async-iterator';
7
- export * from './src/input/types';
8
5
  export * from './src/support/barrier';
9
6
  export * from './src/support/timeout';
10
7
  export * from './src/support/error';
8
+ export * from './src/queue';
11
9
  export * from './src/pool';
12
10
  export * from './src/util';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/worker",
3
- "version": "3.4.1",
3
+ "version": "4.0.0-rc.0",
4
4
  "description": "Process management utilities, with a focus on inter-process communication",
5
5
  "keywords": [
6
6
  "exec",
@@ -25,7 +25,7 @@
25
25
  "directory": "module/worker"
26
26
  },
27
27
  "dependencies": {
28
- "@travetto/base": "^3.4.1",
28
+ "@travetto/base": "^4.0.0-rc.0",
29
29
  "generic-pool": "^3.9.0"
30
30
  },
31
31
  "travetto": {
@@ -1,5 +1,5 @@
1
- import { ChildProcess } from 'child_process';
2
- import { EventEmitter } from 'events';
1
+ import { ChildProcess } from 'node:child_process';
2
+ import { EventEmitter } from 'node:events';
3
3
 
4
4
  import { ExecUtil } from '@travetto/base';
5
5
 
@@ -1,4 +1,4 @@
1
- import { ChildProcess } from 'child_process';
1
+ import { ChildProcess } from 'node:child_process';
2
2
 
3
3
  import { ShutdownManager, ExecutionState } from '@travetto/base';
4
4
 
@@ -13,7 +13,7 @@ export class ParentCommChannel<U = unknown> extends ProcessCommChannel<ChildProc
13
13
 
14
14
  constructor(state: ExecutionState) {
15
15
  super(state.process);
16
- ShutdownManager.onShutdown(this, { close: () => this.destroy() });
16
+ ShutdownManager.onGracefulShutdown(() => this.destroy(), this);
17
17
  this.#complete = state.result
18
18
  .finally(() => { this.proc = undefined; });
19
19
  }
package/src/pool.ts CHANGED
@@ -1,211 +1,178 @@
1
- import os from 'os';
2
1
  import gp from 'generic-pool';
2
+ import os from 'node:os';
3
+ import timers from 'node:timers/promises';
3
4
 
4
- import { GlobalEnv, ShutdownManager, TimeUtil } from '@travetto/base';
5
+ import { Env, Util } from '@travetto/base';
5
6
 
6
- import { WorkSet } from './input/types';
7
- import { ManualAsyncIterator } from '../src/input/async-iterator';
7
+ import { WorkQueue } from './queue';
8
+
9
+ type ItrSource<I> = Iterable<I> | AsyncIterable<I>;
8
10
 
9
11
  /**
10
12
  * Worker definition
11
13
  */
12
- export interface Worker<X> {
13
- active: boolean;
14
- id: unknown;
14
+ export interface Worker<I, O = unknown> {
15
+ active?: boolean;
16
+ id?: unknown;
15
17
  init?(): Promise<unknown>;
16
- execute(input: X): Promise<unknown>;
17
- destroy(): Promise<void>;
18
+ execute(input: I, idx: number): Promise<O>;
19
+ destroy?(): Promise<void>;
18
20
  release?(): unknown;
19
21
  }
20
22
 
21
- type WorkPoolProcessConfig<X> = {
22
- shutdownOnComplete?: boolean;
23
- onComplete?: (ev: WorkCompletionEvent<X>) => (void | Promise<void>);
23
+ type WorkerInput<I, O> = (() => Worker<I, O>) | ((input: I, inputIdx: number) => Promise<O>);
24
+ type WorkPoolConfig<I, O> = gp.Options & {
25
+ onComplete?: (output: O, input: I, finishIdx: number) => void;
26
+ onError?(ev: Error, input: I, finishIdx: number): (unknown | Promise<unknown>);
27
+ shutdown?: AbortSignal;
24
28
  };
25
29
 
26
- type WorkCompletionEvent<X> = { idx: number, total?: number, value: X };
30
+ const isWorkerFactory = <I, O>(o: WorkerInput<I, O>): o is (() => Worker<I, O>) => o.length === 0;
27
31
 
28
32
  /**
29
33
  * Work pool support
30
34
  */
31
- export class WorkPool<X> {
35
+ export class WorkPool {
32
36
 
33
37
  static MAX_SIZE = os.cpus().length - 1;
34
38
  static DEFAULT_SIZE = Math.min(WorkPool.MAX_SIZE, 4);
35
39
 
36
- /**
37
- * Generic-pool pool
38
- */
39
- #pool: gp.Pool<Worker<X>>;
40
- /**
41
- * Number of acquisitions in process
42
- */
43
- #pendingAcquires = 0;
44
- /**
45
- * List of errors during processing
46
- */
47
- #errors: Error[] = [];
48
-
49
- /**
50
- * Error count during creation
51
- */
52
- #createErrors = 0;
40
+ /** Build worker pool */
41
+ static #buildPool<I, O>(worker: WorkerInput<I, O>, opts?: WorkPoolConfig<I, O>): gp.Pool<Worker<I, O>> {
42
+ let pendingAcquires = 0;
53
43
 
54
- /**
55
- * Are we tracing
56
- */
57
- #trace: boolean;
44
+ const trace = /@travetto\/worker/.test(Env.DEBUG.val ?? '');
58
45
 
59
- /**
60
- * Cleanup for shutdown
61
- */
62
- #shutdownCleanup?: () => void;
63
-
64
- /**
65
- *
66
- * @param getWorker Produces a new worker for the pool
67
- * @param opts Pool options
68
- */
69
- constructor(getWorker: () => Promise<Worker<X>> | Worker<X>, opts?: gp.Options) {
70
- const args = {
46
+ // Create the pool
47
+ const pool = gp.createPool({
48
+ async create() {
49
+ try {
50
+ pendingAcquires += 1;
51
+ const res = isWorkerFactory(worker) ? await worker() : { execute: worker };
52
+ res.id ??= Util.shortHash(`${Math.random()}`);
53
+
54
+ if (res.init) {
55
+ await res.init();
56
+ }
57
+
58
+ return res;
59
+ } finally {
60
+ pendingAcquires -= 1;
61
+ }
62
+ },
63
+ async destroy(x) {
64
+ if (trace) {
65
+ console.debug('Destroying', { pid: process.pid, worker: x.id });
66
+ }
67
+ return x.destroy?.();
68
+ },
69
+ validate: async (x: Worker<I, O>) => x.active ?? true
70
+ }, {
71
71
  max: WorkPool.DEFAULT_SIZE,
72
72
  min: 1,
73
73
  evictionRunIntervalMillis: 5000,
74
74
  ...(opts ?? {}),
75
- };
76
-
77
- // Create the pool
78
- this.#pool = gp.createPool({
79
- create: () => this.#createAndTrack(getWorker, args),
80
- destroy: x => this.destroy(x),
81
- validate: async (x: Worker<X>) => x.active
82
- }, args);
83
-
84
- this.#shutdownCleanup = ShutdownManager.onShutdown(`worker.pool.${this.constructor.name}`, () => {
85
- this.#shutdownCleanup = undefined;
86
- this.shutdown();
87
75
  });
88
76
 
89
- this.#trace = !!GlobalEnv.debug?.includes('@travetto/worker');
90
- }
91
-
92
- /**
93
- * Creates and tracks new worker
94
- */
95
- async #createAndTrack(getWorker: () => Promise<Worker<X>> | Worker<X>, opts: gp.Options): Promise<Worker<X>> {
96
- try {
97
- this.#pendingAcquires += 1;
98
- const res = await getWorker();
99
-
100
- if (res.init) {
101
- await res.init();
102
- }
103
-
104
- this.#createErrors = 0; // Reset errors on success
105
77
 
106
- return res;
107
- } catch (err) {
108
- if (this.#createErrors++ > opts.max!) { // If error count is bigger than pool size, we broke
109
- console.error('Failed in creating pool', { error: err });
110
- await ShutdownManager.exit(1);
78
+ // Listen for shutdown
79
+ opts?.shutdown?.addEventListener('abort', async () => {
80
+ while (pendingAcquires) {
81
+ await timers.setTimeout(10);
111
82
  }
112
- throw err;
113
- } finally {
114
- this.#pendingAcquires -= 1;
115
- }
116
- }
83
+ await pool.drain();
84
+ await pool.clear();
85
+ });
117
86
 
118
- /**
119
- * Destroy the worker
120
- */
121
- async destroy(worker: Worker<X>): Promise<void> {
122
- if (this.#trace) {
123
- console.debug('Destroying', { pid: process.pid, worker: worker.id });
124
- }
125
- return worker.destroy();
87
+ return pool;
126
88
  }
127
89
 
128
90
  /**
129
- * Free worker on completion
91
+ * Process a given input source and worker, and fire on completion
130
92
  */
131
- async release(worker: Worker<X>): Promise<void> {
132
- if (this.#trace) {
133
- console.debug('Releasing', { pid: process.pid, worker: worker.id });
134
- }
135
- try {
136
- if (worker.active) {
137
- try {
138
- await worker.release?.();
139
- } catch { }
140
- await this.#pool.release(worker);
141
- } else {
142
- await this.#pool.destroy(worker);
143
- }
144
- } catch { }
145
- }
93
+ static async run<I, O>(workerFactory: WorkerInput<I, O>, src: ItrSource<I>, opts: WorkPoolConfig<I, O> = {}): Promise<void> {
146
94
 
147
- /**
148
- * Process a given input source
149
- */
150
- async process(src: WorkSet<X>, cfg: WorkPoolProcessConfig<X> = {}): Promise<void> {
95
+ const trace = /@travetto\/worker/.test(Env.DEBUG.val ?? '');
151
96
  const pending = new Set<Promise<unknown>>();
152
- let count = 0;
97
+ const errors: Error[] = [];
98
+ let inputIdx = 0;
99
+ let finishIdx = 0;
153
100
 
154
- if (src.size && cfg.onComplete) {
155
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
156
- cfg.onComplete({ value: undefined as X, idx: 0, total: src.size });
157
- }
101
+ const pool = this.#buildPool(workerFactory, opts);
158
102
 
159
- while (await src.hasNext()) {
160
- const worker = (await this.#pool.acquire())!;
161
- if (this.#trace) {
103
+ for await (const nextInput of src) {
104
+ const worker = await pool.acquire()!;
105
+
106
+ if (trace) {
162
107
  console.debug('Acquired', { pid: process.pid, worker: worker.id });
163
108
  }
164
- const nextInput = await src.next();
165
109
 
166
- const completion = worker.execute(nextInput)
167
- .catch(err => this.#errors.push(err)) // Catch error
168
- .finally(() => this.release(worker))
169
- .finally(() => cfg.onComplete?.({ value: nextInput, idx: count += 1, total: src.size }));
110
+ const completion = worker.execute(nextInput, inputIdx += 1)
111
+ .then(v => opts.onComplete?.(v, nextInput, finishIdx += 1))
112
+ .catch(err => {
113
+ errors.push(err);
114
+ opts?.onError?.(err, nextInput, finishIdx += 1);
115
+ }) // Catch error
116
+ .finally(async () => {
117
+ if (trace) {
118
+ console.debug('Releasing', { pid: process.pid, worker: worker.id });
119
+ }
120
+ try {
121
+ if (worker.active ?? true) {
122
+ try {
123
+ await worker.release?.();
124
+ } catch { }
125
+ await pool.release(worker);
126
+ } else {
127
+ await pool.destroy(worker);
128
+ }
129
+ } catch { }
130
+ });
170
131
 
171
132
  completion.finally(() => pending.delete(completion));
172
-
173
133
  pending.add(completion);
174
134
  }
175
135
 
176
- try {
177
- await Promise.all(Array.from(pending));
136
+ await Promise.all(Array.from(pending));
178
137
 
179
- if (this.#errors.length) {
180
- throw this.#errors[0];
181
- }
182
- } finally {
183
- if (cfg.shutdownOnComplete !== false) {
184
- await this.shutdown();
185
- }
138
+ if (errors.length) {
139
+ throw errors[0];
186
140
  }
187
141
  }
188
142
 
189
143
  /**
190
- * Process a given input source as an async iterable, emitting on completion
144
+ * Process a given input source as an async iterable
191
145
  */
192
- iterateProcess(src: WorkSet<X>, shutdownOnComplete?: boolean): AsyncIterable<WorkCompletionEvent<X>> {
193
- const itr = new ManualAsyncIterator<WorkCompletionEvent<X>>();
194
- const res = this.process(src, { onComplete: ev => itr.add(ev), shutdownOnComplete });
146
+ static runStream<I, O>(worker: WorkerInput<I, O>, input: ItrSource<I>, opts?: WorkPoolConfig<I, O>): AsyncIterable<O> {
147
+ const itr = new WorkQueue<O>();
148
+ const res = this.run(worker, input, {
149
+ ...opts,
150
+ onComplete: (ev, inp, finishIdx) => {
151
+ itr.add(ev);
152
+ opts?.onComplete?.(ev, inp, finishIdx);
153
+ }
154
+ });
195
155
  res.finally(() => itr.close());
196
156
  return itr;
197
157
  }
198
158
 
199
159
  /**
200
- * Shutdown pool
160
+ * Process a given input source as an async iterable with progress information
201
161
  */
202
- async shutdown(): Promise<void> {
203
- this.#shutdownCleanup?.();
204
-
205
- while (this.#pendingAcquires) {
206
- await TimeUtil.wait(10);
207
- }
208
- await this.#pool.drain();
209
- await this.#pool.clear();
162
+ static runStreamProgress<I, O>(worker: WorkerInput<I, O>, input: ItrSource<I>, total: number, opts?: WorkPoolConfig<I, O>): AsyncIterable<{
163
+ idx: number;
164
+ value: O;
165
+ total: number;
166
+ }> {
167
+ const itr = new WorkQueue<{ idx: number, value: O, total: number }>();
168
+ const res = this.run(worker, input, {
169
+ ...opts,
170
+ onComplete: (ev, inp, finishIdx) => {
171
+ itr.add({ value: ev, idx: finishIdx, total });
172
+ opts?.onComplete?.(ev, inp, finishIdx);
173
+ }
174
+ });
175
+ res.finally(() => itr.close());
176
+ return itr;
210
177
  }
211
- }
178
+ }
@@ -1,9 +1,9 @@
1
1
  import { Util } from '@travetto/base';
2
2
 
3
3
  /**
4
- * Manual async iterator. Items are added manually, and consumed asynchronously
4
+ * WorkQueue, a manual async iterator. Items are added manually, and consumed asynchronously
5
5
  */
6
- export class ManualAsyncIterator<X> implements AsyncIterator<X>, AsyncIterable<X> {
6
+ export class WorkQueue<X> implements AsyncIterator<X>, AsyncIterable<X> {
7
7
 
8
8
  #queue: X[] = [];
9
9
  #done = false;
@@ -17,6 +17,9 @@ export class ManualAsyncIterator<X> implements AsyncIterator<X>, AsyncIterable<X
17
17
  this.#queue.push(...initial);
18
18
  this.#size = this.#queue.length;
19
19
  signal?.addEventListener('abort', () => this.close());
20
+ if (signal?.aborted) {
21
+ this.close();
22
+ }
20
23
  }
21
24
 
22
25
  // Allow for iteration
@@ -39,14 +42,20 @@ export class ManualAsyncIterator<X> implements AsyncIterator<X>, AsyncIterable<X
39
42
  * Queue next event to fire
40
43
  * @param {boolean} immediate Determines if item(s) should be append or prepended to the queue
41
44
  */
42
- add(item: X | X[], immediate = false): void {
43
- item = Array.isArray(item) ? item : [item];
44
- if (!immediate) {
45
- this.#queue.push(...item);
46
- } else {
47
- this.#queue.unshift(...item);
48
- }
49
- this.#size += item.length;
45
+ add(item: X, immediate = false): void {
46
+ this.#queue[immediate ? 'unshift' : 'push'](item);
47
+ this.#size += 1;
48
+ this.#ready.resolve();
49
+ }
50
+
51
+ /**
52
+ * Queue a list of data to stream
53
+ * @param {boolean} immediate Determines if item(s) should be append or prepended to the queue
54
+ */
55
+ addAll(items: Iterable<X>): void {
56
+ const copy = [...items];
57
+ this.#queue.push(...copy);
58
+ this.#size += copy.length;
50
59
  this.#ready.resolve();
51
60
  }
52
61
 
@@ -1,6 +1,6 @@
1
- import { setTimeout } from 'timers/promises';
1
+ import { setTimeout } from 'node:timers/promises';
2
2
 
3
- import { ShutdownManager, TimeSpan, Util } from '@travetto/base';
3
+ import { TimeSpan, Util } from '@travetto/base';
4
4
 
5
5
  import { Timeout } from './timeout';
6
6
 
@@ -19,7 +19,11 @@ export class Barrier {
19
19
  static listenForUnhandled(): Promise<never> & { cancel: () => void } {
20
20
  const uncaught = Util.resolvablePromise<never>();
21
21
  const uncaughtWithCancel: typeof uncaught & { cancel?: () => void } = uncaught;
22
- const cancel = ShutdownManager.onUnhandled(err => { setTimeout(1).then(() => uncaught.reject(err)); return true; }, 0);
22
+ const onError = (err: Error): void => { setTimeout(1).then(() => uncaught.reject(err)); };
23
+ process.on('unhandledRejection', onError).on('uncaughtException', onError);
24
+ const cancel = (): void => {
25
+ process.off('unhandledRejection', onError).off('unhandledException', onError);
26
+ };
23
27
  uncaughtWithCancel.cancel = (): void => {
24
28
  cancel(); // Remove the handler
25
29
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -1,3 +1,4 @@
1
+ import { clearTimeout } from 'node:timers';
1
2
  import { TimeSpan, TimeUtil, Util } from '@travetto/base';
2
3
  import { ExecutionError } from './error';
3
4
 
@@ -6,7 +7,7 @@ import { ExecutionError } from './error';
6
7
  */
7
8
  export class Timeout extends ExecutionError {
8
9
 
9
- #id: NodeJS.Timer | undefined;
10
+ #id: ReturnType<typeof setTimeout> | undefined;
10
11
  #promise = Util.resolvablePromise();
11
12
  #duration: number;
12
13
 
@@ -1,73 +0,0 @@
1
- import { WorkSet } from './types';
2
-
3
- type Itr<T> = Iterator<T> | AsyncIterator<T>;
4
-
5
- const hasAsyncItr = (o: unknown): o is AsyncIterable<unknown> => !!o && typeof o === 'object' && Symbol.asyncIterator in o;
6
- const hasItr = (o: unknown): o is Iterable<unknown> => !!o && typeof o === 'object' && Symbol.iterator in o;
7
-
8
- /**
9
- * Basic input source given an iterable input
10
- */
11
- export class IterableWorkSet<X> implements WorkSet<X> {
12
-
13
- #src: Itr<X>;
14
- #ondeck?: X;
15
- #done = false;
16
- #size?: number;
17
-
18
- constructor(src: Iterable<X> | AsyncIterable<X> | (() => Generator<X>) | (() => AsyncGenerator<X>) | Itr<X>) {
19
- if ('next' in src) {
20
- this.#src = src;
21
- } else {
22
- if (hasAsyncItr(src)) {
23
- this.#src = src[Symbol.asyncIterator]();
24
- } else if (hasItr(src)) {
25
- this.#src = src[Symbol.iterator]();
26
- } else {
27
- this.#src = src();
28
- }
29
- }
30
- if (Array.isArray(src)) {
31
- this.#size = src.length;
32
- } else if (src instanceof Set) {
33
- this.#size = src.size;
34
- }
35
- }
36
-
37
- /**
38
- * Fetch next item from the iterable
39
- */
40
- async #primeNext(): Promise<void> {
41
- const res = await this.#src.next();
42
- this.#done = !!res.done;
43
- this.#ondeck = res.value;
44
- }
45
-
46
- /**
47
- * Determine if the iterable has more data
48
- */
49
- async hasNext(): Promise<boolean> {
50
- if (this.#ondeck === undefined) {
51
- await this.#primeNext();
52
- }
53
- return !this.#done;
54
- }
55
-
56
- /**
57
- * Fetch next item
58
- */
59
- async next(): Promise<X> {
60
- await this.hasNext();
61
-
62
- const out = this.#ondeck!;
63
- this.#ondeck = undefined;
64
- return out;
65
- }
66
-
67
- /**
68
- * Get size, if defined
69
- */
70
- get size(): number | undefined {
71
- return this.#size;
72
- }
73
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Definition for an work set
3
- */
4
- export interface WorkSet<X> {
5
- /**
6
- * Determines if there is more work to do
7
- */
8
- hasNext(): boolean | Promise<boolean>;
9
- /**
10
- * Get next item
11
- */
12
- next(): X | Promise<X>;
13
- /**
14
- * Total size of work set
15
- */
16
- size?: number;
17
- }