gingersnap 0.23.1 → 0.23.3

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.
@@ -49,6 +49,9 @@ class Queue extends WatchableObject.WatchableObject {
49
49
  this.set(this.tail, value);
50
50
  this.tail++;
51
51
  }
52
+ add(value) {
53
+ this.enqueue(value);
54
+ }
52
55
  awaitEnqueue(value) {
53
56
  return future.Future.of((resolve, reject, signal) => _tslib.__awaiter(this, void 0, void 0, function* () {
54
57
  while (this.objectMaxSize && this.size() >= this.objectMaxSize) {
@@ -16,6 +16,7 @@ export declare class Queue<T> extends WatchableObject<number, T> implements Iter
16
16
  clone(): Queue<T>;
17
17
  ingest(stream: Stream<T>): Future<void>;
18
18
  enqueue(value: T): void;
19
+ add(value: T): void;
19
20
  awaitEnqueue(value: T): Future<void>;
20
21
  dequeue(): T;
21
22
  awaitDequeue(abortSignal?: AbortSignal): Future<T>;
@@ -47,6 +47,9 @@ class Queue extends WatchableObject {
47
47
  this.set(this.tail, value);
48
48
  this.tail++;
49
49
  }
50
+ add(value) {
51
+ this.enqueue(value);
52
+ }
50
53
  awaitEnqueue(value) {
51
54
  return Future.of((resolve, reject, signal) => __awaiter(this, void 0, void 0, function* () {
52
55
  while (this.objectMaxSize && this.size() >= this.objectMaxSize) {
@@ -1,11 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var FutureCancelled = require('./FutureCancelled.cjs');
4
-
5
3
  /**
6
4
  * Thrown to indicate that some timed operation has exceeded the maximum duration
7
5
  */
8
- class TimeoutError extends FutureCancelled.FutureCancelled {
6
+ class TimeoutError extends Error {
9
7
  }
10
8
 
11
9
  exports.TimeoutError = TimeoutError;
@@ -1,6 +1,5 @@
1
- import { FutureCancelled } from "./FutureCancelled";
2
1
  /**
3
2
  * Thrown to indicate that some timed operation has exceeded the maximum duration
4
3
  */
5
- export declare class TimeoutError extends FutureCancelled {
4
+ export declare class TimeoutError extends Error {
6
5
  }
@@ -1,9 +1,7 @@
1
- import { FutureCancelled } from './FutureCancelled.mjs';
2
-
3
1
  /**
4
2
  * Thrown to indicate that some timed operation has exceeded the maximum duration
5
3
  */
6
- class TimeoutError extends FutureCancelled {
4
+ class TimeoutError extends Error {
7
5
  }
8
6
 
9
7
  export { TimeoutError };
package/future/future.cjs CHANGED
@@ -91,7 +91,7 @@ class Future {
91
91
  const timeableFuture = Future.sleep(timeout)
92
92
  .registerSignal(value.defaultSignal)
93
93
  .catch(() => { });
94
- return Future.of((resolve, reject) => Promise.race([timeableFuture, value.run()])
94
+ return Future.of((resolve, reject) => Promise.race([timeableFuture, value])
95
95
  .then((v) => {
96
96
  if (value.done) {
97
97
  timeableFuture.cancel();
@@ -130,19 +130,28 @@ class Future {
130
130
  * @param value
131
131
  */
132
132
  static completed(value) {
133
- return new Future((resolve) => resolve(value));
133
+ const future = new Future(() => { });
134
+ future.underLyingPromise = Promise.resolve(value);
135
+ future.completedResult = value;
136
+ future.fulfilled = true;
137
+ future.isRunning = false;
138
+ return future;
134
139
  }
135
140
  /**
136
141
  * Returns a future that fails with the given value
137
142
  * @param value
138
143
  */
139
144
  static exceptionally(value) {
140
- return new Future((_, reject) => reject(value));
145
+ const future = new Future(() => { });
146
+ future.isRunning = false;
147
+ future.failureResult = value;
148
+ future.underLyingPromise = Promise.reject(value);
149
+ return future;
141
150
  }
142
151
  /**
143
152
  * Returns the first completed or failed. If this is cancelled, then all futures provided will also be cancelled
144
153
  * @param futures list of futures
145
- * @param signal optinal abort signal
154
+ * @param signal optional abort signal
146
155
  */
147
156
  static firstCompleted(futures, signal) {
148
157
  return Future.of((resolve, reject, signal) => {
@@ -243,7 +252,7 @@ class Future {
243
252
  * Checks if the future failed
244
253
  */
245
254
  get failed() {
246
- return this.error instanceof Error;
255
+ return this.error !== undefined && this.error instanceof Error;
247
256
  }
248
257
  /**
249
258
  * Retrieves the result of the future, if there is any
@@ -327,7 +336,7 @@ class Future {
327
336
  this.signals.forEach((signal) => newFuture.registerSignal(signal));
328
337
  return newFuture;
329
338
  }
330
- else if (this.done) {
339
+ else if (this.done || this.failed) {
331
340
  const newFuture = Future.wrap(this.underLyingPromise).thenApply(callback);
332
341
  return newFuture;
333
342
  }
@@ -438,24 +447,20 @@ class Future {
438
447
  const resolver = (v) => {
439
448
  if (v instanceof Promise) {
440
449
  v.then((v) => {
441
- this.fulfilled = true;
442
450
  resolve(v);
443
451
  }).catch(reject);
444
452
  }
445
453
  else if (v instanceof Future) {
446
454
  v.run()
447
455
  .then((v) => {
448
- this.fulfilled = true;
449
456
  resolve(v);
450
457
  })
451
458
  .catch(reject);
452
459
  }
453
460
  else if (v instanceof result.FutureResult) {
454
- this.fulfilled = true;
455
461
  resolve(v.value);
456
462
  }
457
463
  else {
458
- this.fulfilled = true;
459
464
  resolve(v);
460
465
  }
461
466
  };
@@ -482,7 +487,6 @@ class Future {
482
487
  result$1
483
488
  .then(() => {
484
489
  if (!this.fulfilled) {
485
- this.fulfilled = true;
486
490
  resolve(null);
487
491
  }
488
492
  })
@@ -492,10 +496,13 @@ class Future {
492
496
  .then((v) => {
493
497
  this.completedResult = v;
494
498
  onFulfilled === null || onFulfilled === void 0 ? void 0 : onFulfilled(v);
499
+ this.isRunning = false;
500
+ this.fulfilled = true;
495
501
  return v;
496
502
  })
497
503
  .catch((e) => {
498
504
  this.failureResult = e instanceof Error ? e : new Error(String(e));
505
+ this.isRunning = false;
499
506
  throw this.failureResult;
500
507
  })
501
508
  .finally(() => {
@@ -508,7 +515,6 @@ class Future {
508
515
  })
509
516
  .finally(() => {
510
517
  this.signals = new Set();
511
- this.isRunning = false;
512
518
  });
513
519
  return this.underLyingPromise;
514
520
  }
@@ -72,11 +72,11 @@ export declare class Future<T> {
72
72
  * Returns a future that fails with the given value
73
73
  * @param value
74
74
  */
75
- static exceptionally(value: Error): Future<unknown>;
75
+ static exceptionally<K>(value: Error): Future<K>;
76
76
  /**
77
77
  * Returns the first completed or failed. If this is cancelled, then all futures provided will also be cancelled
78
78
  * @param futures list of futures
79
- * @param signal optinal abort signal
79
+ * @param signal optional abort signal
80
80
  */
81
81
  static firstCompleted<T extends Array<Future<any>>>(futures: T, signal?: AbortSignal): Future<FutureReturnType<T[number]>>;
82
82
  /**
package/future/future.mjs CHANGED
@@ -70,7 +70,7 @@ class Future {
70
70
  const timeableFuture = Future.sleep(timeout)
71
71
  .registerSignal(value.defaultSignal)
72
72
  .catch(() => { });
73
- return Future.of((resolve, reject) => Promise.race([timeableFuture, value.run()])
73
+ return Future.of((resolve, reject) => Promise.race([timeableFuture, value])
74
74
  .then((v) => {
75
75
  if (value.done) {
76
76
  timeableFuture.cancel();
@@ -109,19 +109,28 @@ class Future {
109
109
  * @param value
110
110
  */
111
111
  static completed(value) {
112
- return new Future((resolve) => resolve(value));
112
+ const future = new Future(() => { });
113
+ future.underLyingPromise = Promise.resolve(value);
114
+ future.completedResult = value;
115
+ future.fulfilled = true;
116
+ future.isRunning = false;
117
+ return future;
113
118
  }
114
119
  /**
115
120
  * Returns a future that fails with the given value
116
121
  * @param value
117
122
  */
118
123
  static exceptionally(value) {
119
- return new Future((_, reject) => reject(value));
124
+ const future = new Future(() => { });
125
+ future.isRunning = false;
126
+ future.failureResult = value;
127
+ future.underLyingPromise = Promise.reject(value);
128
+ return future;
120
129
  }
121
130
  /**
122
131
  * Returns the first completed or failed. If this is cancelled, then all futures provided will also be cancelled
123
132
  * @param futures list of futures
124
- * @param signal optinal abort signal
133
+ * @param signal optional abort signal
125
134
  */
126
135
  static firstCompleted(futures, signal) {
127
136
  return Future.of((resolve, reject, signal) => {
@@ -222,7 +231,7 @@ class Future {
222
231
  * Checks if the future failed
223
232
  */
224
233
  get failed() {
225
- return this.error instanceof Error;
234
+ return this.error !== undefined && this.error instanceof Error;
226
235
  }
227
236
  /**
228
237
  * Retrieves the result of the future, if there is any
@@ -306,7 +315,7 @@ class Future {
306
315
  this.signals.forEach((signal) => newFuture.registerSignal(signal));
307
316
  return newFuture;
308
317
  }
309
- else if (this.done) {
318
+ else if (this.done || this.failed) {
310
319
  const newFuture = Future.wrap(this.underLyingPromise).thenApply(callback);
311
320
  return newFuture;
312
321
  }
@@ -417,24 +426,20 @@ class Future {
417
426
  const resolver = (v) => {
418
427
  if (v instanceof Promise) {
419
428
  v.then((v) => {
420
- this.fulfilled = true;
421
429
  resolve(v);
422
430
  }).catch(reject);
423
431
  }
424
432
  else if (v instanceof Future) {
425
433
  v.run()
426
434
  .then((v) => {
427
- this.fulfilled = true;
428
435
  resolve(v);
429
436
  })
430
437
  .catch(reject);
431
438
  }
432
439
  else if (v instanceof FutureResult) {
433
- this.fulfilled = true;
434
440
  resolve(v.value);
435
441
  }
436
442
  else {
437
- this.fulfilled = true;
438
443
  resolve(v);
439
444
  }
440
445
  };
@@ -461,7 +466,6 @@ class Future {
461
466
  result
462
467
  .then(() => {
463
468
  if (!this.fulfilled) {
464
- this.fulfilled = true;
465
469
  resolve(null);
466
470
  }
467
471
  })
@@ -471,10 +475,13 @@ class Future {
471
475
  .then((v) => {
472
476
  this.completedResult = v;
473
477
  onFulfilled === null || onFulfilled === void 0 ? void 0 : onFulfilled(v);
478
+ this.isRunning = false;
479
+ this.fulfilled = true;
474
480
  return v;
475
481
  })
476
482
  .catch((e) => {
477
483
  this.failureResult = e instanceof Error ? e : new Error(String(e));
484
+ this.isRunning = false;
478
485
  throw this.failureResult;
479
486
  })
480
487
  .finally(() => {
@@ -487,7 +494,6 @@ class Future {
487
494
  })
488
495
  .finally(() => {
489
496
  this.signals = new Set();
490
- this.isRunning = false;
491
497
  });
492
498
  return this.underLyingPromise;
493
499
  }
@@ -131,7 +131,7 @@ class WebSocketService extends NetworkService.NetworkService {
131
131
  data = body.object();
132
132
  }
133
133
  data = R__namespace.set(lens, guid, data);
134
- yield this.socket.send(JSON.stringify(data));
134
+ yield this.socket.sendNow(JSON.stringify(data));
135
135
  return 1;
136
136
  })))
137
137
  .map(() => this.socket.streamView(R__namespace.compose(R__namespace.equals(guid), R__namespace.view(lens)), config.socketRequestReply.objectMaxSize, config.socketRequestReply.expiryPeriod))
@@ -162,13 +162,13 @@ class WebSocketService extends NetworkService.NetworkService {
162
162
  yield this.socket.open();
163
163
  }
164
164
  if (body instanceof model.Model) {
165
- yield this.socket.send(body.blob());
165
+ yield this.socket.sendNow(body.blob());
166
166
  }
167
167
  else if (body instanceof ArrayBuffer || body instanceof Blob) {
168
- yield this.socket.send(body);
168
+ yield this.socket.sendNow(body);
169
169
  }
170
170
  else {
171
- yield this.socket.send(JSON.stringify(body));
171
+ yield this.socket.sendNow(JSON.stringify(body));
172
172
  }
173
173
  const result = oldMethod();
174
174
  if (result instanceof Promise) {
@@ -110,7 +110,7 @@ class WebSocketService extends NetworkService {
110
110
  data = body.object();
111
111
  }
112
112
  data = R.set(lens, guid, data);
113
- yield this.socket.send(JSON.stringify(data));
113
+ yield this.socket.sendNow(JSON.stringify(data));
114
114
  return 1;
115
115
  })))
116
116
  .map(() => this.socket.streamView(R.compose(R.equals(guid), R.view(lens)), config.socketRequestReply.objectMaxSize, config.socketRequestReply.expiryPeriod))
@@ -141,13 +141,13 @@ class WebSocketService extends NetworkService {
141
141
  yield this.socket.open();
142
142
  }
143
143
  if (body instanceof Model) {
144
- yield this.socket.send(body.blob());
144
+ yield this.socket.sendNow(body.blob());
145
145
  }
146
146
  else if (body instanceof ArrayBuffer || body instanceof Blob) {
147
- yield this.socket.send(body);
147
+ yield this.socket.sendNow(body);
148
148
  }
149
149
  else {
150
- yield this.socket.send(JSON.stringify(body));
150
+ yield this.socket.sendNow(JSON.stringify(body));
151
151
  }
152
152
  const result = oldMethod();
153
153
  if (result instanceof Promise) {
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"gingersnap","version":"0.23.1","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/value":{"import":{"types":"./data/value.d.ts","default":"./data/value.mjs"},"require":{"types":"./data/value.d.ts","default":"./data/value.cjs"}},"./store":{"import":{"types":"./store/index.d.ts","default":"./store.mjs"},"require":{"types":"./store/index.d.ts","default":"./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.3","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/value":{"import":{"types":"./data/value.d.ts","default":"./data/value.mjs"},"require":{"types":"./data/value.d.ts","default":"./data/value.cjs"}},"./store":{"import":{"types":"./store/index.d.ts","default":"./store.mjs"},"require":{"types":"./store/index.d.ts","default":"./store.cjs"}},"./functools":{"import":{"types":"./functools/index.d.ts","default":"./functools.mjs"},"require":{"types":"./functools/index.d.ts","default":"./functools.cjs"}}}}
package/socket.cjs CHANGED
@@ -11,6 +11,7 @@ require('ramda');
11
11
  require('uuid');
12
12
  var stream = require('./stream.cjs');
13
13
  var future = require('./future/future.cjs');
14
+ var result = require('./future/result.cjs');
14
15
  var synchronize = require('./synchronize.cjs');
15
16
  var stream_state = require('./stream/state.cjs');
16
17
  var WebSocket = require('modern-isomorphic-ws');
@@ -155,15 +156,28 @@ class StreamableWebSocket {
155
156
  this.signal.dispatchEvent(new CustomEvent("abort"));
156
157
  }
157
158
  /**
158
- * Sends data via socket
159
+ * Sends data via socket as soon as possible
159
160
  * @param data
160
161
  */
161
- send(data) {
162
+ sendNow(data) {
162
163
  var _a;
163
164
  return _tslib.__awaiter(this, void 0, void 0, function* () {
164
165
  (_a = this.getSocket()) === null || _a === void 0 ? void 0 : _a.send(data instanceof Blob ? yield data.arrayBuffer() : data);
165
166
  });
166
167
  }
168
+ /**
169
+ * Sends data via socket, awaiting socket connection if currently disconnected
170
+ * @param data
171
+ */
172
+ send(data) {
173
+ return this.open().thenApply(() => _tslib.__awaiter(this, void 0, void 0, function* () { var _a; return (_a = this.getSocket()) === null || _a === void 0 ? void 0 : _a.send(data instanceof Blob ? yield data.arrayBuffer() : data); }));
174
+ }
175
+ /**
176
+ * Gets a stream of messages that match the given filter provided
177
+ * @param lens filter function to select specific messages
178
+ * @param objectMaxSize max messages to buffer if reading from this stream is slower than messages coming in
179
+ * @param expiryPeriod how long to store buffered messages if not read
180
+ */
167
181
  streamView(lens, objectMaxSize, expiryPeriod) {
168
182
  const queue = new Queue.Queue(objectMaxSize, expiryPeriod);
169
183
  const tuple = Pair.pair(queue, lens);
@@ -200,6 +214,21 @@ class StreamableWebSocket {
200
214
  this.messageQueues = this.messageQueues.filter((v) => v !== queue);
201
215
  });
202
216
  }
217
+ with(functor) {
218
+ return future.Future.of((_, __, signal) => _tslib.__awaiter(this, void 0, void 0, function* () {
219
+ const stream = this.stream().buffer(this.cacheSize);
220
+ yield this.open().registerSignal(signal);
221
+ try {
222
+ functor(new result.FutureResult(stream, signal));
223
+ }
224
+ finally {
225
+ if (!this.closed) {
226
+ this.close();
227
+ }
228
+ stream.cancel();
229
+ }
230
+ })).schedule();
231
+ }
203
232
  addEventListener(type, functor) {
204
233
  this.socketListeners.push([type, functor]);
205
234
  if (this.socket) {
package/socket.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Stream } from "./stream";
2
- import { Future, WaitPeriod } from "./future";
2
+ import { Future, FutureResult, WaitPeriod } from "./future";
3
3
  import { Decoder } from "./data/decoders";
4
+ import { ContextManager } from "./managers";
4
5
  interface WebSocketConfiguration {
5
6
  retryOnDisconnect?: boolean;
6
7
  cacheSize?: number;
@@ -11,7 +12,7 @@ interface WebSocketConfiguration {
11
12
  /**
12
13
  * Future-based web sockets
13
14
  */
14
- export declare class StreamableWebSocket<T> {
15
+ export declare class StreamableWebSocket<T> implements ContextManager<Stream<T>> {
15
16
  private readonly maxReconnectAttempt;
16
17
  private readonly retryOnDisconnect;
17
18
  private openFuture?;
@@ -49,15 +50,27 @@ export declare class StreamableWebSocket<T> {
49
50
  */
50
51
  close(): void;
51
52
  /**
52
- * Sends data via socket
53
+ * Sends data via socket as soon as possible
53
54
  * @param data
54
55
  */
55
- send(data: string | ArrayBufferView | Blob | ArrayBufferLike): Promise<void>;
56
+ sendNow(data: string | ArrayBufferView | Blob | ArrayBufferLike): Promise<void>;
57
+ /**
58
+ * Sends data via socket, awaiting socket connection if currently disconnected
59
+ * @param data
60
+ */
61
+ send(data: string | ArrayBufferView | Blob | ArrayBufferLike): Future<void>;
62
+ /**
63
+ * Gets a stream of messages that match the given filter provided
64
+ * @param lens filter function to select specific messages
65
+ * @param objectMaxSize max messages to buffer if reading from this stream is slower than messages coming in
66
+ * @param expiryPeriod how long to store buffered messages if not read
67
+ */
56
68
  streamView(lens: (v: T) => boolean, objectMaxSize?: number, expiryPeriod?: WaitPeriod): Stream<T>;
57
69
  /**
58
70
  * Gets the stream for messages received via this socket
59
71
  */
60
72
  stream(): Stream<T>;
73
+ with(functor: (value: FutureResult<Stream<T>>) => any): Future<void>;
61
74
  private addEventListener;
62
75
  private createSocket;
63
76
  private getSocket;
package/socket.mjs CHANGED
@@ -9,6 +9,7 @@ import 'ramda';
9
9
  import 'uuid';
10
10
  import { Stream } from './stream.mjs';
11
11
  import { Future } from './future/future.mjs';
12
+ import { FutureResult } from './future/result.mjs';
12
13
  import { FutureEvent, Lock } from './synchronize.mjs';
13
14
  import { ExecutorState } from './stream/state.mjs';
14
15
  import WebSocket from 'modern-isomorphic-ws';
@@ -153,15 +154,28 @@ class StreamableWebSocket {
153
154
  this.signal.dispatchEvent(new CustomEvent("abort"));
154
155
  }
155
156
  /**
156
- * Sends data via socket
157
+ * Sends data via socket as soon as possible
157
158
  * @param data
158
159
  */
159
- send(data) {
160
+ sendNow(data) {
160
161
  var _a;
161
162
  return __awaiter(this, void 0, void 0, function* () {
162
163
  (_a = this.getSocket()) === null || _a === void 0 ? void 0 : _a.send(data instanceof Blob ? yield data.arrayBuffer() : data);
163
164
  });
164
165
  }
166
+ /**
167
+ * Sends data via socket, awaiting socket connection if currently disconnected
168
+ * @param data
169
+ */
170
+ send(data) {
171
+ return this.open().thenApply(() => __awaiter(this, void 0, void 0, function* () { var _a; return (_a = this.getSocket()) === null || _a === void 0 ? void 0 : _a.send(data instanceof Blob ? yield data.arrayBuffer() : data); }));
172
+ }
173
+ /**
174
+ * Gets a stream of messages that match the given filter provided
175
+ * @param lens filter function to select specific messages
176
+ * @param objectMaxSize max messages to buffer if reading from this stream is slower than messages coming in
177
+ * @param expiryPeriod how long to store buffered messages if not read
178
+ */
165
179
  streamView(lens, objectMaxSize, expiryPeriod) {
166
180
  const queue = new Queue(objectMaxSize, expiryPeriod);
167
181
  const tuple = pair(queue, lens);
@@ -198,6 +212,21 @@ class StreamableWebSocket {
198
212
  this.messageQueues = this.messageQueues.filter((v) => v !== queue);
199
213
  });
200
214
  }
215
+ with(functor) {
216
+ return Future.of((_, __, signal) => __awaiter(this, void 0, void 0, function* () {
217
+ const stream = this.stream().buffer(this.cacheSize);
218
+ yield this.open().registerSignal(signal);
219
+ try {
220
+ functor(new FutureResult(stream, signal));
221
+ }
222
+ finally {
223
+ if (!this.closed) {
224
+ this.close();
225
+ }
226
+ stream.cancel();
227
+ }
228
+ })).schedule();
229
+ }
201
230
  addEventListener(type, functor) {
202
231
  this.socketListeners.push([type, functor]);
203
232
  if (this.socket) {
package/stream/index.d.ts CHANGED
@@ -33,6 +33,8 @@ export declare class Stream<T> implements AsyncGenerator<T> {
33
33
  protected cancelHooks: Set<() => any>;
34
34
  protected completionHooks: Set<() => any>;
35
35
  protected completionHookInvoked: boolean;
36
+ protected monitorHooks: Set<(v: T) => void>;
37
+ private frozen;
36
38
  protected done: boolean;
37
39
  protected backlog: Array<{
38
40
  records: T[] | Stream<T>;
@@ -92,11 +94,44 @@ export declare class Stream<T> implements AsyncGenerator<T> {
92
94
  */
93
95
  get future(): Future<T>;
94
96
  get readableStream(): ReadableStream<T>;
97
+ /**
98
+ * Breaks up a stream into 2 other streams based on the callback result. If callback result is true, data will go to
99
+ * the first stream, otherwise it will go to the second stream. Note that splitting the stream will immediately invoke
100
+ * the parent stream if it's not already running
101
+ *
102
+ * Important Note! This will freeze the parent stream (this means you cannot add additional transformers to the parent
103
+ * stream)
104
+ * @param callback
105
+ */
106
+ split(callback: (v: T) => boolean | Promise<boolean> | Future<boolean>): [Stream<T>, Stream<T>];
107
+ /**
108
+ * Creates a new branch from the main stream, that monitors and collects values that matches the filter specified.
109
+ * Note, this does not trigger the main stream to run nor does it alter the main stream. It only collects matched
110
+ * records from main stream once main stream is running.
111
+ *
112
+ * Important Note! This will freeze the parent stream (this means you cannot add additional transformers to the parent
113
+ * stream)
114
+ * @param filter
115
+ * @param objectMaxSize max records to keep in queue, provided you start reading from this stream slower than data is
116
+ * being collected from main stream
117
+ * @param expiryPeriod
118
+ */
119
+ subStream(filter: (v: T) => boolean | Promise<boolean> | Future<boolean>, objectMaxSize?: number, expiryPeriod?: WaitPeriod): Stream<T>;
95
120
  /**
96
121
  * Cancel the stream on the given signal
97
122
  * @param signal
98
123
  */
99
124
  cancelOnSignal(signal: AbortSignal): this;
125
+ /**
126
+ * Waits at most 'period' time for the data upstream to be received, otherwise will cancel the stream
127
+ * @param period
128
+ */
129
+ waitFor(period: WaitPeriod): Stream<T>;
130
+ /**
131
+ * Waits at most 'period' time for the first data upstream to be received, otherwise will cancel the stream
132
+ * @param period
133
+ */
134
+ waitFirstFor(period: WaitPeriod): Stream<T>;
100
135
  parallel(concurrentlyLimit?: number): Stream<T>;
101
136
  /**
102
137
  * Transforms each data on the stream using the callback provided
@@ -253,5 +288,6 @@ export declare class Stream<T> implements AsyncGenerator<T> {
253
288
  private yieldTrueResult;
254
289
  private forwardExecute;
255
290
  private collectResult;
291
+ private get canAlterStream();
256
292
  }
257
293
  export {};
@@ -39,6 +39,30 @@ class Observable {
39
39
  this.publish(reqTopic, data);
40
40
  return future.Future.waitFor(this.subscribe(replyTopic, 2).future, timeout).thenApply(({ value }) => value.data);
41
41
  }
42
+ /**
43
+ * Provides a Request-Multiple-Reply model by sending data over the given topic, and
44
+ * stream responses from the second topic provided. If you cancel the reply stream, a cancellation message
45
+ * can be sent via the cancellationTopic
46
+ * @param reqTopic request topic
47
+ * @param replyTopic reply topic which will be used to form the reply stream
48
+ * @param data request data
49
+ * @param cancellationTopic topic used to send a message if the stream is cancelled
50
+ * @param cancellationData cancellation message
51
+ * @param bufferSize max reply stream message buffer size
52
+ * @param timeout
53
+ */
54
+ requestStream(reqTopic, replyTopic, data, cancellationTopic, cancellationData, bufferSize = 100, timeout = { seconds: 15 }) {
55
+ this.publish(reqTopic, data);
56
+ const stream = this.subscribe(replyTopic, bufferSize);
57
+ return stream
58
+ .waitFirstFor(timeout)
59
+ .map(({ data }) => data)
60
+ .onCancellation(() => {
61
+ if (cancellationTopic) {
62
+ this.publish(cancellationTopic, cancellationData);
63
+ }
64
+ });
65
+ }
42
66
  }
43
67
  /**
44
68
  * Provides Publisher-Subscriber service around an EventTarget
@@ -38,6 +38,19 @@ export declare abstract class Observable<T> {
38
38
  * @param timeout how long to wait for a response, defaults to 15 seconds
39
39
  */
40
40
  request(reqTopic: string, replyTopic: string, data: T, timeout?: WaitPeriod): Future<T>;
41
+ /**
42
+ * Provides a Request-Multiple-Reply model by sending data over the given topic, and
43
+ * stream responses from the second topic provided. If you cancel the reply stream, a cancellation message
44
+ * can be sent via the cancellationTopic
45
+ * @param reqTopic request topic
46
+ * @param replyTopic reply topic which will be used to form the reply stream
47
+ * @param data request data
48
+ * @param cancellationTopic topic used to send a message if the stream is cancelled
49
+ * @param cancellationData cancellation message
50
+ * @param bufferSize max reply stream message buffer size
51
+ * @param timeout
52
+ */
53
+ requestStream(reqTopic: string, replyTopic: string, data: T, cancellationTopic?: string, cancellationData?: T, bufferSize?: number, timeout?: WaitPeriod): Stream<T>;
41
54
  }
42
55
  /**
43
56
  * Provides Publisher-Subscriber service around an EventTarget
@@ -37,6 +37,30 @@ class Observable {
37
37
  this.publish(reqTopic, data);
38
38
  return Future.waitFor(this.subscribe(replyTopic, 2).future, timeout).thenApply(({ value }) => value.data);
39
39
  }
40
+ /**
41
+ * Provides a Request-Multiple-Reply model by sending data over the given topic, and
42
+ * stream responses from the second topic provided. If you cancel the reply stream, a cancellation message
43
+ * can be sent via the cancellationTopic
44
+ * @param reqTopic request topic
45
+ * @param replyTopic reply topic which will be used to form the reply stream
46
+ * @param data request data
47
+ * @param cancellationTopic topic used to send a message if the stream is cancelled
48
+ * @param cancellationData cancellation message
49
+ * @param bufferSize max reply stream message buffer size
50
+ * @param timeout
51
+ */
52
+ requestStream(reqTopic, replyTopic, data, cancellationTopic, cancellationData, bufferSize = 100, timeout = { seconds: 15 }) {
53
+ this.publish(reqTopic, data);
54
+ const stream = this.subscribe(replyTopic, bufferSize);
55
+ return stream
56
+ .waitFirstFor(timeout)
57
+ .map(({ data }) => data)
58
+ .onCancellation(() => {
59
+ if (cancellationTopic) {
60
+ this.publish(cancellationTopic, cancellationData);
61
+ }
62
+ });
63
+ }
40
64
  }
41
65
  /**
42
66
  * Provides Publisher-Subscriber service around an EventTarget
package/stream.cjs CHANGED
@@ -68,6 +68,8 @@ class Stream {
68
68
  this.completionHookInvoked = false;
69
69
  this.runLock = new synchronize.Lock();
70
70
  this.concurrencyLimit = 0;
71
+ this.monitorHooks = new Set();
72
+ this.frozen = false;
71
73
  }
72
74
  /**
73
75
  * Used to provide setup logic that should only be invoked once, when stream
@@ -245,6 +247,66 @@ class Stream {
245
247
  },
246
248
  });
247
249
  }
250
+ /**
251
+ * Breaks up a stream into 2 other streams based on the callback result. If callback result is true, data will go to
252
+ * the first stream, otherwise it will go to the second stream. Note that splitting the stream will immediately invoke
253
+ * the parent stream if it's not already running
254
+ *
255
+ * Important Note! This will freeze the parent stream (this means you cannot add additional transformers to the parent
256
+ * stream)
257
+ * @param callback
258
+ */
259
+ split(callback) {
260
+ this.frozen = true;
261
+ const queue1 = new Queue.Queue();
262
+ const queue2 = new Queue.Queue();
263
+ let canWrite1 = true;
264
+ let canWrite2 = true;
265
+ const future = this.forEach((v) => {
266
+ if (callback(v)) {
267
+ if (canWrite1) {
268
+ queue1.enqueue(v);
269
+ }
270
+ }
271
+ else {
272
+ if (canWrite2) {
273
+ queue2.enqueue(v);
274
+ }
275
+ }
276
+ if (!canWrite1 && !canWrite2) {
277
+ future.cancel();
278
+ }
279
+ });
280
+ return [
281
+ queue1.streamEntries.onCancellation(() => {
282
+ canWrite1 = false;
283
+ }),
284
+ queue2.streamEntries.onCancellation(() => {
285
+ canWrite2 = false;
286
+ }),
287
+ ];
288
+ }
289
+ /**
290
+ * Creates a new branch from the main stream, that monitors and collects values that matches the filter specified.
291
+ * Note, this does not trigger the main stream to run nor does it alter the main stream. It only collects matched
292
+ * records from main stream once main stream is running.
293
+ *
294
+ * Important Note! This will freeze the parent stream (this means you cannot add additional transformers to the parent
295
+ * stream)
296
+ * @param filter
297
+ * @param objectMaxSize max records to keep in queue, provided you start reading from this stream slower than data is
298
+ * being collected from main stream
299
+ * @param expiryPeriod
300
+ */
301
+ subStream(filter, objectMaxSize, expiryPeriod) {
302
+ this.frozen = true;
303
+ const queue = new Queue.Queue(objectMaxSize, expiryPeriod);
304
+ this.monitorHooks.add((v) => {
305
+ if (filter(v))
306
+ queue.enqueue(v);
307
+ });
308
+ return queue.streamEntries;
309
+ }
248
310
  /**
249
311
  * Cancel the stream on the given signal
250
312
  * @param signal
@@ -253,6 +315,38 @@ class Stream {
253
315
  signal.addEventListener("abort", () => this.cancel());
254
316
  return this;
255
317
  }
318
+ /**
319
+ * Waits at most 'period' time for the data upstream to be received, otherwise will cancel the stream
320
+ * @param period
321
+ */
322
+ waitFor(period) {
323
+ const action = () => future.Future.waitFor(this.internalNext(), period);
324
+ return new Stream((signal) => _tslib.__awaiter(this, void 0, void 0, function* () {
325
+ return action()
326
+ .registerSignal(signal)
327
+ .thenApply(({ value }) => new stream_state.ExecutorState(value.done, value.value));
328
+ }));
329
+ }
330
+ /**
331
+ * Waits at most 'period' time for the first data upstream to be received, otherwise will cancel the stream
332
+ * @param period
333
+ */
334
+ waitFirstFor(period) {
335
+ let i = 0;
336
+ const actions = [
337
+ () => future.Future.waitFor(this.internalNext(), period).thenApply(({ value }) => {
338
+ i = 1;
339
+ return value;
340
+ }),
341
+ () => this.internalNext(),
342
+ ];
343
+ return new Stream((signal) => _tslib.__awaiter(this, void 0, void 0, function* () {
344
+ const action = actions[i];
345
+ return action()
346
+ .registerSignal(signal)
347
+ .thenApply(({ value }) => new stream_state.ExecutorState(value.done, value.value));
348
+ }));
349
+ }
256
350
  parallel(concurrentlyLimit = 3) {
257
351
  if (concurrentlyLimit < 1) {
258
352
  throw new IllegalOperationError.IllegalOperationError("Cannot start parallel stream less than 2");
@@ -272,6 +366,8 @@ class Stream {
272
366
  * @param callback
273
367
  */
274
368
  map(callback) {
369
+ if (!this.canAlterStream)
370
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
275
371
  this.actions.push({ type: ActionType.TRANSFORM, functor: callback });
276
372
  return this;
277
373
  }
@@ -280,6 +376,8 @@ class Stream {
280
376
  * @param callback
281
377
  */
282
378
  filter(callback) {
379
+ if (!this.canAlterStream)
380
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
283
381
  this.actions.push({
284
382
  type: ActionType.FILTER,
285
383
  functor: (v) => _tslib.__awaiter(this, void 0, void 0, function* () {
@@ -296,6 +394,8 @@ class Stream {
296
394
  return this;
297
395
  }
298
396
  reduce(initialData, functor, exhaustive = true) {
397
+ if (!this.canAlterStream)
398
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
299
399
  let previousData = initialData;
300
400
  if (exhaustive) {
301
401
  this.actions.push({
@@ -321,6 +421,8 @@ class Stream {
321
421
  return this;
322
422
  }
323
423
  reduceWhile(predicate, initialData, functor, exhaustive = true) {
424
+ if (!this.canAlterStream)
425
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
324
426
  let previousData = initialData;
325
427
  if (exhaustive) {
326
428
  this.actions.push({
@@ -397,6 +499,8 @@ class Stream {
397
499
  * for the split criteria should be added to the chunk, if false then the data will be added to the next chunk
398
500
  */
399
501
  chunk(value, keepSplitCriteria = false) {
502
+ if (!this.canAlterStream)
503
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
400
504
  if (typeof value === "number" && value < 1)
401
505
  throw new Error("Invalid chunk size");
402
506
  let chunkedResults = [];
@@ -503,6 +607,8 @@ class Stream {
503
607
  * @param count
504
608
  */
505
609
  take(count) {
610
+ if (!this.canAlterStream)
611
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
506
612
  let index = 0;
507
613
  this.actions.push({
508
614
  type: ActionType.LIMIT,
@@ -520,6 +626,8 @@ class Stream {
520
626
  * @param predicate
521
627
  */
522
628
  takeWhile(predicate) {
629
+ if (!this.canAlterStream)
630
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
523
631
  this.actions.push({
524
632
  type: ActionType.LIMIT,
525
633
  functor: (value) => {
@@ -536,6 +644,8 @@ class Stream {
536
644
  * @param predicate
537
645
  */
538
646
  skipWhile(predicate) {
647
+ if (!this.canAlterStream)
648
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
539
649
  let startFound = false;
540
650
  this.actions.push({
541
651
  type: ActionType.FILTER,
@@ -555,6 +665,8 @@ class Stream {
555
665
  * @param expiryPeriod
556
666
  */
557
667
  dropRepeats(uniqKeyExtractor, expiryPeriod = undefined) {
668
+ if (!this.canAlterStream)
669
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
558
670
  const cache = new TimeableObject.TimeableObject(undefined, expiryPeriod);
559
671
  this.actions.push({
560
672
  type: ActionType.FILTER,
@@ -580,6 +692,8 @@ class Stream {
580
692
  * @param count
581
693
  */
582
694
  skip(count) {
695
+ if (!this.canAlterStream)
696
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
583
697
  let index = 1;
584
698
  this.actions.push({
585
699
  type: ActionType.FILTER,
@@ -597,6 +711,8 @@ class Stream {
597
711
  * @param period
598
712
  */
599
713
  throttleBy(period) {
714
+ if (!this.canAlterStream)
715
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
600
716
  let future$1;
601
717
  this.actions.push({
602
718
  type: ActionType.TRANSFORM,
@@ -616,6 +732,8 @@ class Stream {
616
732
  * Flattens any nested structure from the data arriving on the stream
617
733
  */
618
734
  flatten() {
735
+ if (!this.canAlterStream)
736
+ throw new IllegalOperationError.IllegalOperationError("Cannot alter the stream as it is frozen");
619
737
  this.actions.push({
620
738
  type: ActionType.UNPACK,
621
739
  functor: (value) => {
@@ -772,6 +890,9 @@ class Stream {
772
890
  const { state, value } = yield (this.sourceStream && this.concurrencyLimit
773
891
  ? this.forwardExecute().registerSignal(signal)
774
892
  : this.__execute__().registerSignal(signal));
893
+ if (value !== null && value !== undefined) {
894
+ this.monitorHooks.forEach((hook) => hook(value));
895
+ }
775
896
  if (state !== exports.State.CONTINUE) {
776
897
  this.done = true;
777
898
  this.invokeCompletionHooks();
@@ -991,6 +1112,9 @@ class Stream {
991
1112
  const { state, value } = yield (this.sourceStream && this.concurrencyLimit
992
1113
  ? this.forwardExecute()
993
1114
  : this.__execute__());
1115
+ if (value !== null && value !== undefined) {
1116
+ this.monitorHooks.forEach((hook) => hook(value));
1117
+ }
994
1118
  if (state === exports.State.MATCHED && value !== undefined)
995
1119
  return { done: false, value };
996
1120
  if (state === exports.State.DONE) {
@@ -1148,6 +1272,9 @@ class Stream {
1148
1272
  return resolve({ state: exports.State.CONTINUE });
1149
1273
  }), signal);
1150
1274
  }
1275
+ get canAlterStream() {
1276
+ return !this.frozen;
1277
+ }
1151
1278
  }
1152
1279
 
1153
1280
  exports.Stream = Stream;
package/stream.mjs CHANGED
@@ -47,6 +47,8 @@ class Stream {
47
47
  this.completionHookInvoked = false;
48
48
  this.runLock = new Lock();
49
49
  this.concurrencyLimit = 0;
50
+ this.monitorHooks = new Set();
51
+ this.frozen = false;
50
52
  }
51
53
  /**
52
54
  * Used to provide setup logic that should only be invoked once, when stream
@@ -224,6 +226,66 @@ class Stream {
224
226
  },
225
227
  });
226
228
  }
229
+ /**
230
+ * Breaks up a stream into 2 other streams based on the callback result. If callback result is true, data will go to
231
+ * the first stream, otherwise it will go to the second stream. Note that splitting the stream will immediately invoke
232
+ * the parent stream if it's not already running
233
+ *
234
+ * Important Note! This will freeze the parent stream (this means you cannot add additional transformers to the parent
235
+ * stream)
236
+ * @param callback
237
+ */
238
+ split(callback) {
239
+ this.frozen = true;
240
+ const queue1 = new Queue();
241
+ const queue2 = new Queue();
242
+ let canWrite1 = true;
243
+ let canWrite2 = true;
244
+ const future = this.forEach((v) => {
245
+ if (callback(v)) {
246
+ if (canWrite1) {
247
+ queue1.enqueue(v);
248
+ }
249
+ }
250
+ else {
251
+ if (canWrite2) {
252
+ queue2.enqueue(v);
253
+ }
254
+ }
255
+ if (!canWrite1 && !canWrite2) {
256
+ future.cancel();
257
+ }
258
+ });
259
+ return [
260
+ queue1.streamEntries.onCancellation(() => {
261
+ canWrite1 = false;
262
+ }),
263
+ queue2.streamEntries.onCancellation(() => {
264
+ canWrite2 = false;
265
+ }),
266
+ ];
267
+ }
268
+ /**
269
+ * Creates a new branch from the main stream, that monitors and collects values that matches the filter specified.
270
+ * Note, this does not trigger the main stream to run nor does it alter the main stream. It only collects matched
271
+ * records from main stream once main stream is running.
272
+ *
273
+ * Important Note! This will freeze the parent stream (this means you cannot add additional transformers to the parent
274
+ * stream)
275
+ * @param filter
276
+ * @param objectMaxSize max records to keep in queue, provided you start reading from this stream slower than data is
277
+ * being collected from main stream
278
+ * @param expiryPeriod
279
+ */
280
+ subStream(filter, objectMaxSize, expiryPeriod) {
281
+ this.frozen = true;
282
+ const queue = new Queue(objectMaxSize, expiryPeriod);
283
+ this.monitorHooks.add((v) => {
284
+ if (filter(v))
285
+ queue.enqueue(v);
286
+ });
287
+ return queue.streamEntries;
288
+ }
227
289
  /**
228
290
  * Cancel the stream on the given signal
229
291
  * @param signal
@@ -232,6 +294,38 @@ class Stream {
232
294
  signal.addEventListener("abort", () => this.cancel());
233
295
  return this;
234
296
  }
297
+ /**
298
+ * Waits at most 'period' time for the data upstream to be received, otherwise will cancel the stream
299
+ * @param period
300
+ */
301
+ waitFor(period) {
302
+ const action = () => Future.waitFor(this.internalNext(), period);
303
+ return new Stream((signal) => __awaiter(this, void 0, void 0, function* () {
304
+ return action()
305
+ .registerSignal(signal)
306
+ .thenApply(({ value }) => new ExecutorState(value.done, value.value));
307
+ }));
308
+ }
309
+ /**
310
+ * Waits at most 'period' time for the first data upstream to be received, otherwise will cancel the stream
311
+ * @param period
312
+ */
313
+ waitFirstFor(period) {
314
+ let i = 0;
315
+ const actions = [
316
+ () => Future.waitFor(this.internalNext(), period).thenApply(({ value }) => {
317
+ i = 1;
318
+ return value;
319
+ }),
320
+ () => this.internalNext(),
321
+ ];
322
+ return new Stream((signal) => __awaiter(this, void 0, void 0, function* () {
323
+ const action = actions[i];
324
+ return action()
325
+ .registerSignal(signal)
326
+ .thenApply(({ value }) => new ExecutorState(value.done, value.value));
327
+ }));
328
+ }
235
329
  parallel(concurrentlyLimit = 3) {
236
330
  if (concurrentlyLimit < 1) {
237
331
  throw new IllegalOperationError("Cannot start parallel stream less than 2");
@@ -251,6 +345,8 @@ class Stream {
251
345
  * @param callback
252
346
  */
253
347
  map(callback) {
348
+ if (!this.canAlterStream)
349
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
254
350
  this.actions.push({ type: ActionType.TRANSFORM, functor: callback });
255
351
  return this;
256
352
  }
@@ -259,6 +355,8 @@ class Stream {
259
355
  * @param callback
260
356
  */
261
357
  filter(callback) {
358
+ if (!this.canAlterStream)
359
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
262
360
  this.actions.push({
263
361
  type: ActionType.FILTER,
264
362
  functor: (v) => __awaiter(this, void 0, void 0, function* () {
@@ -275,6 +373,8 @@ class Stream {
275
373
  return this;
276
374
  }
277
375
  reduce(initialData, functor, exhaustive = true) {
376
+ if (!this.canAlterStream)
377
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
278
378
  let previousData = initialData;
279
379
  if (exhaustive) {
280
380
  this.actions.push({
@@ -300,6 +400,8 @@ class Stream {
300
400
  return this;
301
401
  }
302
402
  reduceWhile(predicate, initialData, functor, exhaustive = true) {
403
+ if (!this.canAlterStream)
404
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
303
405
  let previousData = initialData;
304
406
  if (exhaustive) {
305
407
  this.actions.push({
@@ -376,6 +478,8 @@ class Stream {
376
478
  * for the split criteria should be added to the chunk, if false then the data will be added to the next chunk
377
479
  */
378
480
  chunk(value, keepSplitCriteria = false) {
481
+ if (!this.canAlterStream)
482
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
379
483
  if (typeof value === "number" && value < 1)
380
484
  throw new Error("Invalid chunk size");
381
485
  let chunkedResults = [];
@@ -482,6 +586,8 @@ class Stream {
482
586
  * @param count
483
587
  */
484
588
  take(count) {
589
+ if (!this.canAlterStream)
590
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
485
591
  let index = 0;
486
592
  this.actions.push({
487
593
  type: ActionType.LIMIT,
@@ -499,6 +605,8 @@ class Stream {
499
605
  * @param predicate
500
606
  */
501
607
  takeWhile(predicate) {
608
+ if (!this.canAlterStream)
609
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
502
610
  this.actions.push({
503
611
  type: ActionType.LIMIT,
504
612
  functor: (value) => {
@@ -515,6 +623,8 @@ class Stream {
515
623
  * @param predicate
516
624
  */
517
625
  skipWhile(predicate) {
626
+ if (!this.canAlterStream)
627
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
518
628
  let startFound = false;
519
629
  this.actions.push({
520
630
  type: ActionType.FILTER,
@@ -534,6 +644,8 @@ class Stream {
534
644
  * @param expiryPeriod
535
645
  */
536
646
  dropRepeats(uniqKeyExtractor, expiryPeriod = undefined) {
647
+ if (!this.canAlterStream)
648
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
537
649
  const cache = new TimeableObject(undefined, expiryPeriod);
538
650
  this.actions.push({
539
651
  type: ActionType.FILTER,
@@ -559,6 +671,8 @@ class Stream {
559
671
  * @param count
560
672
  */
561
673
  skip(count) {
674
+ if (!this.canAlterStream)
675
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
562
676
  let index = 1;
563
677
  this.actions.push({
564
678
  type: ActionType.FILTER,
@@ -576,6 +690,8 @@ class Stream {
576
690
  * @param period
577
691
  */
578
692
  throttleBy(period) {
693
+ if (!this.canAlterStream)
694
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
579
695
  let future;
580
696
  this.actions.push({
581
697
  type: ActionType.TRANSFORM,
@@ -595,6 +711,8 @@ class Stream {
595
711
  * Flattens any nested structure from the data arriving on the stream
596
712
  */
597
713
  flatten() {
714
+ if (!this.canAlterStream)
715
+ throw new IllegalOperationError("Cannot alter the stream as it is frozen");
598
716
  this.actions.push({
599
717
  type: ActionType.UNPACK,
600
718
  functor: (value) => {
@@ -751,6 +869,9 @@ class Stream {
751
869
  const { state, value } = yield (this.sourceStream && this.concurrencyLimit
752
870
  ? this.forwardExecute().registerSignal(signal)
753
871
  : this.__execute__().registerSignal(signal));
872
+ if (value !== null && value !== undefined) {
873
+ this.monitorHooks.forEach((hook) => hook(value));
874
+ }
754
875
  if (state !== State.CONTINUE) {
755
876
  this.done = true;
756
877
  this.invokeCompletionHooks();
@@ -970,6 +1091,9 @@ class Stream {
970
1091
  const { state, value } = yield (this.sourceStream && this.concurrencyLimit
971
1092
  ? this.forwardExecute()
972
1093
  : this.__execute__());
1094
+ if (value !== null && value !== undefined) {
1095
+ this.monitorHooks.forEach((hook) => hook(value));
1096
+ }
973
1097
  if (state === State.MATCHED && value !== undefined)
974
1098
  return { done: false, value };
975
1099
  if (state === State.DONE) {
@@ -1127,6 +1251,9 @@ class Stream {
1127
1251
  return resolve({ state: State.CONTINUE });
1128
1252
  }), signal);
1129
1253
  }
1254
+ get canAlterStream() {
1255
+ return !this.frozen;
1256
+ }
1130
1257
  }
1131
1258
 
1132
1259
  export { State, Stream };