gingersnap 0.23.9 → 0.23.10

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.
@@ -58,6 +58,31 @@ class BufferQueue extends WatchableObject.WatchableObject {
58
58
  });
59
59
  }));
60
60
  }
61
+ flushableStream(ignoreCache = false) {
62
+ let pointer = ignoreCache ? this.tail : 0;
63
+ return new stream.Stream(((signal) => {
64
+ const value = this.get(pointer);
65
+ if (value !== undefined) {
66
+ pointer++;
67
+ return {
68
+ value,
69
+ done: () => {
70
+ this.delete(pointer);
71
+ },
72
+ };
73
+ }
74
+ return new Promise((resolve) => {
75
+ const unsubscribe = this.on(pointer, (v) => {
76
+ pointer++;
77
+ resolve(v);
78
+ }, false);
79
+ signal.onabort = () => {
80
+ unsubscribe();
81
+ resolve(new stream_state.ExecutorState(true));
82
+ };
83
+ });
84
+ }));
85
+ }
61
86
  clone() {
62
87
  const obj = super.clone();
63
88
  obj.head = this.head;
@@ -11,6 +11,10 @@ export declare class BufferQueue<T> extends WatchableObject<number, T> {
11
11
  constructor(objectMaxSize?: number, expiryPeriod?: WaitPeriod);
12
12
  ingest(stream: Stream<T>): Future<void>;
13
13
  streamEntries(ignoreCache?: boolean): Stream<T>;
14
+ flushableStream(ignoreCache?: boolean): Stream<{
15
+ value: T;
16
+ done: () => void;
17
+ }>;
14
18
  clone(): BufferQueue<T>;
15
19
  enqueue(value: T, tracker?: string | number): void;
16
20
  clear(): void;
@@ -37,6 +37,31 @@ class BufferQueue extends WatchableObject {
37
37
  });
38
38
  }));
39
39
  }
40
+ flushableStream(ignoreCache = false) {
41
+ let pointer = ignoreCache ? this.tail : 0;
42
+ return new Stream(((signal) => {
43
+ const value = this.get(pointer);
44
+ if (value !== undefined) {
45
+ pointer++;
46
+ return {
47
+ value,
48
+ done: () => {
49
+ this.delete(pointer);
50
+ },
51
+ };
52
+ }
53
+ return new Promise((resolve) => {
54
+ const unsubscribe = this.on(pointer, (v) => {
55
+ pointer++;
56
+ resolve(v);
57
+ }, false);
58
+ signal.onabort = () => {
59
+ unsubscribe();
60
+ resolve(new ExecutorState(true));
61
+ };
62
+ });
63
+ }));
64
+ }
40
65
  clone() {
41
66
  const obj = super.clone();
42
67
  obj.head = this.head;
@@ -7,6 +7,7 @@ var future = require('../../future/future.cjs');
7
7
  require('ramda');
8
8
  var synchronize = require('../../synchronize.cjs');
9
9
  var stream = require('../../stream.cjs');
10
+ var stream_state = require('../../stream/state.cjs');
10
11
  var QueueFullError = require('../../errors/QueueFullError.cjs');
11
12
  var ValueDestroyedError = require('../../errors/ValueDestroyedError.cjs');
12
13
  var Publisher = require('./Publisher.cjs');
@@ -26,6 +27,41 @@ class Queue extends WatchableObject.WatchableObject {
26
27
  get streamEntries() {
27
28
  return stream.Stream.of(this.asyncIterator);
28
29
  }
30
+ /**
31
+ * A stream where data is only removed from the queue once the terminal action of the stream is finished with the data.
32
+ * Useful for cases where the stream is canceled in the middle and that data must not be removed from the queue as
33
+ * operations on it were not finished.
34
+ *
35
+ * Important Note: transactions means the data is not removed until finalized, therefore if more than one transactionalStream
36
+ * is open, each stream may see the same data from the queue if one stream is faster than another
37
+ */
38
+ get transactionStream() {
39
+ let collected = [];
40
+ return new stream.Stream((signal) => {
41
+ if (this.closed) {
42
+ return new stream_state.ExecutorState(true);
43
+ }
44
+ const forwardHead = collected || collected[collected.length - 1] > this.head ? this.head + collected.length : this.head;
45
+ collected.push(forwardHead);
46
+ if (forwardHead > this.tail) {
47
+ return this.await(forwardHead, signal);
48
+ }
49
+ return this.get(forwardHead);
50
+ }).onFinalAction(() => {
51
+ try {
52
+ const lastHead = collected[collected.length - 1];
53
+ while (this.head <= lastHead) {
54
+ this.internalDequeue(true);
55
+ }
56
+ }
57
+ catch (e) {
58
+ // possible cause is another transaction is running
59
+ }
60
+ finally {
61
+ collected = [];
62
+ }
63
+ });
64
+ }
29
65
  createPublisher(transformer) {
30
66
  return new Publisher.Publisher(this, transformer);
31
67
  }
@@ -68,17 +104,7 @@ class Queue extends WatchableObject.WatchableObject {
68
104
  }));
69
105
  }
70
106
  dequeue() {
71
- if (this.closed) {
72
- throw new ValueDestroyedError.ValueDestroyedError();
73
- }
74
- const value = this.get(this.head);
75
- if (value !== undefined && value !== null) {
76
- this.delete(this.head);
77
- this.head++;
78
- this.dequeueEvt.set();
79
- return value;
80
- }
81
- throw new QueueEmptyError.QueueEmptyError();
107
+ return this.internalDequeue();
82
108
  }
83
109
  awaitDequeue(abortSignal) {
84
110
  if (this.empty && !this.closed) {
@@ -182,6 +208,19 @@ class Queue extends WatchableObject.WatchableObject {
182
208
  throw(e) {
183
209
  throw e;
184
210
  }
211
+ internalDequeue(soft = false) {
212
+ if (this.closed && !soft) {
213
+ throw new ValueDestroyedError.ValueDestroyedError();
214
+ }
215
+ const value = this.get(this.head);
216
+ if (value !== undefined && value !== null) {
217
+ this.delete(this.head);
218
+ this.head++;
219
+ this.dequeueEvt.set();
220
+ return value;
221
+ }
222
+ throw new QueueEmptyError.QueueEmptyError();
223
+ }
185
224
  }
186
225
 
187
226
  exports.Queue = Queue;
@@ -13,6 +13,15 @@ export declare class Queue<T> extends WatchableObject<number, T> implements Iter
13
13
  private readonly dequeueEvt;
14
14
  constructor(objectMaxSize?: number, expiryPeriod?: WaitPeriod);
15
15
  get streamEntries(): Stream<T>;
16
+ /**
17
+ * A stream where data is only removed from the queue once the terminal action of the stream is finished with the data.
18
+ * Useful for cases where the stream is canceled in the middle and that data must not be removed from the queue as
19
+ * operations on it were not finished.
20
+ *
21
+ * Important Note: transactions means the data is not removed until finalized, therefore if more than one transactionalStream
22
+ * is open, each stream may see the same data from the queue if one stream is faster than another
23
+ */
24
+ get transactionStream(): Stream<T>;
16
25
  createPublisher<K>(transformer: (v: K) => T): Publisher<T, K>;
17
26
  close(): void;
18
27
  clone(): Queue<T>;
@@ -31,4 +40,5 @@ export declare class Queue<T> extends WatchableObject<number, T> implements Iter
31
40
  next(...args: [] | [undefined]): IteratorResult<T, any>;
32
41
  return?(value?: any): IteratorResult<T, any>;
33
42
  throw?(e?: any): IteratorResult<T, any>;
43
+ private internalDequeue;
34
44
  }
@@ -5,6 +5,7 @@ import { Future } from '../../future/future.mjs';
5
5
  import 'ramda';
6
6
  import { FutureEvent } from '../../synchronize.mjs';
7
7
  import { Stream } from '../../stream.mjs';
8
+ import { ExecutorState } from '../../stream/state.mjs';
8
9
  import { QueueFullError } from '../../errors/QueueFullError.mjs';
9
10
  import { ValueDestroyedError } from '../../errors/ValueDestroyedError.mjs';
10
11
  import { Publisher } from './Publisher.mjs';
@@ -24,6 +25,41 @@ class Queue extends WatchableObject {
24
25
  get streamEntries() {
25
26
  return Stream.of(this.asyncIterator);
26
27
  }
28
+ /**
29
+ * A stream where data is only removed from the queue once the terminal action of the stream is finished with the data.
30
+ * Useful for cases where the stream is canceled in the middle and that data must not be removed from the queue as
31
+ * operations on it were not finished.
32
+ *
33
+ * Important Note: transactions means the data is not removed until finalized, therefore if more than one transactionalStream
34
+ * is open, each stream may see the same data from the queue if one stream is faster than another
35
+ */
36
+ get transactionStream() {
37
+ let collected = [];
38
+ return new Stream((signal) => {
39
+ if (this.closed) {
40
+ return new ExecutorState(true);
41
+ }
42
+ const forwardHead = collected || collected[collected.length - 1] > this.head ? this.head + collected.length : this.head;
43
+ collected.push(forwardHead);
44
+ if (forwardHead > this.tail) {
45
+ return this.await(forwardHead, signal);
46
+ }
47
+ return this.get(forwardHead);
48
+ }).onFinalAction(() => {
49
+ try {
50
+ const lastHead = collected[collected.length - 1];
51
+ while (this.head <= lastHead) {
52
+ this.internalDequeue(true);
53
+ }
54
+ }
55
+ catch (e) {
56
+ // possible cause is another transaction is running
57
+ }
58
+ finally {
59
+ collected = [];
60
+ }
61
+ });
62
+ }
27
63
  createPublisher(transformer) {
28
64
  return new Publisher(this, transformer);
29
65
  }
@@ -66,17 +102,7 @@ class Queue extends WatchableObject {
66
102
  }));
67
103
  }
68
104
  dequeue() {
69
- if (this.closed) {
70
- throw new ValueDestroyedError();
71
- }
72
- const value = this.get(this.head);
73
- if (value !== undefined && value !== null) {
74
- this.delete(this.head);
75
- this.head++;
76
- this.dequeueEvt.set();
77
- return value;
78
- }
79
- throw new QueueEmptyError();
105
+ return this.internalDequeue();
80
106
  }
81
107
  awaitDequeue(abortSignal) {
82
108
  if (this.empty && !this.closed) {
@@ -180,6 +206,19 @@ class Queue extends WatchableObject {
180
206
  throw(e) {
181
207
  throw e;
182
208
  }
209
+ internalDequeue(soft = false) {
210
+ if (this.closed && !soft) {
211
+ throw new ValueDestroyedError();
212
+ }
213
+ const value = this.get(this.head);
214
+ if (value !== undefined && value !== null) {
215
+ this.delete(this.head);
216
+ this.head++;
217
+ this.dequeueEvt.set();
218
+ return value;
219
+ }
220
+ throw new QueueEmptyError();
221
+ }
183
222
  }
184
223
 
185
224
  export { Queue };
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"gingersnap","version":"0.23.9","description":"A general purpose library built with the aim of filling the gaps of javascript shortcomings","dependencies":{"@msgpack/msgpack":"^3.0.0-beta2","browser-or-node":"^3.0.0-pre.0","modern-isomorphic-ws":"^1.0.5","object-hash":"^3.0.0","papaparse":"^5.4.1","protobufjs":"^7.2.6","ramda":"^0.30.1","reflect-metadata":"^0.2.2","tslib":"^2.6.3","uuid":"^9.0.1","ws":"^8.16.0","x2js":"^3.4.4"},"author":"CookieNerds LLC","exports":{"./synchronize":{"import":{"types":"./synchronize.d.ts","default":"./synchronize.mjs"},"require":{"types":"./synchronize.d.ts","default":"./synchronize.cjs"}},"./mocks":{"import":{"types":"./mocks.d.ts","default":"./mocks.mjs"},"require":{"types":"./mocks.d.ts","default":"./mocks.cjs"}},"./socket":{"import":{"types":"./socket.d.ts","default":"./socket.mjs"},"require":{"types":"./socket.d.ts","default":"./socket.cjs"}},"./typing":{"import":{"types":"./typing.d.ts","default":"./typing.mjs"},"require":{"types":"./typing.d.ts","default":"./typing.cjs"}},"./stream":{"import":{"types":"./stream/index.d.ts","default":"./stream.mjs"},"require":{"types":"./stream/index.d.ts","default":"./stream.cjs"}},"./reflection/injector":{"import":{"types":"./reflection/injector.d.ts","default":"./reflection/injector.mjs"},"require":{"types":"./reflection/injector.d.ts","default":"./reflection/injector.cjs"}},"./stream/call":{"import":{"types":"./stream/call.d.ts","default":"./stream/call.mjs"},"require":{"types":"./stream/call.d.ts","default":"./stream/call.cjs"}},"./stream/state":{"import":{"types":"./stream/state.d.ts","default":"./stream/state.mjs"},"require":{"types":"./stream/state.d.ts","default":"./stream/state.cjs"}},"./stream/collector":{"import":{"types":"./stream/collector.d.ts","default":"./stream/collector.mjs"},"require":{"types":"./stream/collector.d.ts","default":"./stream/collector.cjs"}},"./stream/observable":{"import":{"types":"./stream/observable.d.ts","default":"./stream/observable.mjs"},"require":{"types":"./stream/observable.d.ts","default":"./stream/observable.cjs"}},"./networking":{"import":{"types":"./networking/index.d.ts","default":"./networking.mjs"},"require":{"types":"./networking/index.d.ts","default":"./networking.cjs"}},"./managers":{"import":{"types":"./managers/index.d.ts","default":"./managers.mjs"},"require":{"types":"./managers/index.d.ts","default":"./managers.cjs"}},"./future":{"import":{"types":"./future/index.d.ts","default":"./future.mjs"},"require":{"types":"./future/index.d.ts","default":"./future.cjs"}},"./errors":{"import":{"types":"./errors/index.d.ts","default":"./errors.mjs"},"require":{"types":"./errors/index.d.ts","default":"./errors.cjs"}},"./data-structures/array":{"import":{"types":"./data-structures/array/index.d.ts","default":"./data-structures/array.mjs"},"require":{"types":"./data-structures/array/index.d.ts","default":"./data-structures/array.cjs"}},"./data-structures/object":{"import":{"types":"./data-structures/object/index.d.ts","default":"./data-structures/object.mjs"},"require":{"types":"./data-structures/object/index.d.ts","default":"./data-structures/object.cjs"}},"./data/decoders":{"import":{"types":"./data/decoders/index.d.ts","default":"./data/decoders.mjs"},"require":{"types":"./data/decoders/index.d.ts","default":"./data/decoders.cjs"}},"./data/model":{"import":{"types":"./data/model/index.d.ts","default":"./data/model.mjs"},"require":{"types":"./data/model/index.d.ts","default":"./data/model.cjs"}},"./data/bus":{"import":{"types":"./data/bus.d.ts","default":"./data/bus.mjs"},"require":{"types":"./data/bus.d.ts","default":"./data/bus.cjs"}},"./data/AtomicValue":{"import":{"types":"./data/AtomicValue.d.ts","default":"./data/AtomicValue.mjs"},"require":{"types":"./data/AtomicValue.d.ts","default":"./data/AtomicValue.cjs"}},"./data/store":{"import":{"types":"./data/store/index.d.ts","default":"./data/store.mjs"},"require":{"types":"./data/store/index.d.ts","default":"./data/store.cjs"}},"./functools":{"import":{"types":"./functools/index.d.ts","default":"./functools.mjs"},"require":{"types":"./functools/index.d.ts","default":"./functools.cjs"}}}}
1
+ {"name":"gingersnap","version":"0.23.10","description":"A general purpose library built with the aim of filling the gaps of javascript shortcomings","dependencies":{"@msgpack/msgpack":"^3.0.0-beta2","browser-or-node":"^3.0.0-pre.0","modern-isomorphic-ws":"^1.0.5","object-hash":"^3.0.0","papaparse":"^5.4.1","protobufjs":"^7.2.6","ramda":"^0.30.1","reflect-metadata":"^0.2.2","tslib":"^2.6.3","uuid":"^9.0.1","ws":"^8.16.0","x2js":"^3.4.4"},"author":"CookieNerds LLC","exports":{"./synchronize":{"import":{"types":"./synchronize.d.ts","default":"./synchronize.mjs"},"require":{"types":"./synchronize.d.ts","default":"./synchronize.cjs"}},"./mocks":{"import":{"types":"./mocks.d.ts","default":"./mocks.mjs"},"require":{"types":"./mocks.d.ts","default":"./mocks.cjs"}},"./socket":{"import":{"types":"./socket.d.ts","default":"./socket.mjs"},"require":{"types":"./socket.d.ts","default":"./socket.cjs"}},"./typing":{"import":{"types":"./typing.d.ts","default":"./typing.mjs"},"require":{"types":"./typing.d.ts","default":"./typing.cjs"}},"./stream":{"import":{"types":"./stream/index.d.ts","default":"./stream.mjs"},"require":{"types":"./stream/index.d.ts","default":"./stream.cjs"}},"./reflection/injector":{"import":{"types":"./reflection/injector.d.ts","default":"./reflection/injector.mjs"},"require":{"types":"./reflection/injector.d.ts","default":"./reflection/injector.cjs"}},"./stream/call":{"import":{"types":"./stream/call.d.ts","default":"./stream/call.mjs"},"require":{"types":"./stream/call.d.ts","default":"./stream/call.cjs"}},"./stream/state":{"import":{"types":"./stream/state.d.ts","default":"./stream/state.mjs"},"require":{"types":"./stream/state.d.ts","default":"./stream/state.cjs"}},"./stream/collector":{"import":{"types":"./stream/collector.d.ts","default":"./stream/collector.mjs"},"require":{"types":"./stream/collector.d.ts","default":"./stream/collector.cjs"}},"./stream/observable":{"import":{"types":"./stream/observable.d.ts","default":"./stream/observable.mjs"},"require":{"types":"./stream/observable.d.ts","default":"./stream/observable.cjs"}},"./networking":{"import":{"types":"./networking/index.d.ts","default":"./networking.mjs"},"require":{"types":"./networking/index.d.ts","default":"./networking.cjs"}},"./managers":{"import":{"types":"./managers/index.d.ts","default":"./managers.mjs"},"require":{"types":"./managers/index.d.ts","default":"./managers.cjs"}},"./future":{"import":{"types":"./future/index.d.ts","default":"./future.mjs"},"require":{"types":"./future/index.d.ts","default":"./future.cjs"}},"./errors":{"import":{"types":"./errors/index.d.ts","default":"./errors.mjs"},"require":{"types":"./errors/index.d.ts","default":"./errors.cjs"}},"./data-structures/array":{"import":{"types":"./data-structures/array/index.d.ts","default":"./data-structures/array.mjs"},"require":{"types":"./data-structures/array/index.d.ts","default":"./data-structures/array.cjs"}},"./data-structures/object":{"import":{"types":"./data-structures/object/index.d.ts","default":"./data-structures/object.mjs"},"require":{"types":"./data-structures/object/index.d.ts","default":"./data-structures/object.cjs"}},"./data/decoders":{"import":{"types":"./data/decoders/index.d.ts","default":"./data/decoders.mjs"},"require":{"types":"./data/decoders/index.d.ts","default":"./data/decoders.cjs"}},"./data/model":{"import":{"types":"./data/model/index.d.ts","default":"./data/model.mjs"},"require":{"types":"./data/model/index.d.ts","default":"./data/model.cjs"}},"./data/bus":{"import":{"types":"./data/bus.d.ts","default":"./data/bus.mjs"},"require":{"types":"./data/bus.d.ts","default":"./data/bus.cjs"}},"./data/AtomicValue":{"import":{"types":"./data/AtomicValue.d.ts","default":"./data/AtomicValue.mjs"},"require":{"types":"./data/AtomicValue.d.ts","default":"./data/AtomicValue.cjs"}},"./data/store":{"import":{"types":"./data/store/index.d.ts","default":"./data/store.mjs"},"require":{"types":"./data/store/index.d.ts","default":"./data/store.cjs"}},"./functools":{"import":{"types":"./functools/index.d.ts","default":"./functools.mjs"},"require":{"types":"./functools/index.d.ts","default":"./functools.cjs"}}}}
package/stream/index.d.ts CHANGED
@@ -32,6 +32,7 @@ export declare class Stream<T> implements AsyncGenerator<T> {
32
32
  }>;
33
33
  protected cancelHooks: Set<() => any>;
34
34
  protected completionHooks: Set<() => any>;
35
+ protected finalActionHooks: Set<() => any>;
35
36
  protected completionHookInvoked: boolean;
36
37
  protected monitorHooks: Set<(v: T) => void>;
37
38
  private frozen;
@@ -180,7 +181,17 @@ export declare class Stream<T> implements AsyncGenerator<T> {
180
181
  * @param hook
181
182
  */
182
183
  onCompletion(hook: () => any): this;
184
+ /**
185
+ * Register hook that is called at the terminal end of a stream after each value is being consumed
186
+ * E.g. stream.onFinalAction(() => console.log('called after every foreach')).forEach(v => ....)
187
+ *
188
+ * Terminal Actions are those that consume the stream, and would be the final action that can be performed on the stream,
189
+ * usually returning a future
190
+ * @param hook
191
+ */
192
+ onFinalAction(hook: () => any): this;
183
193
  removeCompletionHook(hook: () => any): this;
194
+ removeFinalActionHook(hook: () => any): this;
184
195
  /**
185
196
  * Cancels the stream
186
197
  * @param reason
@@ -258,7 +269,7 @@ export declare class Stream<T> implements AsyncGenerator<T> {
258
269
  */
259
270
  catch<K>(callback: (v: Error) => K | null | undefined): Stream<InferErrorResult<K, T> | T>;
260
271
  /**
261
- * Consumes the entire stream and store the data in an array. Future is immediately executed
272
+ * Consumes the entire stream and store the data in a collection. Future is immediately executed
262
273
  */
263
274
  collect<K>(collector: Collector<K, T>): Future<K>;
264
275
  /**
@@ -271,7 +282,7 @@ export declare class Stream<T> implements AsyncGenerator<T> {
271
282
  * Iterates over the stream of values, used as a collector. Future is immediately executed
272
283
  * @param callback
273
284
  */
274
- forEach(callback: (v: T) => void | Future<void>): Future<void>;
285
+ forEach(callback: (v: T) => void | Future<void> | Promise<void>): Future<void>;
275
286
  forEachLatest(callback: (v: T) => void | Future<void>): Future<void>;
276
287
  /**
277
288
  * Runs the stream only once. After this call, the stream is closed. Future is immediately executed
package/stream.cjs CHANGED
@@ -65,6 +65,7 @@ class Stream {
65
65
  this.backlog = [];
66
66
  this.cancelHooks = new Set();
67
67
  this.completionHooks = new Set();
68
+ this.finalActionHooks = new Set();
68
69
  this.completionHookInvoked = false;
69
70
  this.runLock = new synchronize.Lock();
70
71
  this.concurrencyLimit = 0;
@@ -225,6 +226,7 @@ class Stream {
225
226
  }
226
227
  else {
227
228
  resolve(v.value);
229
+ this.finalActionHooks.forEach((hook) => hook());
228
230
  }
229
231
  })
230
232
  .catch(reject);
@@ -358,6 +360,7 @@ class Stream {
358
360
  newStream.done = this.done;
359
361
  newStream.cancelHooks = new Set(this.cancelHooks);
360
362
  newStream.completionHooks = new Set(this.completionHooks);
363
+ newStream.finalActionHooks = new Set(this.finalActionHooks);
361
364
  newStream.completionHookInvoked = this.completionHookInvoked;
362
365
  return newStream;
363
366
  }
@@ -553,6 +556,7 @@ class Stream {
553
556
  newStream.backlog = [...this.backlog];
554
557
  newStream.cancelHooks = new Set(this.cancelHooks);
555
558
  newStream.completionHooks = new Set(this.completionHooks);
559
+ newStream.finalActionHooks = new Set(this.finalActionHooks);
556
560
  newStream.completionHookInvoked = this.completionHookInvoked;
557
561
  if (withSignals) {
558
562
  newStream.controller = this.controller;
@@ -579,10 +583,26 @@ class Stream {
579
583
  this.completionHooks.add(hook);
580
584
  return this;
581
585
  }
586
+ /**
587
+ * Register hook that is called at the terminal end of a stream after each value is being consumed
588
+ * E.g. stream.onFinalAction(() => console.log('called after every foreach')).forEach(v => ....)
589
+ *
590
+ * Terminal Actions are those that consume the stream, and would be the final action that can be performed on the stream,
591
+ * usually returning a future
592
+ * @param hook
593
+ */
594
+ onFinalAction(hook) {
595
+ this.finalActionHooks.add(hook);
596
+ return this;
597
+ }
582
598
  removeCompletionHook(hook) {
583
599
  this.completionHooks.delete(hook);
584
600
  return this;
585
601
  }
602
+ removeFinalActionHook(hook) {
603
+ this.finalActionHooks.delete(hook);
604
+ return this;
605
+ }
586
606
  /**
587
607
  * Cancels the stream
588
608
  * @param reason
@@ -861,10 +881,18 @@ class Stream {
861
881
  return this;
862
882
  }
863
883
  /**
864
- * Consumes the entire stream and store the data in an array. Future is immediately executed
884
+ * Consumes the entire stream and store the data in a collection. Future is immediately executed
865
885
  */
866
886
  collect(collector) {
867
- return collector(this.isParallel ? this.join() : this).schedule();
887
+ return collector(this.isParallel
888
+ ? this.join().map((v) => {
889
+ this.finalActionHooks.forEach((hook) => hook());
890
+ return v;
891
+ })
892
+ : this.map((v) => {
893
+ this.finalActionHooks.forEach((hook) => hook());
894
+ return v;
895
+ })).schedule();
868
896
  }
869
897
  /**
870
898
  * Continuously exhaust the stream until the stream ends or the limit is reached. No result will be provided at
@@ -887,6 +915,7 @@ class Stream {
887
915
  _h = false;
888
916
  try {
889
917
  const _ = _c;
918
+ this.finalActionHooks.forEach((hook) => hook());
890
919
  if (++index >= limit)
891
920
  break;
892
921
  }
@@ -911,6 +940,7 @@ class Stream {
911
940
  _l = false;
912
941
  try {
913
942
  const _ = _f;
943
+ this.finalActionHooks.forEach((hook) => hook());
914
944
  }
915
945
  finally {
916
946
  _l = true;
@@ -937,16 +967,38 @@ class Stream {
937
967
  * @param callback
938
968
  */
939
969
  forEach(callback) {
940
- return new future.Future((resolve, reject, signal) => _tslib.__awaiter(this, void 0, void 0, function* () {
941
- var _a;
970
+ return new future.Future((resolve, reject) => _tslib.__awaiter(this, void 0, void 0, function* () {
971
+ var _a, e_4, _b, _c;
972
+ var _d;
942
973
  try {
943
- yield this.map((value) => callback(value))
944
- .consume()
945
- .registerSignal(signal);
974
+ try {
975
+ for (var _e = true, _f = _tslib.__asyncValues(this), _g; _g = yield _f.next(), _a = _g.done, !_a;) {
976
+ _c = _g.value;
977
+ _e = false;
978
+ try {
979
+ const value = _c;
980
+ const result = callback(value);
981
+ if (result instanceof future.Future || result instanceof Promise) {
982
+ yield result;
983
+ }
984
+ this.finalActionHooks.forEach((hook) => hook());
985
+ }
986
+ finally {
987
+ _e = true;
988
+ }
989
+ }
990
+ }
991
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
992
+ finally {
993
+ try {
994
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
995
+ }
996
+ finally { if (e_4) throw e_4.error; }
997
+ }
946
998
  resolve();
947
999
  }
948
1000
  catch (error) {
949
- reject(error instanceof FutureError.FutureError ? error : new FutureError.FutureError((_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : "Unknown"));
1001
+ reject(error instanceof FutureError.FutureError ? error : new FutureError.FutureError((_d = error === null || error === void 0 ? void 0 : error.message) !== null && _d !== void 0 ? _d : "Unknown"));
950
1002
  }
951
1003
  })).schedule();
952
1004
  }
@@ -957,6 +1009,7 @@ class Stream {
957
1009
  let pending = null;
958
1010
  while (!signal.aborted) {
959
1011
  const value = yield this.internalNext().registerSignal(signal);
1012
+ this.finalActionHooks.forEach((hook) => hook());
960
1013
  if (!value.done) {
961
1014
  pending === null || pending === void 0 ? void 0 : pending.cancel();
962
1015
  const result = callback(value.value);
@@ -990,6 +1043,7 @@ class Stream {
990
1043
  if (value !== null && value !== undefined) {
991
1044
  this.monitorHooks.forEach((hook) => hook(value));
992
1045
  }
1046
+ this.finalActionHooks.forEach((hook) => hook());
993
1047
  if (state !== exports.State.CONTINUE) {
994
1048
  this.done = true;
995
1049
  this.invokeCompletionHooks();
@@ -1097,6 +1151,7 @@ class Stream {
1097
1151
  const preResult = yield this.yieldTrueResult(preProcessor(data), signal);
1098
1152
  const result = yield this.yieldTrueResult(functor(preResult instanceof Promise ? yield preResult : preResult), signal);
1099
1153
  if (result === null || result === undefined) {
1154
+ this.finalActionHooks.forEach((hook) => hook());
1100
1155
  return resolve({ state: exports.State.CONTINUE });
1101
1156
  }
1102
1157
  data = result;
@@ -1309,7 +1364,7 @@ class Stream {
1309
1364
  }
1310
1365
  forwardExecute(preProcessor = R__namespace.identity) {
1311
1366
  return future.Future.of((resolve, reject, signal) => _tslib.__awaiter(this, void 0, void 0, function* () {
1312
- var _a, e_4, _b, _c;
1367
+ var _a, e_5, _b, _c;
1313
1368
  let pending = [];
1314
1369
  try {
1315
1370
  for (var _d = true, _e = _tslib.__asyncValues(this.sourceStream.internalIterator()), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
@@ -1333,12 +1388,12 @@ class Stream {
1333
1388
  }
1334
1389
  }
1335
1390
  }
1336
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
1391
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
1337
1392
  finally {
1338
1393
  try {
1339
1394
  if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
1340
1395
  }
1341
- finally { if (e_4) throw e_4.error; }
1396
+ finally { if (e_5) throw e_5.error; }
1342
1397
  }
1343
1398
  const { state, value } = yield this.collectResult(pending, signal);
1344
1399
  if (state === exports.State.CONTINUE) {
package/stream.mjs CHANGED
@@ -44,6 +44,7 @@ class Stream {
44
44
  this.backlog = [];
45
45
  this.cancelHooks = new Set();
46
46
  this.completionHooks = new Set();
47
+ this.finalActionHooks = new Set();
47
48
  this.completionHookInvoked = false;
48
49
  this.runLock = new Lock();
49
50
  this.concurrencyLimit = 0;
@@ -204,6 +205,7 @@ class Stream {
204
205
  }
205
206
  else {
206
207
  resolve(v.value);
208
+ this.finalActionHooks.forEach((hook) => hook());
207
209
  }
208
210
  })
209
211
  .catch(reject);
@@ -337,6 +339,7 @@ class Stream {
337
339
  newStream.done = this.done;
338
340
  newStream.cancelHooks = new Set(this.cancelHooks);
339
341
  newStream.completionHooks = new Set(this.completionHooks);
342
+ newStream.finalActionHooks = new Set(this.finalActionHooks);
340
343
  newStream.completionHookInvoked = this.completionHookInvoked;
341
344
  return newStream;
342
345
  }
@@ -532,6 +535,7 @@ class Stream {
532
535
  newStream.backlog = [...this.backlog];
533
536
  newStream.cancelHooks = new Set(this.cancelHooks);
534
537
  newStream.completionHooks = new Set(this.completionHooks);
538
+ newStream.finalActionHooks = new Set(this.finalActionHooks);
535
539
  newStream.completionHookInvoked = this.completionHookInvoked;
536
540
  if (withSignals) {
537
541
  newStream.controller = this.controller;
@@ -558,10 +562,26 @@ class Stream {
558
562
  this.completionHooks.add(hook);
559
563
  return this;
560
564
  }
565
+ /**
566
+ * Register hook that is called at the terminal end of a stream after each value is being consumed
567
+ * E.g. stream.onFinalAction(() => console.log('called after every foreach')).forEach(v => ....)
568
+ *
569
+ * Terminal Actions are those that consume the stream, and would be the final action that can be performed on the stream,
570
+ * usually returning a future
571
+ * @param hook
572
+ */
573
+ onFinalAction(hook) {
574
+ this.finalActionHooks.add(hook);
575
+ return this;
576
+ }
561
577
  removeCompletionHook(hook) {
562
578
  this.completionHooks.delete(hook);
563
579
  return this;
564
580
  }
581
+ removeFinalActionHook(hook) {
582
+ this.finalActionHooks.delete(hook);
583
+ return this;
584
+ }
565
585
  /**
566
586
  * Cancels the stream
567
587
  * @param reason
@@ -840,10 +860,18 @@ class Stream {
840
860
  return this;
841
861
  }
842
862
  /**
843
- * Consumes the entire stream and store the data in an array. Future is immediately executed
863
+ * Consumes the entire stream and store the data in a collection. Future is immediately executed
844
864
  */
845
865
  collect(collector) {
846
- return collector(this.isParallel ? this.join() : this).schedule();
866
+ return collector(this.isParallel
867
+ ? this.join().map((v) => {
868
+ this.finalActionHooks.forEach((hook) => hook());
869
+ return v;
870
+ })
871
+ : this.map((v) => {
872
+ this.finalActionHooks.forEach((hook) => hook());
873
+ return v;
874
+ })).schedule();
847
875
  }
848
876
  /**
849
877
  * Continuously exhaust the stream until the stream ends or the limit is reached. No result will be provided at
@@ -866,6 +894,7 @@ class Stream {
866
894
  _h = false;
867
895
  try {
868
896
  const _ = _c;
897
+ this.finalActionHooks.forEach((hook) => hook());
869
898
  if (++index >= limit)
870
899
  break;
871
900
  }
@@ -890,6 +919,7 @@ class Stream {
890
919
  _l = false;
891
920
  try {
892
921
  const _ = _f;
922
+ this.finalActionHooks.forEach((hook) => hook());
893
923
  }
894
924
  finally {
895
925
  _l = true;
@@ -916,16 +946,38 @@ class Stream {
916
946
  * @param callback
917
947
  */
918
948
  forEach(callback) {
919
- return new Future((resolve, reject, signal) => __awaiter(this, void 0, void 0, function* () {
920
- var _a;
949
+ return new Future((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
950
+ var _a, e_4, _b, _c;
951
+ var _d;
921
952
  try {
922
- yield this.map((value) => callback(value))
923
- .consume()
924
- .registerSignal(signal);
953
+ try {
954
+ for (var _e = true, _f = __asyncValues(this), _g; _g = yield _f.next(), _a = _g.done, !_a;) {
955
+ _c = _g.value;
956
+ _e = false;
957
+ try {
958
+ const value = _c;
959
+ const result = callback(value);
960
+ if (result instanceof Future || result instanceof Promise) {
961
+ yield result;
962
+ }
963
+ this.finalActionHooks.forEach((hook) => hook());
964
+ }
965
+ finally {
966
+ _e = true;
967
+ }
968
+ }
969
+ }
970
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
971
+ finally {
972
+ try {
973
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
974
+ }
975
+ finally { if (e_4) throw e_4.error; }
976
+ }
925
977
  resolve();
926
978
  }
927
979
  catch (error) {
928
- reject(error instanceof FutureError ? error : new FutureError((_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : "Unknown"));
980
+ reject(error instanceof FutureError ? error : new FutureError((_d = error === null || error === void 0 ? void 0 : error.message) !== null && _d !== void 0 ? _d : "Unknown"));
929
981
  }
930
982
  })).schedule();
931
983
  }
@@ -936,6 +988,7 @@ class Stream {
936
988
  let pending = null;
937
989
  while (!signal.aborted) {
938
990
  const value = yield this.internalNext().registerSignal(signal);
991
+ this.finalActionHooks.forEach((hook) => hook());
939
992
  if (!value.done) {
940
993
  pending === null || pending === void 0 ? void 0 : pending.cancel();
941
994
  const result = callback(value.value);
@@ -969,6 +1022,7 @@ class Stream {
969
1022
  if (value !== null && value !== undefined) {
970
1023
  this.monitorHooks.forEach((hook) => hook(value));
971
1024
  }
1025
+ this.finalActionHooks.forEach((hook) => hook());
972
1026
  if (state !== State.CONTINUE) {
973
1027
  this.done = true;
974
1028
  this.invokeCompletionHooks();
@@ -1076,6 +1130,7 @@ class Stream {
1076
1130
  const preResult = yield this.yieldTrueResult(preProcessor(data), signal);
1077
1131
  const result = yield this.yieldTrueResult(functor(preResult instanceof Promise ? yield preResult : preResult), signal);
1078
1132
  if (result === null || result === undefined) {
1133
+ this.finalActionHooks.forEach((hook) => hook());
1079
1134
  return resolve({ state: State.CONTINUE });
1080
1135
  }
1081
1136
  data = result;
@@ -1288,7 +1343,7 @@ class Stream {
1288
1343
  }
1289
1344
  forwardExecute(preProcessor = R.identity) {
1290
1345
  return Future.of((resolve, reject, signal) => __awaiter(this, void 0, void 0, function* () {
1291
- var _a, e_4, _b, _c;
1346
+ var _a, e_5, _b, _c;
1292
1347
  let pending = [];
1293
1348
  try {
1294
1349
  for (var _d = true, _e = __asyncValues(this.sourceStream.internalIterator()), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
@@ -1312,12 +1367,12 @@ class Stream {
1312
1367
  }
1313
1368
  }
1314
1369
  }
1315
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
1370
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
1316
1371
  finally {
1317
1372
  try {
1318
1373
  if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
1319
1374
  }
1320
- finally { if (e_4) throw e_4.error; }
1375
+ finally { if (e_5) throw e_5.error; }
1321
1376
  }
1322
1377
  const { state, value } = yield this.collectResult(pending, signal);
1323
1378
  if (state === State.CONTINUE) {