@aleph-ai/tinyaleph 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core/events.js ADDED
@@ -0,0 +1,907 @@
1
+ /**
2
+ * Aleph Event System
3
+ *
4
+ * Event-driven monitoring for real-time applications.
5
+ *
6
+ * Features:
7
+ * - EventEmitter-based for Node.js compatibility
8
+ * - Named events for different state changes
9
+ * - Subscription management
10
+ * - Throttling and debouncing
11
+ * - Event history buffer
12
+ *
13
+ * Events:
14
+ * - 'tick': Each time step {t, state, entropy, coherence}
15
+ * - 'collapse': State collapse {from, to, probability}
16
+ * - 'resonance': Strong resonance {primes, strength}
17
+ * - 'sync': Synchronization crossed {orderParameter, clusters}
18
+ * - 'entropy:low': Entropy below threshold {value, threshold}
19
+ * - 'entropy:high': Entropy above threshold {value, threshold}
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ /**
25
+ * AlephEventEmitter - Core event system
26
+ *
27
+ * Compatible with Node.js EventEmitter pattern but standalone.
28
+ */
29
+ class AlephEventEmitter {
30
+ constructor(options = {}) {
31
+ this._listeners = new Map();
32
+ this._onceListeners = new Map();
33
+ this._history = [];
34
+ this._maxHistoryLength = options.maxHistory ?? 1000;
35
+ this._throttleIntervals = new Map();
36
+ this._lastEmitTime = new Map();
37
+ this._paused = false;
38
+
39
+ // Statistics
40
+ this._stats = {
41
+ totalEmitted: 0,
42
+ eventCounts: new Map()
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Add event listener
48
+ * @param {string} event - Event name
49
+ * @param {Function} callback - Handler function
50
+ * @returns {AlephEventEmitter} this for chaining
51
+ */
52
+ on(event, callback) {
53
+ if (!this._listeners.has(event)) {
54
+ this._listeners.set(event, new Set());
55
+ }
56
+ this._listeners.get(event).add(callback);
57
+ return this;
58
+ }
59
+
60
+ /**
61
+ * Add one-time event listener
62
+ * @param {string} event - Event name
63
+ * @param {Function} callback - Handler function
64
+ * @returns {AlephEventEmitter} this for chaining
65
+ */
66
+ once(event, callback) {
67
+ if (!this._onceListeners.has(event)) {
68
+ this._onceListeners.set(event, new Set());
69
+ }
70
+ this._onceListeners.get(event).add(callback);
71
+ return this;
72
+ }
73
+
74
+ /**
75
+ * Remove event listener
76
+ * @param {string} event - Event name
77
+ * @param {Function} callback - Handler function to remove
78
+ * @returns {AlephEventEmitter} this for chaining
79
+ */
80
+ off(event, callback) {
81
+ if (this._listeners.has(event)) {
82
+ this._listeners.get(event).delete(callback);
83
+ }
84
+ if (this._onceListeners.has(event)) {
85
+ this._onceListeners.get(event).delete(callback);
86
+ }
87
+ return this;
88
+ }
89
+
90
+ /**
91
+ * Remove all listeners for an event (or all events if no event specified)
92
+ * @param {string} [event] - Event name (optional)
93
+ */
94
+ removeAllListeners(event) {
95
+ if (event) {
96
+ this._listeners.delete(event);
97
+ this._onceListeners.delete(event);
98
+ } else {
99
+ this._listeners.clear();
100
+ this._onceListeners.clear();
101
+ }
102
+ return this;
103
+ }
104
+
105
+ /**
106
+ * Emit an event
107
+ * @param {string} event - Event name
108
+ * @param {*} data - Event data
109
+ * @returns {boolean} Whether any listeners were called
110
+ */
111
+ emit(event, data) {
112
+ if (this._paused) return false;
113
+
114
+ // Check throttling
115
+ if (this._throttleIntervals.has(event)) {
116
+ const interval = this._throttleIntervals.get(event);
117
+ const lastTime = this._lastEmitTime.get(event) || 0;
118
+ const now = Date.now();
119
+
120
+ if (now - lastTime < interval) {
121
+ return false; // Throttled
122
+ }
123
+ this._lastEmitTime.set(event, now);
124
+ }
125
+
126
+ const eventData = {
127
+ event,
128
+ data,
129
+ timestamp: Date.now()
130
+ };
131
+
132
+ // Add to history
133
+ this._history.push(eventData);
134
+ if (this._history.length > this._maxHistoryLength) {
135
+ this._history.shift();
136
+ }
137
+
138
+ // Update stats
139
+ this._stats.totalEmitted++;
140
+ this._stats.eventCounts.set(
141
+ event,
142
+ (this._stats.eventCounts.get(event) || 0) + 1
143
+ );
144
+
145
+ let called = false;
146
+
147
+ // Call regular listeners
148
+ if (this._listeners.has(event)) {
149
+ for (const callback of this._listeners.get(event)) {
150
+ try {
151
+ callback(data);
152
+ called = true;
153
+ } catch (err) {
154
+ console.error(`Error in event listener for '${event}':`, err);
155
+ }
156
+ }
157
+ }
158
+
159
+ // Call and remove once listeners
160
+ if (this._onceListeners.has(event)) {
161
+ const onceCallbacks = this._onceListeners.get(event);
162
+ this._onceListeners.delete(event);
163
+
164
+ for (const callback of onceCallbacks) {
165
+ try {
166
+ callback(data);
167
+ called = true;
168
+ } catch (err) {
169
+ console.error(`Error in once listener for '${event}':`, err);
170
+ }
171
+ }
172
+ }
173
+
174
+ // Emit wildcard event
175
+ if (event !== '*' && this._listeners.has('*')) {
176
+ for (const callback of this._listeners.get('*')) {
177
+ try {
178
+ callback(eventData);
179
+ called = true;
180
+ } catch (err) {
181
+ console.error(`Error in wildcard listener:`, err);
182
+ }
183
+ }
184
+ }
185
+
186
+ return called;
187
+ }
188
+
189
+ /**
190
+ * Set throttle interval for an event
191
+ * @param {string} event - Event name
192
+ * @param {number} interval - Minimum ms between emissions
193
+ */
194
+ throttle(event, interval) {
195
+ this._throttleIntervals.set(event, interval);
196
+ return this;
197
+ }
198
+
199
+ /**
200
+ * Remove throttling for an event
201
+ * @param {string} event - Event name
202
+ */
203
+ unthrottle(event) {
204
+ this._throttleIntervals.delete(event);
205
+ this._lastEmitTime.delete(event);
206
+ return this;
207
+ }
208
+
209
+ /**
210
+ * Pause all event emissions
211
+ */
212
+ pause() {
213
+ this._paused = true;
214
+ return this;
215
+ }
216
+
217
+ /**
218
+ * Resume event emissions
219
+ */
220
+ resume() {
221
+ this._paused = false;
222
+ return this;
223
+ }
224
+
225
+ /**
226
+ * Check if paused
227
+ */
228
+ isPaused() {
229
+ return this._paused;
230
+ }
231
+
232
+ /**
233
+ * Get listener count for an event
234
+ * @param {string} event - Event name
235
+ */
236
+ listenerCount(event) {
237
+ const regular = this._listeners.get(event)?.size || 0;
238
+ const once = this._onceListeners.get(event)?.size || 0;
239
+ return regular + once;
240
+ }
241
+
242
+ /**
243
+ * Get all registered event names
244
+ */
245
+ eventNames() {
246
+ const names = new Set([
247
+ ...this._listeners.keys(),
248
+ ...this._onceListeners.keys()
249
+ ]);
250
+ return Array.from(names);
251
+ }
252
+
253
+ /**
254
+ * Get event history
255
+ * @param {string} [event] - Filter by event name
256
+ * @param {number} [limit] - Maximum entries to return
257
+ */
258
+ getHistory(event = null, limit = 100) {
259
+ let history = this._history;
260
+
261
+ if (event) {
262
+ history = history.filter(h => h.event === event);
263
+ }
264
+
265
+ return history.slice(-limit);
266
+ }
267
+
268
+ /**
269
+ * Clear event history
270
+ */
271
+ clearHistory() {
272
+ this._history = [];
273
+ return this;
274
+ }
275
+
276
+ /**
277
+ * Get event statistics
278
+ */
279
+ getStats() {
280
+ return {
281
+ totalEmitted: this._stats.totalEmitted,
282
+ eventCounts: Object.fromEntries(this._stats.eventCounts),
283
+ historyLength: this._history.length,
284
+ listenerCounts: Object.fromEntries(
285
+ this.eventNames().map(e => [e, this.listenerCount(e)])
286
+ )
287
+ };
288
+ }
289
+
290
+ /**
291
+ * Reset statistics
292
+ */
293
+ resetStats() {
294
+ this._stats.totalEmitted = 0;
295
+ this._stats.eventCounts.clear();
296
+ return this;
297
+ }
298
+
299
+ /**
300
+ * Create a filtered emitter that only emits matching events
301
+ * @param {Function} predicate - Filter function (eventData) => boolean
302
+ */
303
+ filter(predicate) {
304
+ const filtered = new AlephEventEmitter();
305
+
306
+ this.on('*', (eventData) => {
307
+ if (predicate(eventData)) {
308
+ filtered.emit(eventData.event, eventData.data);
309
+ }
310
+ });
311
+
312
+ return filtered;
313
+ }
314
+
315
+ /**
316
+ * Create a mapped emitter that transforms event data
317
+ * @param {Function} transform - Transform function (data) => newData
318
+ */
319
+ map(transform) {
320
+ const mapped = new AlephEventEmitter();
321
+
322
+ this.on('*', (eventData) => {
323
+ mapped.emit(eventData.event, transform(eventData.data));
324
+ });
325
+
326
+ return mapped;
327
+ }
328
+
329
+ /**
330
+ * Promise-based wait for next event
331
+ * @param {string} event - Event name
332
+ * @param {number} [timeout] - Timeout in ms
333
+ */
334
+ waitFor(event, timeout = null) {
335
+ return new Promise((resolve, reject) => {
336
+ const handler = (data) => {
337
+ if (timeoutId) clearTimeout(timeoutId);
338
+ resolve(data);
339
+ };
340
+
341
+ let timeoutId = null;
342
+ if (timeout) {
343
+ timeoutId = setTimeout(() => {
344
+ this.off(event, handler);
345
+ reject(new Error(`Timeout waiting for event '${event}'`));
346
+ }, timeout);
347
+ }
348
+
349
+ this.once(event, handler);
350
+ });
351
+ }
352
+
353
+ /**
354
+ * Collect events into batches
355
+ * @param {string} event - Event name
356
+ * @param {number} size - Batch size
357
+ * @param {Function} callback - Handler for batch
358
+ */
359
+ batch(event, size, callback) {
360
+ const buffer = [];
361
+
362
+ this.on(event, (data) => {
363
+ buffer.push(data);
364
+
365
+ if (buffer.length >= size) {
366
+ callback([...buffer]);
367
+ buffer.length = 0;
368
+ }
369
+ });
370
+
371
+ return this;
372
+ }
373
+
374
+ /**
375
+ * Debounce an event (emit only after silence)
376
+ * @param {string} event - Event name
377
+ * @param {number} delay - Delay in ms
378
+ * @param {Function} callback - Handler function
379
+ */
380
+ debounce(event, delay, callback) {
381
+ let timeoutId = null;
382
+ let lastData = null;
383
+
384
+ this.on(event, (data) => {
385
+ lastData = data;
386
+
387
+ if (timeoutId) {
388
+ clearTimeout(timeoutId);
389
+ }
390
+
391
+ timeoutId = setTimeout(() => {
392
+ callback(lastData);
393
+ timeoutId = null;
394
+ }, delay);
395
+ });
396
+
397
+ return this;
398
+ }
399
+ }
400
+
401
+ /**
402
+ * AlephMonitor - High-level monitoring for AlephEngine
403
+ *
404
+ * Wraps an AlephEngine and emits structured events.
405
+ */
406
+ class AlephMonitor {
407
+ /**
408
+ * @param {object} engine - AlephEngine instance
409
+ * @param {object} options - Configuration
410
+ */
411
+ constructor(engine, options = {}) {
412
+ this.engine = engine;
413
+ this.emitter = new AlephEventEmitter({
414
+ maxHistory: options.maxHistory ?? 1000
415
+ });
416
+
417
+ // Thresholds
418
+ this.thresholds = {
419
+ entropyLow: options.entropyLow ?? 1.0,
420
+ entropyHigh: options.entropyHigh ?? 3.0,
421
+ coherenceHigh: options.coherenceHigh ?? 0.8,
422
+ resonanceStrong: options.resonanceStrong ?? 0.7,
423
+ syncThreshold: options.syncThreshold ?? 0.7
424
+ };
425
+
426
+ // State tracking
427
+ this._lastState = null;
428
+ this._stepCount = 0;
429
+ this._startTime = Date.now();
430
+ }
431
+
432
+ /**
433
+ * Get event emitter for subscribing
434
+ */
435
+ getEmitter() {
436
+ return this.emitter;
437
+ }
438
+
439
+ /**
440
+ * Convenience method to subscribe to events
441
+ */
442
+ on(event, callback) {
443
+ this.emitter.on(event, callback);
444
+ return this;
445
+ }
446
+
447
+ /**
448
+ * Wrap engine.tick() with monitoring
449
+ * @param {number} dt - Time step
450
+ */
451
+ tick(dt) {
452
+ const prevState = this._lastState;
453
+
454
+ // Call engine tick
455
+ this.engine.tick(dt);
456
+
457
+ // Get current state
458
+ const state = this.engine.getPhysicsState();
459
+ this._lastState = state;
460
+ this._stepCount++;
461
+
462
+ // Emit tick event
463
+ this.emitter.emit('tick', {
464
+ t: this._stepCount,
465
+ dt,
466
+ entropy: state.entropy,
467
+ coherence: state.coherence,
468
+ orderParameter: state.orderParameter,
469
+ stability: state.stability,
470
+ coupling: state.coupling
471
+ });
472
+
473
+ // Check for threshold crossings
474
+ this._checkThresholds(state, prevState);
475
+
476
+ return state;
477
+ }
478
+
479
+ /**
480
+ * Check threshold crossings and emit events
481
+ * @private
482
+ */
483
+ _checkThresholds(state, prevState) {
484
+ // Entropy crossings
485
+ if (state.entropy < this.thresholds.entropyLow) {
486
+ if (!prevState || prevState.entropy >= this.thresholds.entropyLow) {
487
+ this.emitter.emit('entropy:low', {
488
+ value: state.entropy,
489
+ threshold: this.thresholds.entropyLow
490
+ });
491
+ }
492
+ }
493
+
494
+ if (state.entropy > this.thresholds.entropyHigh) {
495
+ if (!prevState || prevState.entropy <= this.thresholds.entropyHigh) {
496
+ this.emitter.emit('entropy:high', {
497
+ value: state.entropy,
498
+ threshold: this.thresholds.entropyHigh
499
+ });
500
+ }
501
+ }
502
+
503
+ // Synchronization
504
+ if (state.orderParameter > this.thresholds.syncThreshold) {
505
+ if (!prevState || prevState.orderParameter <= this.thresholds.syncThreshold) {
506
+ this.emitter.emit('sync', {
507
+ orderParameter: state.orderParameter,
508
+ threshold: this.thresholds.syncThreshold
509
+ });
510
+ }
511
+ }
512
+
513
+ // Coherence
514
+ if (state.coherence > this.thresholds.coherenceHigh) {
515
+ if (!prevState || prevState.coherence <= this.thresholds.coherenceHigh) {
516
+ this.emitter.emit('coherence:high', {
517
+ value: state.coherence,
518
+ threshold: this.thresholds.coherenceHigh
519
+ });
520
+ }
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Emit collapse event
526
+ * @param {object} from - State before collapse
527
+ * @param {object} to - State after collapse
528
+ * @param {number} probability - Collapse probability
529
+ */
530
+ emitCollapse(from, to, probability) {
531
+ this.emitter.emit('collapse', { from, to, probability });
532
+ }
533
+
534
+ /**
535
+ * Emit resonance event
536
+ * @param {number[]} primes - Resonating primes
537
+ * @param {number} strength - Resonance strength
538
+ */
539
+ emitResonance(primes, strength) {
540
+ if (strength > this.thresholds.resonanceStrong) {
541
+ this.emitter.emit('resonance', { primes, strength });
542
+ }
543
+ }
544
+
545
+ /**
546
+ * Wrap engine.run() with monitoring
547
+ * @param {string} input - Input to process
548
+ */
549
+ run(input) {
550
+ const startTime = Date.now();
551
+
552
+ this.emitter.emit('run:start', { input, startTime });
553
+
554
+ const result = this.engine.run(input);
555
+
556
+ const endTime = Date.now();
557
+
558
+ this.emitter.emit('run:complete', {
559
+ input,
560
+ output: result.output,
561
+ entropy: result.entropy,
562
+ coherence: result.coherence,
563
+ fieldBased: result.fieldBased,
564
+ duration: endTime - startTime
565
+ });
566
+
567
+ // Check for collapse
568
+ if (result.collapsed) {
569
+ this.emitCollapse(
570
+ { entropy: result.entropy, primes: result.inputPrimes },
571
+ { primes: result.resultPrimes },
572
+ result.collapseProbability || 1.0
573
+ );
574
+ }
575
+
576
+ return result;
577
+ }
578
+
579
+ /**
580
+ * Get monitoring statistics
581
+ */
582
+ getStats() {
583
+ return {
584
+ stepCount: this._stepCount,
585
+ runTime: Date.now() - this._startTime,
586
+ events: this.emitter.getStats()
587
+ };
588
+ }
589
+
590
+ /**
591
+ * Reset monitor state
592
+ */
593
+ reset() {
594
+ this._lastState = null;
595
+ this._stepCount = 0;
596
+ this._startTime = Date.now();
597
+ this.emitter.clearHistory();
598
+ this.emitter.resetStats();
599
+ return this;
600
+ }
601
+ }
602
+
603
+ /**
604
+ * EvolutionStream - Async iterator for engine evolution
605
+ *
606
+ * Enables for-await-of loops over engine state.
607
+ */
608
+ class EvolutionStream {
609
+ /**
610
+ * @param {object} engine - AlephEngine instance or evolvable object
611
+ * @param {object} options - Configuration
612
+ */
613
+ constructor(engine, options = {}) {
614
+ this.engine = engine;
615
+ this.dt = options.dt ?? 0.01;
616
+ this.maxSteps = options.maxSteps ?? Infinity;
617
+ this.stopCondition = options.stopCondition ?? null;
618
+ this._step = 0;
619
+ this._stopped = false;
620
+
621
+ // Adapter functions for different engine types
622
+ this._tick = options.tickFn ?? ((dt) => {
623
+ if (typeof this.engine.tick === 'function') {
624
+ this.engine.tick(dt);
625
+ }
626
+ });
627
+
628
+ this._getState = options.getStateFn ?? (() => {
629
+ if (typeof this.engine.getPhysicsState === 'function') {
630
+ return this.engine.getPhysicsState();
631
+ }
632
+ // Try common patterns
633
+ const state = {};
634
+ if (typeof this.engine.orderParameter === 'function') {
635
+ state.orderParameter = this.engine.orderParameter();
636
+ }
637
+ if (typeof this.engine.synchronization === 'function') {
638
+ state.synchronization = this.engine.synchronization();
639
+ }
640
+ if (typeof this.engine.entropy === 'function') {
641
+ state.entropy = this.engine.entropy();
642
+ }
643
+ if (this.engine.oscillators) {
644
+ state.oscillators = this.engine.oscillators.length;
645
+ }
646
+ return state;
647
+ });
648
+ }
649
+
650
+ /**
651
+ * Create stream from any evolvable object
652
+ * @param {object} evolvable - Object with tick-like method
653
+ * @param {object} options - Configuration
654
+ */
655
+ static fromEvolvable(evolvable, options = {}) {
656
+ return new EvolutionStream(evolvable, options);
657
+ }
658
+
659
+ /**
660
+ * Stop the stream
661
+ */
662
+ stop() {
663
+ this._stopped = true;
664
+ }
665
+
666
+ /**
667
+ * Make stream async iterable
668
+ */
669
+ async *[Symbol.asyncIterator]() {
670
+ while (!this._stopped && this._step < this.maxSteps) {
671
+ this._tick(this.dt);
672
+
673
+ const state = this._getState();
674
+ const data = {
675
+ step: this._step,
676
+ t: this._step * this.dt,
677
+ ...state
678
+ };
679
+
680
+ yield data;
681
+
682
+ // Check stop condition
683
+ if (this.stopCondition && this.stopCondition(data)) {
684
+ this._stopped = true;
685
+ }
686
+
687
+ this._step++;
688
+
689
+ // Yield to event loop
690
+ await new Promise(resolve => setImmediate(resolve));
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Collect stream into array
696
+ * @param {number} maxItems - Maximum items to collect
697
+ */
698
+ async collect(maxItems = 1000) {
699
+ const items = [];
700
+
701
+ for await (const state of this) {
702
+ items.push(state);
703
+ if (items.length >= maxItems) break;
704
+ }
705
+
706
+ return items;
707
+ }
708
+
709
+ /**
710
+ * Batch items into groups
711
+ * @param {number} size - Batch size
712
+ */
713
+ batch(size) {
714
+ const source = this;
715
+
716
+ return {
717
+ async *[Symbol.asyncIterator]() {
718
+ let batch = [];
719
+ for await (const state of source) {
720
+ batch.push(state);
721
+ if (batch.length >= size) {
722
+ yield batch;
723
+ batch = [];
724
+ }
725
+ }
726
+ if (batch.length > 0) {
727
+ yield batch;
728
+ }
729
+ }
730
+ };
731
+ }
732
+
733
+ /**
734
+ * Apply filter to stream
735
+ * @param {Function} predicate - Filter function
736
+ */
737
+ filter(predicate) {
738
+ const source = this;
739
+
740
+ const result = {
741
+ async *[Symbol.asyncIterator]() {
742
+ for await (const state of source) {
743
+ if (predicate(state)) {
744
+ yield state;
745
+ }
746
+ }
747
+ },
748
+ take(n) {
749
+ return EvolutionStream.prototype.take.call({ [Symbol.asyncIterator]: result[Symbol.asyncIterator] }, n);
750
+ },
751
+ collect(max) {
752
+ return EvolutionStream.prototype.collect.call({ [Symbol.asyncIterator]: result[Symbol.asyncIterator] }, max);
753
+ }
754
+ };
755
+ return result;
756
+ }
757
+
758
+ /**
759
+ * Apply transform to stream
760
+ * @param {Function} transform - Transform function
761
+ */
762
+ map(transform) {
763
+ const source = this;
764
+
765
+ const result = {
766
+ async *[Symbol.asyncIterator]() {
767
+ for await (const state of source) {
768
+ yield transform(state);
769
+ }
770
+ },
771
+ take(n) {
772
+ return EvolutionStream.prototype.take.call({ [Symbol.asyncIterator]: result[Symbol.asyncIterator] }, n);
773
+ },
774
+ collect(max) {
775
+ return EvolutionStream.prototype.collect.call({ [Symbol.asyncIterator]: result[Symbol.asyncIterator] }, max);
776
+ }
777
+ };
778
+ return result;
779
+ }
780
+
781
+ /**
782
+ * Take first n items
783
+ * @param {number} n - Number of items
784
+ */
785
+ take(n) {
786
+ const source = this;
787
+
788
+ const result = {
789
+ async *[Symbol.asyncIterator]() {
790
+ let count = 0;
791
+ for await (const state of source) {
792
+ yield state;
793
+ count++;
794
+ if (count >= n) break;
795
+ }
796
+ },
797
+ async collect(max) {
798
+ const items = [];
799
+ for await (const state of this) {
800
+ items.push(state);
801
+ if (max && items.length >= max) break;
802
+ }
803
+ return items;
804
+ },
805
+ async reduce(reducer, initial) {
806
+ let acc = initial;
807
+ for await (const state of this) {
808
+ acc = reducer(acc, state);
809
+ }
810
+ return acc;
811
+ }
812
+ };
813
+ return result;
814
+ }
815
+
816
+ /**
817
+ * Skip first n items
818
+ * @param {number} n - Number of items to skip
819
+ */
820
+ skip(n) {
821
+ const source = this;
822
+
823
+ return {
824
+ async *[Symbol.asyncIterator]() {
825
+ let count = 0;
826
+ for await (const state of source) {
827
+ if (count >= n) {
828
+ yield state;
829
+ }
830
+ count++;
831
+ }
832
+ }
833
+ };
834
+ }
835
+
836
+ /**
837
+ * Take while condition is true
838
+ * @param {Function} predicate - Condition function
839
+ */
840
+ takeWhile(predicate) {
841
+ const source = this;
842
+
843
+ return {
844
+ async *[Symbol.asyncIterator]() {
845
+ for await (const state of source) {
846
+ if (!predicate(state)) break;
847
+ yield state;
848
+ }
849
+ }
850
+ };
851
+ }
852
+
853
+ /**
854
+ * Reduce stream to single value
855
+ * @param {Function} reducer - Reducer function
856
+ * @param {*} initial - Initial value
857
+ */
858
+ async reduce(reducer, initial) {
859
+ let acc = initial;
860
+
861
+ for await (const state of this) {
862
+ acc = reducer(acc, state);
863
+ }
864
+
865
+ return acc;
866
+ }
867
+
868
+ /**
869
+ * Find first matching item
870
+ * @param {Function} predicate - Match function
871
+ */
872
+ async find(predicate) {
873
+ for await (const state of this) {
874
+ if (predicate(state)) {
875
+ this.stop();
876
+ return state;
877
+ }
878
+ }
879
+ return null;
880
+ }
881
+ }
882
+
883
+ /**
884
+ * Create monitored evolution stream
885
+ * @param {object} engine - AlephEngine instance
886
+ * @param {object} options - Configuration
887
+ */
888
+ function createEvolutionStream(engine, options = {}) {
889
+ return new EvolutionStream(engine, options);
890
+ }
891
+
892
+ /**
893
+ * Create monitor for engine
894
+ * @param {object} engine - AlephEngine instance
895
+ * @param {object} options - Configuration
896
+ */
897
+ function createMonitor(engine, options = {}) {
898
+ return new AlephMonitor(engine, options);
899
+ }
900
+
901
+ module.exports = {
902
+ AlephEventEmitter,
903
+ AlephMonitor,
904
+ EvolutionStream,
905
+ createEvolutionStream,
906
+ createMonitor
907
+ };