gingersnap 0.23.9 → 0.23.11

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.11","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;
@@ -143,6 +144,7 @@ export declare class Stream<T> implements AsyncGenerator<T> {
143
144
  * @param callback
144
145
  */
145
146
  filter(callback: (v: T) => boolean | Promise<boolean> | Future<boolean>): Stream<T>;
147
+ filterNotNull(): Stream<NonNullable<T>>;
146
148
  reduce<K, V>(initialData: K, functor: (v: T, prev: K | V) => V, exhaustive?: boolean): Stream<V>;
147
149
  reduceWhile<K, V>(predicate: (v: T) => boolean, initialData: K, functor: (v: T, prev: K | V) => V, exhaustive?: boolean): Stream<V>;
148
150
  /**
@@ -180,7 +182,17 @@ export declare class Stream<T> implements AsyncGenerator<T> {
180
182
  * @param hook
181
183
  */
182
184
  onCompletion(hook: () => any): this;
185
+ /**
186
+ * Register hook that is called at the terminal end of a stream after each value is being consumed
187
+ * E.g. stream.onFinalAction(() => console.log('called after every foreach')).forEach(v => ....)
188
+ *
189
+ * Terminal Actions are those that consume the stream, and would be the final action that can be performed on the stream,
190
+ * usually returning a future
191
+ * @param hook
192
+ */
193
+ onFinalAction(hook: () => any): this;
183
194
  removeCompletionHook(hook: () => any): this;
195
+ removeFinalActionHook(hook: () => any): this;
184
196
  /**
185
197
  * Cancels the stream
186
198
  * @param reason
@@ -258,7 +270,7 @@ export declare class Stream<T> implements AsyncGenerator<T> {
258
270
  */
259
271
  catch<K>(callback: (v: Error) => K | null | undefined): Stream<InferErrorResult<K, T> | T>;
260
272
  /**
261
- * Consumes the entire stream and store the data in an array. Future is immediately executed
273
+ * Consumes the entire stream and store the data in a collection. Future is immediately executed
262
274
  */
263
275
  collect<K>(collector: Collector<K, T>): Future<K>;
264
276
  /**
@@ -271,7 +283,7 @@ export declare class Stream<T> implements AsyncGenerator<T> {
271
283
  * Iterates over the stream of values, used as a collector. Future is immediately executed
272
284
  * @param callback
273
285
  */
274
- forEach(callback: (v: T) => void | Future<void>): Future<void>;
286
+ forEach(callback: (v: T) => void | Future<void> | Promise<void>): Future<void>;
275
287
  forEachLatest(callback: (v: T) => void | Future<void>): Future<void>;
276
288
  /**
277
289
  * 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
  }
@@ -393,6 +396,9 @@ class Stream {
393
396
  });
394
397
  return this;
395
398
  }
399
+ filterNotNull() {
400
+ return this.filter((it) => it !== null || it !== undefined);
401
+ }
396
402
  reduce(initialData, functor, exhaustive = true) {
397
403
  if (!this.canAlterStream)
398
404
  throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
@@ -553,6 +559,7 @@ class Stream {
553
559
  newStream.backlog = [...this.backlog];
554
560
  newStream.cancelHooks = new Set(this.cancelHooks);
555
561
  newStream.completionHooks = new Set(this.completionHooks);
562
+ newStream.finalActionHooks = new Set(this.finalActionHooks);
556
563
  newStream.completionHookInvoked = this.completionHookInvoked;
557
564
  if (withSignals) {
558
565
  newStream.controller = this.controller;
@@ -579,10 +586,26 @@ class Stream {
579
586
  this.completionHooks.add(hook);
580
587
  return this;
581
588
  }
589
+ /**
590
+ * Register hook that is called at the terminal end of a stream after each value is being consumed
591
+ * E.g. stream.onFinalAction(() => console.log('called after every foreach')).forEach(v => ....)
592
+ *
593
+ * Terminal Actions are those that consume the stream, and would be the final action that can be performed on the stream,
594
+ * usually returning a future
595
+ * @param hook
596
+ */
597
+ onFinalAction(hook) {
598
+ this.finalActionHooks.add(hook);
599
+ return this;
600
+ }
582
601
  removeCompletionHook(hook) {
583
602
  this.completionHooks.delete(hook);
584
603
  return this;
585
604
  }
605
+ removeFinalActionHook(hook) {
606
+ this.finalActionHooks.delete(hook);
607
+ return this;
608
+ }
586
609
  /**
587
610
  * Cancels the stream
588
611
  * @param reason
@@ -861,10 +884,18 @@ class Stream {
861
884
  return this;
862
885
  }
863
886
  /**
864
- * Consumes the entire stream and store the data in an array. Future is immediately executed
887
+ * Consumes the entire stream and store the data in a collection. Future is immediately executed
865
888
  */
866
889
  collect(collector) {
867
- return collector(this.isParallel ? this.join() : this).schedule();
890
+ return collector(this.isParallel
891
+ ? this.join().map((v) => {
892
+ this.finalActionHooks.forEach((hook) => hook());
893
+ return v;
894
+ })
895
+ : this.map((v) => {
896
+ this.finalActionHooks.forEach((hook) => hook());
897
+ return v;
898
+ })).schedule();
868
899
  }
869
900
  /**
870
901
  * Continuously exhaust the stream until the stream ends or the limit is reached. No result will be provided at
@@ -887,6 +918,7 @@ class Stream {
887
918
  _h = false;
888
919
  try {
889
920
  const _ = _c;
921
+ this.finalActionHooks.forEach((hook) => hook());
890
922
  if (++index >= limit)
891
923
  break;
892
924
  }
@@ -911,6 +943,7 @@ class Stream {
911
943
  _l = false;
912
944
  try {
913
945
  const _ = _f;
946
+ this.finalActionHooks.forEach((hook) => hook());
914
947
  }
915
948
  finally {
916
949
  _l = true;
@@ -937,16 +970,38 @@ class Stream {
937
970
  * @param callback
938
971
  */
939
972
  forEach(callback) {
940
- return new future.Future((resolve, reject, signal) => _tslib.__awaiter(this, void 0, void 0, function* () {
941
- var _a;
973
+ return new future.Future((resolve, reject) => _tslib.__awaiter(this, void 0, void 0, function* () {
974
+ var _a, e_4, _b, _c;
975
+ var _d;
942
976
  try {
943
- yield this.map((value) => callback(value))
944
- .consume()
945
- .registerSignal(signal);
977
+ try {
978
+ for (var _e = true, _f = _tslib.__asyncValues(this), _g; _g = yield _f.next(), _a = _g.done, !_a;) {
979
+ _c = _g.value;
980
+ _e = false;
981
+ try {
982
+ const value = _c;
983
+ const result = callback(value);
984
+ if (result instanceof future.Future || result instanceof Promise) {
985
+ yield result;
986
+ }
987
+ this.finalActionHooks.forEach((hook) => hook());
988
+ }
989
+ finally {
990
+ _e = true;
991
+ }
992
+ }
993
+ }
994
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
995
+ finally {
996
+ try {
997
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
998
+ }
999
+ finally { if (e_4) throw e_4.error; }
1000
+ }
946
1001
  resolve();
947
1002
  }
948
1003
  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"));
1004
+ 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
1005
  }
951
1006
  })).schedule();
952
1007
  }
@@ -957,6 +1012,7 @@ class Stream {
957
1012
  let pending = null;
958
1013
  while (!signal.aborted) {
959
1014
  const value = yield this.internalNext().registerSignal(signal);
1015
+ this.finalActionHooks.forEach((hook) => hook());
960
1016
  if (!value.done) {
961
1017
  pending === null || pending === void 0 ? void 0 : pending.cancel();
962
1018
  const result = callback(value.value);
@@ -990,6 +1046,7 @@ class Stream {
990
1046
  if (value !== null && value !== undefined) {
991
1047
  this.monitorHooks.forEach((hook) => hook(value));
992
1048
  }
1049
+ this.finalActionHooks.forEach((hook) => hook());
993
1050
  if (state !== exports.State.CONTINUE) {
994
1051
  this.done = true;
995
1052
  this.invokeCompletionHooks();
@@ -1097,6 +1154,7 @@ class Stream {
1097
1154
  const preResult = yield this.yieldTrueResult(preProcessor(data), signal);
1098
1155
  const result = yield this.yieldTrueResult(functor(preResult instanceof Promise ? yield preResult : preResult), signal);
1099
1156
  if (result === null || result === undefined) {
1157
+ this.finalActionHooks.forEach((hook) => hook());
1100
1158
  return resolve({ state: exports.State.CONTINUE });
1101
1159
  }
1102
1160
  data = result;
@@ -1309,7 +1367,7 @@ class Stream {
1309
1367
  }
1310
1368
  forwardExecute(preProcessor = R__namespace.identity) {
1311
1369
  return future.Future.of((resolve, reject, signal) => _tslib.__awaiter(this, void 0, void 0, function* () {
1312
- var _a, e_4, _b, _c;
1370
+ var _a, e_5, _b, _c;
1313
1371
  let pending = [];
1314
1372
  try {
1315
1373
  for (var _d = true, _e = _tslib.__asyncValues(this.sourceStream.internalIterator()), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
@@ -1333,12 +1391,12 @@ class Stream {
1333
1391
  }
1334
1392
  }
1335
1393
  }
1336
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
1394
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
1337
1395
  finally {
1338
1396
  try {
1339
1397
  if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
1340
1398
  }
1341
- finally { if (e_4) throw e_4.error; }
1399
+ finally { if (e_5) throw e_5.error; }
1342
1400
  }
1343
1401
  const { state, value } = yield this.collectResult(pending, signal);
1344
1402
  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
  }
@@ -372,6 +375,9 @@ class Stream {
372
375
  });
373
376
  return this;
374
377
  }
378
+ filterNotNull() {
379
+ return this.filter((it) => it !== null || it !== undefined);
380
+ }
375
381
  reduce(initialData, functor, exhaustive = true) {
376
382
  if (!this.canAlterStream)
377
383
  throw new IllegalOperationError("Cannot alter the stream as it is frozen");
@@ -532,6 +538,7 @@ class Stream {
532
538
  newStream.backlog = [...this.backlog];
533
539
  newStream.cancelHooks = new Set(this.cancelHooks);
534
540
  newStream.completionHooks = new Set(this.completionHooks);
541
+ newStream.finalActionHooks = new Set(this.finalActionHooks);
535
542
  newStream.completionHookInvoked = this.completionHookInvoked;
536
543
  if (withSignals) {
537
544
  newStream.controller = this.controller;
@@ -558,10 +565,26 @@ class Stream {
558
565
  this.completionHooks.add(hook);
559
566
  return this;
560
567
  }
568
+ /**
569
+ * Register hook that is called at the terminal end of a stream after each value is being consumed
570
+ * E.g. stream.onFinalAction(() => console.log('called after every foreach')).forEach(v => ....)
571
+ *
572
+ * Terminal Actions are those that consume the stream, and would be the final action that can be performed on the stream,
573
+ * usually returning a future
574
+ * @param hook
575
+ */
576
+ onFinalAction(hook) {
577
+ this.finalActionHooks.add(hook);
578
+ return this;
579
+ }
561
580
  removeCompletionHook(hook) {
562
581
  this.completionHooks.delete(hook);
563
582
  return this;
564
583
  }
584
+ removeFinalActionHook(hook) {
585
+ this.finalActionHooks.delete(hook);
586
+ return this;
587
+ }
565
588
  /**
566
589
  * Cancels the stream
567
590
  * @param reason
@@ -840,10 +863,18 @@ class Stream {
840
863
  return this;
841
864
  }
842
865
  /**
843
- * Consumes the entire stream and store the data in an array. Future is immediately executed
866
+ * Consumes the entire stream and store the data in a collection. Future is immediately executed
844
867
  */
845
868
  collect(collector) {
846
- return collector(this.isParallel ? this.join() : this).schedule();
869
+ return collector(this.isParallel
870
+ ? this.join().map((v) => {
871
+ this.finalActionHooks.forEach((hook) => hook());
872
+ return v;
873
+ })
874
+ : this.map((v) => {
875
+ this.finalActionHooks.forEach((hook) => hook());
876
+ return v;
877
+ })).schedule();
847
878
  }
848
879
  /**
849
880
  * Continuously exhaust the stream until the stream ends or the limit is reached. No result will be provided at
@@ -866,6 +897,7 @@ class Stream {
866
897
  _h = false;
867
898
  try {
868
899
  const _ = _c;
900
+ this.finalActionHooks.forEach((hook) => hook());
869
901
  if (++index >= limit)
870
902
  break;
871
903
  }
@@ -890,6 +922,7 @@ class Stream {
890
922
  _l = false;
891
923
  try {
892
924
  const _ = _f;
925
+ this.finalActionHooks.forEach((hook) => hook());
893
926
  }
894
927
  finally {
895
928
  _l = true;
@@ -916,16 +949,38 @@ class Stream {
916
949
  * @param callback
917
950
  */
918
951
  forEach(callback) {
919
- return new Future((resolve, reject, signal) => __awaiter(this, void 0, void 0, function* () {
920
- var _a;
952
+ return new Future((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
953
+ var _a, e_4, _b, _c;
954
+ var _d;
921
955
  try {
922
- yield this.map((value) => callback(value))
923
- .consume()
924
- .registerSignal(signal);
956
+ try {
957
+ for (var _e = true, _f = __asyncValues(this), _g; _g = yield _f.next(), _a = _g.done, !_a;) {
958
+ _c = _g.value;
959
+ _e = false;
960
+ try {
961
+ const value = _c;
962
+ const result = callback(value);
963
+ if (result instanceof Future || result instanceof Promise) {
964
+ yield result;
965
+ }
966
+ this.finalActionHooks.forEach((hook) => hook());
967
+ }
968
+ finally {
969
+ _e = true;
970
+ }
971
+ }
972
+ }
973
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
974
+ finally {
975
+ try {
976
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
977
+ }
978
+ finally { if (e_4) throw e_4.error; }
979
+ }
925
980
  resolve();
926
981
  }
927
982
  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"));
983
+ reject(error instanceof FutureError ? error : new FutureError((_d = error === null || error === void 0 ? void 0 : error.message) !== null && _d !== void 0 ? _d : "Unknown"));
929
984
  }
930
985
  })).schedule();
931
986
  }
@@ -936,6 +991,7 @@ class Stream {
936
991
  let pending = null;
937
992
  while (!signal.aborted) {
938
993
  const value = yield this.internalNext().registerSignal(signal);
994
+ this.finalActionHooks.forEach((hook) => hook());
939
995
  if (!value.done) {
940
996
  pending === null || pending === void 0 ? void 0 : pending.cancel();
941
997
  const result = callback(value.value);
@@ -969,6 +1025,7 @@ class Stream {
969
1025
  if (value !== null && value !== undefined) {
970
1026
  this.monitorHooks.forEach((hook) => hook(value));
971
1027
  }
1028
+ this.finalActionHooks.forEach((hook) => hook());
972
1029
  if (state !== State.CONTINUE) {
973
1030
  this.done = true;
974
1031
  this.invokeCompletionHooks();
@@ -1076,6 +1133,7 @@ class Stream {
1076
1133
  const preResult = yield this.yieldTrueResult(preProcessor(data), signal);
1077
1134
  const result = yield this.yieldTrueResult(functor(preResult instanceof Promise ? yield preResult : preResult), signal);
1078
1135
  if (result === null || result === undefined) {
1136
+ this.finalActionHooks.forEach((hook) => hook());
1079
1137
  return resolve({ state: State.CONTINUE });
1080
1138
  }
1081
1139
  data = result;
@@ -1288,7 +1346,7 @@ class Stream {
1288
1346
  }
1289
1347
  forwardExecute(preProcessor = R.identity) {
1290
1348
  return Future.of((resolve, reject, signal) => __awaiter(this, void 0, void 0, function* () {
1291
- var _a, e_4, _b, _c;
1349
+ var _a, e_5, _b, _c;
1292
1350
  let pending = [];
1293
1351
  try {
1294
1352
  for (var _d = true, _e = __asyncValues(this.sourceStream.internalIterator()), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
@@ -1312,12 +1370,12 @@ class Stream {
1312
1370
  }
1313
1371
  }
1314
1372
  }
1315
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
1373
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
1316
1374
  finally {
1317
1375
  try {
1318
1376
  if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
1319
1377
  }
1320
- finally { if (e_4) throw e_4.error; }
1378
+ finally { if (e_5) throw e_5.error; }
1321
1379
  }
1322
1380
  const { state, value } = yield this.collectResult(pending, signal);
1323
1381
  if (state === State.CONTINUE) {