evg_observable 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  [![Socket Badge](https://socket.dev/api/badge/npm/package/evg_observable)](https://socket.dev/npm/package/evg_observable)
2
+ <a href="https://www.npmjs.com/package/evg_observable"><img alt="npm version" src="https://img.shields.io/npm/v/evg_observable.svg?style=flat-square"></a>
3
+ <a href="https://bundlephobia.com/result?p=evg_observable"><img alt="Bundle size" src="https://badgen.net/bundlephobia/min/evg_observable"></a>
2
4
  <h1 align=center style="color: saddlebrown">
3
5
  EVG Observable
4
6
  </h1>
@@ -20,6 +22,13 @@ EVG Observable - is a light library for simple use.
20
22
  - [pipe().once()](#pipeonce)
21
23
  - [pipe().unsubscribeBy()](#pipeunsubscribebycondition)
22
24
  - [pipe().and()](#pipeandcondition)
25
+ - [pipe().throttle()](#pipethrottlems)
26
+ - [pipe().debounce()](#pipedebouncems)
27
+ - [pipe().distinctUntilChanged()](#pipedistinctuntilchangedcomparator)
28
+ - [pipe().tap()](#pipetapfn)
29
+ - [pipe().take()](#pipetaken)
30
+ - [pipe().skip()](#pipeskipn)
31
+ - [pipe().scan()](#pipescankfn-seed)
23
32
  - [pipe().toJson()](#pipetojson)
24
33
  - [pipe().fromJson()](#pipefromjsonk)
25
34
  - [pipe().in()](#pipeink-v)
@@ -41,14 +50,26 @@ EVG Observable - is a light library for simple use.
41
50
 
42
51
  | Metric | EVG Observable | RxJS |
43
52
  |--------|----------------|------|
44
- | **Bundle size** | **7.2 kB** | 88 kB |
45
- | **Size advantage** | **12.2x smaller** | - |
46
- | **Operations** | ~40 | 100+ |
47
- | **Performance** | **2-7x faster** | baseline |
53
+ | **Bundle size** | **9.4 kB** | 63.6 kB |
54
+ | **Size advantage** | **6.8x smaller** | - |
55
+ | **Operations** | ~43 | 100+ |
56
+ | **Performance** | **2-6x faster** | baseline |
48
57
 
49
58
  ### Performance Comparison (Bundle vs Bundle)
50
59
 
51
- Benchmarked with minified bundles on Node.js v22.17.1 (v3.0.0 API, averaged over 3 clean runs):
60
+ Benchmarked with minified bundles on Node.js v20.18.3 (v3.1.0 API):
61
+
62
+ | Test | EVG Observable | RxJS | Advantage |
63
+ |------|----------------|------|-----------|
64
+ | Emit 100 values | 2,955K ops/sec | 508K ops/sec | **5.8x faster** |
65
+ | Filter + transform | 615K ops/sec | 237K ops/sec | **2.6x faster** |
66
+ | 10 subscribers | 21,881K ops/sec | 7,556K ops/sec | **2.9x faster** |
67
+ | 100 subscribers | 2,284K ops/sec | 907K ops/sec | **2.5x faster** |
68
+ | 1000 subscribers | 181K ops/sec | 81K ops/sec | **2.2x faster** |
69
+ | Large payload | 1,455K ops/sec | 370K ops/sec | **3.9x faster** |
70
+
71
+ <details>
72
+ <summary>Previous results (v3.0.0 API, Node.js v22.17.1, averaged over 3 clean runs)</summary>
52
73
 
53
74
  | Test | EVG Observable | RxJS | Advantage |
54
75
  |------|----------------|------|-----------|
@@ -60,20 +81,6 @@ Benchmarked with minified bundles on Node.js v22.17.1 (v3.0.0 API, averaged over
60
81
  | Batch emission - of(100) | 906K ops/sec | 176K ops/sec | **5.1x faster** |
61
82
  | 5 chained filters | 19K ops/sec | 9K ops/sec | **2.1x faster** |
62
83
  | Large payload | 879K ops/sec | 184K ops/sec | **4.8x faster** |
63
-
64
- <details>
65
- <summary>Previous results (v2.x API, measured in different conditions)</summary>
66
-
67
- | Test | EVG Observable | RxJS | Advantage |
68
- |------|----------------|------|-----------|
69
- | Emit 100 values | 1,548K ops/sec | 240K ops/sec | **6.4x faster** |
70
- | Filter + transform | 353K ops/sec | 164K ops/sec | **2.1x faster** |
71
- | 10 subscribers | 9,078K ops/sec | 2,900K ops/sec | **3.1x faster** |
72
- | 100 subscribers | 1,245K ops/sec | 336K ops/sec | **3.7x faster** |
73
- | 1000 subscribers | 122K ops/sec | 33K ops/sec | **3.7x faster** |
74
- | Large payload | 865K ops/sec | 199K ops/sec | **4.3x faster** |
75
-
76
- **Note**: v3.0.0 performance is equal or better than v2.x (emit: +7%, 10 subs: +10%). The API redesign with more flexible pipe system maintains excellent performance while providing enhanced functionality.
77
84
  </details>
78
85
 
79
86
  ### EVG Observable Advantages
@@ -90,7 +97,7 @@ Benchmarked with minified bundles on Node.js v22.17.1 (v3.0.0 API, averaged over
90
97
 
91
98
  ### When to use RxJS instead
92
99
 
93
- RxJS is better when you need specialized operators like `debounceTime`, `throttleTime`, `switchMap`, `mergeMap`, `combineLatest`, `withLatestFrom`, or schedulers for async control.
100
+ RxJS is better when you need specialized operators like `switchMap`, `mergeMap`, `combineLatest`, `withLatestFrom`, or schedulers for async control.
94
101
 
95
102
  **For 80% of reactive programming tasks, EVG Observable provides sufficient functionality with significant performance and size benefits.**
96
103
 
@@ -103,7 +110,7 @@ Comparison with lightweight libraries in the same weight category (observable-fn
103
110
  | Metric | EVG Observable | observable-fns |
104
111
  |--------|----------------|----------------|
105
112
  | **Weekly downloads** | Growing | 67K |
106
- | **Bundle size (minified)** | 7.2 kB | 10.8 kB |
113
+ | **Bundle size (minified)** | 9.4 kB | 9.9 kB |
107
114
  | **Implementation** | Original architecture | zen-observable re-implementation |
108
115
  | **Dependencies** | 0 | 0 |
109
116
  | **Architecture** | True hot observables | Cold observables (zen-observable API) |
@@ -334,6 +341,269 @@ observable$.next({message: "some message3", isNeedUnsubscribe: true});
334
341
 
335
342
  Observable will send a value to the listener only if condition returns "true". There is no automatic unsubscription.
336
343
 
344
+ ### pipe().throttle(ms)
345
+
346
+ Throttle emissions using leading-edge strategy. The first value passes immediately; subsequent values within the cooldown interval are silently dropped.
347
+
348
+ ```ts
349
+ import {Observable} from "evg_observable";
350
+
351
+ const observable$ = new Observable<string>('');
352
+ const received: string[] = [];
353
+
354
+ observable$
355
+ .pipe()
356
+ .throttle(300) // Only allow one value per 300ms
357
+ .subscribe((value: string) => received.push(value));
358
+
359
+ observable$.next("first"); // passes immediately
360
+ observable$.next("second"); // dropped (within 300ms)
361
+ observable$.next("third"); // dropped (within 300ms)
362
+ // After 300ms...
363
+ observable$.next("fourth"); // passes (interval expired)
364
+ ```
365
+
366
+ Throttle can be combined with other pipe operators:
367
+
368
+ ```ts
369
+ observable$
370
+ .pipe()
371
+ .and(str => str.length > 1) // filter short strings
372
+ .throttle(500) // throttle remaining values
373
+ .map<number>(str => str.length) // transform to length
374
+ .subscribe(listener);
375
+ ```
376
+
377
+ ### pipe().debounce(ms)
378
+
379
+ Debounce emissions using trailing-edge strategy. Each new value resets the timer. The value is emitted after `ms` milliseconds of silence (no new values).
380
+
381
+ ```ts
382
+ import {Observable} from "evg_observable";
383
+
384
+ const search$ = new Observable<string>('');
385
+ const results: string[] = [];
386
+
387
+ search$
388
+ .pipe()
389
+ .debounce(300) // Wait 300ms of silence before emitting
390
+ .subscribe((value: string) => results.push(value));
391
+
392
+ search$.next("h"); // timer starts
393
+ search$.next("he"); // timer resets
394
+ search$.next("hello"); // timer resets
395
+ // After 300ms of silence → "hello" is emitted
396
+ ```
397
+
398
+ Debounce can be combined with other pipe operators:
399
+
400
+ ```ts
401
+ observable$
402
+ .pipe()
403
+ .and(str => str.length > 2) // filter short strings first
404
+ .debounce(200) // debounce remaining values
405
+ .map<number>(str => str.length) // transform to length
406
+ .subscribe(listener);
407
+ ```
408
+
409
+ ### pipe().distinctUntilChanged(comparator?)
410
+
411
+ Suppresses consecutive duplicate values. A value is emitted only when it differs from the previously emitted value. The first value always passes through.
412
+
413
+ ```ts
414
+ import {Observable} from "evg_observable";
415
+
416
+ const observable$ = new Observable<number>(0);
417
+ const results: number[] = [];
418
+
419
+ observable$
420
+ .pipe()
421
+ .distinctUntilChanged()
422
+ .subscribe((value: number) => results.push(value));
423
+
424
+ observable$.next(1); // emitted (first value)
425
+ observable$.next(1); // suppressed (same as previous)
426
+ observable$.next(2); // emitted (different)
427
+ observable$.next(2); // suppressed (same as previous)
428
+ observable$.next(1); // emitted (different from 2)
429
+ // results: [1, 2, 1]
430
+ ```
431
+
432
+ With custom comparator for objects:
433
+
434
+ ```ts
435
+ const users$ = new Observable<{id: number; name: string}>({id: 0, name: ''});
436
+
437
+ users$
438
+ .pipe()
439
+ .distinctUntilChanged((prev, curr) => prev.id === curr.id)
440
+ .subscribe(user => console.log(user.name));
441
+
442
+ users$.next({id: 1, name: 'Alice'}); // emitted
443
+ users$.next({id: 1, name: 'Alice Updated'}); // suppressed (same id)
444
+ users$.next({id: 2, name: 'Bob'}); // emitted
445
+ ```
446
+
447
+ ### pipe().tap(fn)
448
+
449
+ Executes a side-effect function on the current value without modifying it. The value passes through unchanged to the next operator. Useful for logging, debugging, or triggering external actions mid-pipeline.
450
+
451
+ ```ts
452
+ import {Observable} from "evg_observable";
453
+
454
+ const observable$ = new Observable<number>(0);
455
+
456
+ observable$
457
+ .pipe()
458
+ .tap(value => console.log('before filter:', value))
459
+ .and(value => value > 0)
460
+ .tap(value => console.log('after filter:', value))
461
+ .map<string>(value => `Result: ${value}`)
462
+ .subscribe(result => console.log(result));
463
+
464
+ observable$.next(5);
465
+ // before filter: 5
466
+ // after filter: 5
467
+ // Result: 5
468
+
469
+ observable$.next(-1);
470
+ // before filter: -1
471
+ // (filtered out by .and(), tap after filter is not called)
472
+ ```
473
+
474
+ #### Global debug flag pattern
475
+
476
+ Use a debug flag to enable/disable all taps at once — zero runtime cost when disabled:
477
+
478
+ ```ts
479
+ const DEBUG = true; // set to false in production
480
+
481
+ const observable$ = new Observable<number>(0);
482
+
483
+ observable$
484
+ .pipe()
485
+ .tap(value => DEBUG && console.log('[filter-in]:', value))
486
+ .and(value => value > 0)
487
+ .tap(value => DEBUG && console.log('[filter-out]:', value))
488
+ .map<number>(value => value * 2)
489
+ .tap(value => DEBUG && console.log('[mapped]:', value))
490
+ .subscribe(listener);
491
+
492
+ observable$.next(5);
493
+ // DEBUG=true: [filter-in]: 5 → [filter-out]: 5 → [mapped]: 10
494
+ // DEBUG=false: (nothing logged, V8 optimizes `false && ...` to no-op)
495
+ ```
496
+
497
+ You can also use named taps for clarity in complex pipelines:
498
+
499
+ ```ts
500
+ const tap_log = (name: string) => (value: any) =>
501
+ DEBUG && console.log(`[${name}]:`, value);
502
+
503
+ observable$
504
+ .pipe()
505
+ .tap(tap_log('raw'))
506
+ .and(value => value > 0)
507
+ .tap(tap_log('filtered'))
508
+ .distinctUntilChanged()
509
+ .tap(tap_log('unique'))
510
+ .subscribe(listener);
511
+ ```
512
+
513
+ ### pipe().take(n)
514
+
515
+ Passes the first N values through the pipe, then automatically unsubscribes. Generalization of `once()` — `once()` is equivalent to `take(1)`.
516
+
517
+ ```ts
518
+ import {Observable} from "evg_observable";
519
+
520
+ const observable$ = new Observable<string>('');
521
+ const received: string[] = [];
522
+
523
+ observable$
524
+ .pipe()
525
+ .take(3)
526
+ .subscribe((value: string) => received.push(value));
527
+
528
+ observable$.next("a"); // received: ["a"]
529
+ observable$.next("b"); // received: ["a", "b"]
530
+ observable$.next("c"); // received: ["a", "b", "c"] — auto-unsubscribed
531
+ observable$.next("d"); // not received
532
+ ```
533
+
534
+ Combine with filters and transforms:
535
+
536
+ ```ts
537
+ observable$
538
+ .pipe()
539
+ .and(str => str.length > 2) // filter short strings
540
+ .take(2) // take first 2 that pass
541
+ .subscribe(listener);
542
+ ```
543
+
544
+ ### pipe().skip(n)
545
+
546
+ Ignores the first N values in the pipe, then passes all subsequent values through. Mirror of `take(n)`.
547
+
548
+ ```ts
549
+ import {Observable} from "evg_observable";
550
+
551
+ const observable$ = new Observable<string>('');
552
+ const received: string[] = [];
553
+
554
+ observable$
555
+ .pipe()
556
+ .skip(2)
557
+ .subscribe((value: string) => received.push(value));
558
+
559
+ observable$.next("a"); // skipped
560
+ observable$.next("b"); // skipped
561
+ observable$.next("c"); // received: ["c"]
562
+ observable$.next("d"); // received: ["c", "d"]
563
+ ```
564
+
565
+ Combine skip + take for window slicing:
566
+
567
+ ```ts
568
+ observable$
569
+ .pipe()
570
+ .skip(2) // skip first 2
571
+ .take(3) // take next 3, then unsubscribe
572
+ .subscribe(listener);
573
+ ```
574
+
575
+ ### pipe().scan&lt;K&gt;(fn, seed)
576
+
577
+ Accumulator operator — each value passes through a reducer function, the accumulated result is emitted. Like `Array.reduce()` for streams.
578
+
579
+ ```ts
580
+ import {Observable} from "evg_observable";
581
+
582
+ const observable$ = new Observable<number>(0);
583
+ const sums: number[] = [];
584
+
585
+ observable$
586
+ .pipe()
587
+ .scan<number>((acc, val) => acc + val, 0)
588
+ .subscribe((sum: number) => sums.push(sum));
589
+
590
+ observable$.next(1); // sums: [1]
591
+ observable$.next(2); // sums: [1, 3]
592
+ observable$.next(3); // sums: [1, 3, 6]
593
+ ```
594
+
595
+ Type-changing scan (string → number):
596
+
597
+ ```ts
598
+ const observable$ = new Observable<string>('');
599
+
600
+ observable$
601
+ .pipe()
602
+ .scan<number>((acc, str) => acc + str.length, 0)
603
+ .and(total => total > 5)
604
+ .subscribe(total => console.log('Total chars:', total));
605
+ ```
606
+
337
607
  ### pipe().toJson()
338
608
 
339
609
  To convert the observable's data to JSON format, you can use the serialize method. This method turns the observer's
@@ -805,7 +1075,14 @@ observable$.next(-1); // Listener 1 throws, listener 2 still receives
805
1075
  | `.choice()` | SwitchCase object | transitions the pipe into switch-case mode. In this mode, only the first condition that returns a positive result is triggered, and all others are ignored. This allows you to handle multiple cases more conveniently. |
806
1076
  | `.or(*condition)` | PipeCase object | Adds a condition to the chain of cases. The entire chain operates on the principle of "OR". This is different from other pipe methods which, when chained, operate on the principle of "AND". |
807
1077
  | `.anyOf(*conditions)` | PipeCase object | This method allows you to add a group of conditions for filtering cases data in the pipeline chain. |
1078
+ | `.throttle(ms: number)` | pipe object | Throttles emissions using leading-edge strategy. First value passes immediately, subsequent values within `ms` interval are dropped. |
1079
+ | `.debounce(ms: number)` | pipe object | Debounces emissions using trailing-edge strategy. Each new value resets the timer. Emits after `ms` milliseconds of silence. |
1080
+ | `.distinctUntilChanged(comparator?)` | pipe object | Suppresses consecutive duplicate values. Optional `comparator(prev, curr) => boolean` for custom equality. Defaults to `===`. |
1081
+ | `.tap(fn: ICallback<T>)` | pipe object | Executes a side-effect function without modifying the value. The value passes through unchanged. |
1082
+ | `.take(n: number)` | pipe object | Passes the first N values, then auto-unsubscribes. Generalization of `once()`. |
1083
+ | `.skip(n: number)` | pipe object | Ignores the first N values, then passes all subsequent values through. Mirror of `take()`. |
808
1084
  | `.map<K>(transform: ICallback<T>)` | Observable instance with new data type | This method allows transforming payload data in the pipe chain by applying user callback function. `transform` should be a function that takes the current data and returns transformed data of possibly another type. |
1085
+ | `.scan<K>(fn, seed)` | pipe object with new type K | Accumulator — each value passes through reducer `fn(acc, val) => newAcc`, emits accumulated result. Like `Array.reduce()` for streams. |
809
1086
  | `.toJson()` | pipe object | Converts the observers data into a JSON string. |
810
1087
  | `.fromJson<K>()` | pipe object | Converts a JSON string into an object of type K. |
811
1088
  | `.of<K, V>(transform?: ICallback<K>)`| pipe object | Iterates over array elements. For each element, emits it to subscribers. Optional transform function processes each element before emission. |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "evg_observable",
3
- "version": "3.0.0",
4
- "description": "Alternative fast and light library version - observable.",
3
+ "version": "3.1.0",
4
+ "description": "Lightweight reactive Observable library (zero dependencies) — 2-7x faster than RxJS, 1.5-3x faster than observable-fns. Pipe operators: throttle, debounce, distinctUntilChanged, map, tap. Original hot-observable architecture, not a fork or wrapper.",
5
5
  "main": "src/outLib/index.js",
6
6
  "types": "src/outLib/index.d.ts",
7
7
  "directories": {
@@ -20,7 +20,9 @@
20
20
  "benchmark:patterns-edge": "ts-node benchmarks/benchmark-patterns-edge-cases.ts",
21
21
  "benchmark:patterns-clean": "ts-node benchmarks/benchmark-patterns-clean.ts",
22
22
  "benchmark:patterns-final": "ts-node benchmarks/benchmark-patterns-fixed.ts",
23
- "benchmark:patterns-vs-competitors": "ts-node benchmarks/benchmark-patterns-vs-competitors.ts"
23
+ "benchmark:patterns-vs-competitors": "ts-node benchmarks/benchmark-patterns-vs-competitors.ts",
24
+ "bundle": "esbuild src/browser-entry.ts --bundle --minify --format=iife --outfile=repo/evg_observable.js",
25
+ "bundle:watch": "esbuild src/browser-entry.ts --bundle --minify --format=iife --outfile=repo/evg_observable.js --watch"
24
26
  },
25
27
  "repository": {
26
28
  "type": "git",
@@ -38,6 +40,7 @@
38
40
  "@types/chai": "^4.3.4",
39
41
  "benchmark": "^2.1.4",
40
42
  "chai": "^4.3.7",
43
+ "esbuild": "^0.27.4",
41
44
  "microtime": "^3.1.1",
42
45
  "mocha": "^11.7.5",
43
46
  "nyc": "^17.1.0",
@@ -47,17 +50,23 @@
47
50
  "typescript": "^5.4.5"
48
51
  },
49
52
  "keywords": [
50
- "EVG Observable",
51
- "evg_observable",
52
- "Observable",
53
- "Ordered observable",
54
- "Light observable",
55
- "Collector",
56
- "Type script",
57
- "TypeScript",
58
- "TS",
59
- "Java script",
60
- "JavaScript",
61
- "JS"
53
+ "observable",
54
+ "reactive",
55
+ "event-emitter",
56
+ "rxjs-alternative",
57
+ "lightweight",
58
+ "pub-sub",
59
+ "subscribe",
60
+ "pipe",
61
+ "filter",
62
+ "map",
63
+ "throttle",
64
+ "debounce",
65
+ "distinctUntilChanged",
66
+ "tap",
67
+ "state-management",
68
+ "event-stream",
69
+ "typescript",
70
+ "zero-dependencies"
62
71
  ]
63
72
  }
@@ -1 +1 @@
1
- (()=>{"use strict";function s(s,r){return s.order>r.order?1:s.order<r.order?-1:0}function r(s,r){return s.order>r.order?-1:s.order<r.order?1:0}function e(s,r){const e=s.indexOf(r);return-1!==e&&(s.splice(e,1),!0)}function i(s){return"next"in s?r=>s.next(r):s}class t{pipe;counter;constructor(s){this.pipe=s,this.counter=s.chain.length?s.chain.length:0}or(s){this.counter++;const r=this.counter,e=this.pipe.chain;return e.push(i=>{i.isAvailable=!0,s(i.payload)&&(i.isBreak=!0),r!==e.length||i.isBreak||(i.isAvailable=!1)}),this}anyOf(s){if(!Array.isArray(s))return this;for(let r=0;r<s.length;r++)this.or(s[r]);return this}}class n{chain=[];flow={isBreak:!1,isUnsubscribe:!1,isAvailable:!1,payload:null};push(s){return this.chain.push(s),this}once(){return this.push(s=>{this.listener(s.payload),s.isUnsubscribe=!0})}unsubscribeBy(s){return this.push(r=>{r.isAvailable=!0,s(r.payload)&&(r.isUnsubscribe=!0)})}and(s){return this.push(r=>s(r.payload)&&(r.isAvailable=!0))}allOf(s){if(!Array.isArray(s))return this;for(let r=0;r<s.length;r++)this.and(s[r]);return this}choice(){return new h(this)}map(s){return this.push(r=>{r.payload=s(r.payload),r.isAvailable=!0})}toJson(){return this.push(s=>{s.payload=JSON.stringify(s.payload),s.isAvailable=!0})}fromJson(){return this.push(s=>{s.payload=JSON.parse(s.payload),s.isAvailable=!0})}group(){return this}processChain(s){const r=this.chain,e=this.flow,i=r.length;for(let s=0;s<i;s++){if(e.isUnsubscribe=!1,e.isAvailable=!1,r[s](e),e.isUnsubscribe)return this.unsubscribe();if(!e.isAvailable)return;if(e.isBreak)break}return s(e.payload)}}class h extends t{subscribe(s,r){return this.pipe.subscribe(s,r)}group(){return this.pipe}}class l extends n{observer;listener;errorHandler=(s,r)=>{console.log(`(Unit of SubscribeObject).send(${s}) ERROR:`,r)};_order=0;paused=!1;piped=!1;listeners;errorHandlers;constructor(s,r){super(),this.observer=s,this.piped=!!r}subscribe(s,r){return this.listener=function(s){if(Array.isArray(s)){const r=[];for(let e=0;e<s.length;e++)r.push(i(s[e]));return s=>{for(let e=0;e<r.length;e++)r[e](s)}}return i(s)}(s),r&&(this.errorHandler=r),this}add(s,r){if(this.listeners||(this.listeners=[],this.errorHandlers=[]),Array.isArray(s))for(let e=0;e<s.length;e++){this.listeners.push(s[e]);const i=r&&Array.isArray(r)?r[e]??this.errorHandler:r||this.errorHandler;this.errorHandlers.push(i)}else{this.listeners.push(s);const e=r&&!Array.isArray(r)?r:this.errorHandler;this.errorHandlers.push(e)}return this}unsubscribe(){this.observer&&(this.observer.unSubscribe(this),this.observer=null,this.listener=null,this.chain.length=0)}send(s){const r=this.listener,e=this.listeners&&this.listeners.length>0;if(r||e){if(this.observer&&!this.paused)if(this.piped)try{if(this.flow.payload=s,this.flow.isBreak=!1,r)this.processChain(r);else{const s=this.chain,r=this.flow,e=s.length;r.isAvailable=0===e;for(let i=0;i<e;i++){if(r.isUnsubscribe=!1,r.isAvailable=!1,s[i](r),r.isUnsubscribe)return void this.unsubscribe();if(!r.isAvailable)return;if(r.isBreak)break}}if(e){const s=this.flow.payload;for(let r=0;r<this.listeners.length;r++)try{this.listeners[r](s)}catch(e){this.errorHandlers[r](s,e)}}}catch(r){this.errorHandler(s,r)}else{if(r)try{r(s)}catch(r){this.errorHandler(s,r)}if(e)for(let r=0;r<this.listeners.length;r++)try{this.listeners[r](s)}catch(e){this.errorHandlers[r](s,e)}}}else this.unsubscribe()}resume(){this.paused=!1}pause(){this.paused=!0}get order(){return this._order}set order(s){this._order=s}}class a{chain=[];flow={isBreak:!1,isAvailable:!1,payload:null};response={isOK:!1,payload:void 0};errHandler;get isEmpty(){return!this.chain.length}push(s){return this.chain.push(s),this}and(s){return this.push(r=>s(r.payload)&&(r.isAvailable=!0))}allOf(s){if(!Array.isArray(s))return this;for(let r=0;r<s.length;r++)this.and(s[r]);return this}choice(){return new o(this)}processChain(s){const r=this.chain,e=this.flow,i=this.response;i.isOK=!1,i.payload=void 0,e.payload=s,e.isBreak=!1;try{const s=r.length;for(let t=0;t<s;t++){if(e.isAvailable=!1,r[t](e),!e.isAvailable)return i;if(e.isBreak)break}}catch(s){return this.errHandler?this.errHandler(s,"Filter.processChain ERROR:"):console.log("Filter.processChain ERROR:",s),i}return i.isOK=!0,i.payload=e.payload,i}addErrorHandler(s){this.errHandler=s}}class o extends t{}class u{subs=[];enabled=!0;killed=!1;process=!1;trash=[];filters=new a;_value;constructor(s){this._value=s}addFilter(s){return s&&this.filters.addErrorHandler(s),this.filters}disable(){this.enabled=!1}enable(){this.enabled=!0}get isEnable(){return this.enabled}next(s){if(this.killed)return;if(!this.enabled)return;if(!this.subs.length)return;if(!this.filters.isEmpty&&!this.filters.processChain(s).isOK)return;this.process=!0,this._value=s;const r=this.subs,e=r.length;for(let i=0;i<e;i++)r[i].send(s);this.process=!1,this.trash.length&&this.clearTrash()}of(s){if(!this.killed&&this.enabled)for(let r=0;r<s.length;r++)this.next(s[r])}in(s){if(!this.killed&&this.enabled)for(const r in s)Object.hasOwn(s,r)&&this.next([r,s[r]])}clearTrash(){const s=this.trash.length;for(let r=0;r<s;r++)this.unSubscribe(this.trash[r]);this.trash.length=0}unSubscribe(s){this.killed||(this.process&&s?this.trash.push(s):this.subs&&e(this.subs,s))}destroy(){if(!this.killed){if(this.killed=!0,!this.process)return this._value=null,void(this.subs.length=0);Promise.resolve().then(()=>{this._value=null,this.subs.length=0})}}unsubscribeAll(){if(!this.killed){if(this.process){const s=this.subs;for(let r=0;r<s.length;r++)this.trash.push(s[r]);return}this.subs.length=0}}getValue(){if(!this.killed)return this._value}size(){return this.killed?0:this.subs.length}subscribe(s,r){if(this.killed)return;if(!this.isListener(s))return;const e=new l(this,!1);return this.addObserver(e,s,r),e}addObserver(s,r,e){s.subscribe(r,e),this.subs.push(s)}isListener(s){return!this.killed&&!!s}pipe(){if(this.killed)return;const s=new l(this,!0);return this.subs.push(s),s}get isDestroyed(){return this.killed}}class d extends l{constructor(s,r){super(s,r)}get order(){return this._order}set order(s){!this.observer||this.observer&&this.observer.isDestroyed?this._order=void 0:(this._order=s,this.observer.sortByOrder())}subscribe(s,r){return super.subscribe(s,r),this}once(){return super.once()}}const c=window;c.Observable=u,c.Collector=class{arr=[];killed=!1;collect(...s){this.killed||this.arr.push(...s)}unsubscribe(s){this.killed||(s?.unsubscribe(),e(this.arr,s))}unsubscribeAll(){if(this.killed)return;const s=this.arr;for(let r=0;r<s.length;r++)s[r].unsubscribe();s.length=0}size(){return this.killed?0:this.arr.length}destroy(){this.unsubscribeAll(),this.arr.length=0,this.arr=0,this.killed=!0}get isDestroyed(){return this.killed}},c.OrderedObservable=class extends u{sortDirection=s;ascendingSort(){return this.sortDirection=s,this.sortByOrder()}descendingSort(){return this.sortDirection=r,this.sortByOrder()}sortByOrder(){return!this.killed&&(this.subs.sort(this.sortDirection),!0)}subscribe(s,r){if(!this.isListener(s))return;const e=new d(this,!1);return this.addObserver(e,s,r),e}pipe(){if(this.killed)return;const s=new d(this,!0);return this.subs.push(s),s}unSubscribe(s){this.killed||(this.process&&s?this.trash.push(s):this.subs&&e(this.subs,s))}}})();
1
+ "use strict";(()=>{function y(t,e){return t.order>e.order?1:t.order<e.order?-1:0}function v(t,e){return t.order>e.order?-1:t.order<e.order?1:0}function o(t,e){let r=t.indexOf(e);return r===-1?!1:(t.splice(r,1),!0)}function O(t){if(Array.isArray(t)){let e=t.length,r=new Array(e);for(let i=0;i<e;i++)r[i]=k(t[i]);return i=>{for(let s=0;s<e;s++)r[s](i)}}return k(t)}function k(t){return"next"in t?e=>t.next(e):t}var c=class{pipe;counter;constructor(e){this.pipe=e,this.counter=e.chain.length?e.chain.length:0}or(e){this.counter++;let r=this.counter,i=this.pipe.chain;return i.push(s=>{s.isAvailable=!0,e(s.payload)&&(s.isBreak=!0),r===i.length&&!s.isBreak&&(s.isAvailable=!1)}),this}anyOf(e){if(!Array.isArray(e))return this;for(let r=0;r<e.length;r++)this.or(e[r]);return this}};var I=class{chain=[];flow={isBreak:!1,isUnsubscribe:!1,isAvailable:!1,debounceMs:0,debounceTimer:0,debounceValue:void 0,debounceIndex:0,payload:null};push(e){return this.chain.push(e),this}once(){return this.push(e=>{this.listener(e.payload),e.isUnsubscribe=!0})}take(e){e<0&&(e=0);let r=0;return this.push(i=>{if(r>=e){i.isUnsubscribe=!0;return}r++,this.listener(i.payload),r>=e&&(i.isUnsubscribe=!0)})}skip(e){e<0&&(e=0);let r=0;return this.push(i=>{if(r<e){r++;return}i.isAvailable=!0})}unsubscribeBy(e){return this.push(r=>{r.isAvailable=!0,e(r.payload)&&(r.isUnsubscribe=!0)})}and(e){return this.push(r=>e(r.payload)&&(r.isAvailable=!0))}allOf(e){if(!Array.isArray(e))return this;for(let r=0;r<e.length;r++)this.and(e[r]);return this}choice(){return new T(this)}map(e){return this.push(r=>{r.payload=e(r.payload),r.isAvailable=!0})}scan(e,r){let i=r;return this.push(s=>{i=e(i,s.payload),s.payload=i,s.isAvailable=!0})}tap(e){return this.push(r=>{e(r.payload),r.isAvailable=!0})}throttle(e){let r=0;return this.push(i=>{let s=Date.now();s-r>=e&&(r=s,i.isAvailable=!0)})}debounce(e){return this.push(r=>{r.isAvailable=!0,r.debounceMs=e})}distinctUntilChanged(e){let r=!1,i;return this.push(s=>{let n=s.payload;r&&(e?e(i,n):i===n)||(r=!0,i=n,s.isAvailable=!0)})}toJson(){return this.push(e=>{e.payload=JSON.stringify(e.payload),e.isAvailable=!0})}fromJson(){return this.push(e=>{e.payload=JSON.parse(e.payload),e.isAvailable=!0})}group(){return this}processChain(e){let r=this.chain,i=this.flow,s=r.length;for(let n=0;n<s;n++){if(i.isUnsubscribe=!1,i.isAvailable=!1,i.debounceMs=0,r[n](i),i.isUnsubscribe)return this.unsubscribe();if(i.debounceMs>0){i.debounceValue=i.payload,i.debounceIndex=n+1;let l=()=>{i.debounceTimer=0,i.payload=i.debounceValue,i.isBreak=!1;for(let a=i.debounceIndex;a<s;a++){if(i.isUnsubscribe=!1,i.isAvailable=!1,i.debounceMs=0,r[a](i),i.isUnsubscribe)return this.unsubscribe();if(i.debounceMs>0){i.debounceValue=i.payload,i.debounceIndex=a+1,clearTimeout(i.debounceTimer),i.debounceTimer=setTimeout(l,i.debounceMs);return}if(!i.isAvailable)return;if(i.isBreak)break}e&&e(i.payload)};clearTimeout(i.debounceTimer),i.debounceTimer=setTimeout(l,i.debounceMs);return}if(!i.isAvailable)return;if(i.isBreak)break}i.isAvailable=!0,e&&e(i.payload)}},T=class extends c{subscribe(e,r){return this.pipe.subscribe(e,r)}group(){return this.pipe}};var u=class extends I{observer;listener;errorHandler=(e,r)=>{console.log(`(Unit of SubscribeObject).send(${e}) ERROR:`,r)};_order=0;paused=!1;piped=!1;listeners;errorHandlers;constructor(e,r){super(),this.observer=e,this.piped=!!r}subscribe(e,r){return this.listener=O(e),r&&(this.errorHandler=r),this}add(e,r){if(this.listeners||(this.listeners=[],this.errorHandlers=[]),Array.isArray(e))for(let i=0;i<e.length;i++){this.listeners.push(e[i]);let s=r&&Array.isArray(r)?r[i]??this.errorHandler:r||this.errorHandler;this.errorHandlers.push(s)}else{this.listeners.push(e);let i=r&&!Array.isArray(r)?r:this.errorHandler;this.errorHandlers.push(i)}return this}unsubscribe(){this.observer&&(clearTimeout(this.flow.debounceTimer),this.observer.unSubscribe(this),this.observer=null,this.listener=null,this.chain.length=0)}send(e){let r=this.listener,i=this.listeners&&this.listeners.length>0;if(!r&&!i){this.unsubscribe();return}if(!(!this.observer||this.paused)){if(!this.piped){if(r)try{r(e)}catch(s){this.errorHandler(e,s)}return}try{if(this.flow.payload=e,this.flow.isBreak=!1,i){let s=this.listeners,n=this.errorHandlers;this.processChain(l=>{r&&r(l);for(let a=0;a<s.length;a++)try{s[a](l)}catch(C){n[a](l,C)}})}else this.processChain(r)}catch(s){this.errorHandler(e,s)}}}resume(){this.paused=!1}pause(){this.paused=!0}get order(){return this._order}set order(e){this._order=e}};var f=class{chain=[];flow={isBreak:!1,isAvailable:!1,payload:null};response={isOK:!1,payload:void 0};errHandler;get isEmpty(){return!this.chain.length}push(e){return this.chain.push(e),this}and(e){return this.push(r=>e(r.payload)&&(r.isAvailable=!0))}allOf(e){if(!Array.isArray(e))return this;for(let r=0;r<e.length;r++)this.and(e[r]);return this}choice(){return new m(this)}processChain(e){let r=this.chain,i=this.flow,s=this.response;s.isOK=!1,s.payload=void 0,i.payload=e,i.isBreak=!1;try{let n=r.length;for(let l=0;l<n;l++){if(i.isAvailable=!1,r[l](i),!i.isAvailable)return s;if(i.isBreak)break}}catch(n){return this.errHandler?this.errHandler(n,"Filter.processChain ERROR:"):console.log("Filter.processChain ERROR:",n),s}return s.isOK=!0,s.payload=i.payload,s}addErrorHandler(e){this.errHandler=e}},m=class extends c{};var b=class{subs=[];enabled=!0;killed=!1;process=!1;trash=[];filters=new f;_value;constructor(e){this._value=e}addFilter(e){return e&&this.filters.addErrorHandler(e),this.filters}disable(){this.enabled=!1}enable(){this.enabled=!0}get isEnable(){return this.enabled}next(e){if(this.killed||!this.enabled||!this.subs.length||!this.filters.isEmpty&&!this.filters.processChain(e).isOK)return;this.process=!0,this._value=e;let r=this.subs,i=r.length;for(let s=0;s<i;s++)r[s].send(e);this.process=!1,this.trash.length&&this.clearTrash()}of(e){if(!this.killed&&this.enabled)for(let r=0;r<e.length;r++)this.next(e[r])}in(e){if(!this.killed&&this.enabled)for(let r in e)Object.hasOwn(e,r)&&this.next([r,e[r]])}clearTrash(){let e=this.trash.length;for(let r=0;r<e;r++)this.unSubscribe(this.trash[r]);this.trash.length=0}unSubscribe(e){if(!this.killed){if(this.process&&e){this.trash.push(e);return}this.subs&&o(this.subs,e)}}destroy(){if(!this.killed){if(this.killed=!0,!this.process){this.clearDebounceTimers(),this._value=null,this.subs.length=0;return}Promise.resolve().then(()=>{this.clearDebounceTimers(),this._value=null,this.subs.length=0})}}unsubscribeAll(){if(!this.killed){if(this.process){this.clearDebounceTimers();let e=this.subs;for(let r=0;r<e.length;r++)this.trash.push(e[r]);return}this.clearDebounceTimers(),this.subs.length=0}}clearDebounceTimers(){let e=this.subs;for(let r=0;r<e.length;r++)clearTimeout(e[r].flow.debounceTimer)}getValue(){if(!this.killed)return this._value}size(){return this.killed?0:this.subs.length}subscribe(e,r){if(this.killed||!this.isListener(e))return;let i=new u(this,!1);return this.addObserver(i,e,r),i}addObserver(e,r,i){e.subscribe(r,i),this.subs.push(e)}isListener(e){return this.killed?!1:!!e}pipe(){if(this.killed)return;let e=new u(this,!0);return this.subs.push(e),e}get isDestroyed(){return this.killed}};var d=class extends u{constructor(e,r){super(e,r)}get order(){return this._order}set order(e){if(!this.observer||this.observer&&this.observer.isDestroyed){this._order=void 0;return}this._order=e,this.observer.sortByOrder()}subscribe(e,r){return super.subscribe(e,r),this}once(){return super.once()}take(e){return super.take(e)}skip(e){return super.skip(e)}scan(e,r){return super.scan(e,r)}};var h=class extends b{sortDirection=y;ascendingSort(){return this.sortDirection=y,this.sortByOrder()}descendingSort(){return this.sortDirection=v,this.sortByOrder()}sortByOrder(){return this.killed?!1:(this.subs.sort(this.sortDirection),!0)}subscribe(e,r){if(!this.isListener(e))return;let i=new d(this,!1);return this.addObserver(i,e,r),i}pipe(){if(this.killed)return;let e=new d(this,!0);return this.subs.push(e),e}unSubscribe(e){if(!this.killed){if(this.process&&e){this.trash.push(e);return}this.subs&&o(this.subs,e)}}};var p=class{arr=[];killed=!1;collect(...e){this.killed||this.arr.push(...e)}unsubscribe(e){this.killed||(e?.unsubscribe(),o(this.arr,e))}unsubscribeAll(){if(this.killed)return;let e=this.arr;for(let r=0;r<e.length;r++)e[r].unsubscribe();e.length=0}size(){return this.killed?0:this.arr.length}destroy(){this.unsubscribeAll(),this.arr.length=0,this.arr=0,this.killed=!0}get isDestroyed(){return this.killed}};var S=window;S.Observable=b;S.OrderedObservable=h;S.Collector=p;})();
@@ -36,11 +36,12 @@ function quickDeleteFromArray(arr, component) {
36
36
  }
37
37
  function getListener(listenerGroup) {
38
38
  if (Array.isArray(listenerGroup)) {
39
- const group = [];
40
- for (let i = 0; i < listenerGroup.length; i++)
41
- group.push(wrapListener(listenerGroup[i]));
39
+ const len = listenerGroup.length;
40
+ const group = new Array(len);
41
+ for (let i = 0; i < len; i++)
42
+ group[i] = wrapListener(listenerGroup[i]);
42
43
  return (data) => {
43
- for (let i = 0; i < group.length; i++)
44
+ for (let i = 0; i < len; i++)
44
45
  group[i](data);
45
46
  };
46
47
  }
@@ -21,6 +21,7 @@ export declare class Observable<T> implements IObserver<T>, IStream<T>, IAddFilt
21
21
  unSubscribe(listener: ISubscriptionLike): void;
22
22
  destroy(): void;
23
23
  unsubscribeAll(): void;
24
+ protected clearDebounceTimers(): void;
24
25
  getValue(): T | undefined;
25
26
  size(): number;
26
27
  subscribe(observer: ISubscribeGroup<T>, errorHandler?: IErrorCallback): ISubscriptionLike | undefined;
@@ -86,11 +86,13 @@ class Observable {
86
86
  return;
87
87
  this.killed = true;
88
88
  if (!this.process) {
89
+ this.clearDebounceTimers();
89
90
  this._value = null;
90
91
  this.subs.length = 0;
91
92
  return;
92
93
  }
93
94
  Promise.resolve().then(() => {
95
+ this.clearDebounceTimers();
94
96
  this._value = null;
95
97
  this.subs.length = 0;
96
98
  });
@@ -99,13 +101,20 @@ class Observable {
99
101
  if (this.killed)
100
102
  return;
101
103
  if (this.process) {
104
+ this.clearDebounceTimers();
102
105
  const subs = this.subs;
103
106
  for (let i = 0; i < subs.length; i++)
104
107
  this.trash.push(subs[i]);
105
108
  return;
106
109
  }
110
+ this.clearDebounceTimers();
107
111
  this.subs.length = 0;
108
112
  }
113
+ clearDebounceTimers() {
114
+ const subs = this.subs;
115
+ for (let i = 0; i < subs.length; i++)
116
+ clearTimeout(subs[i].flow.debounceTimer);
117
+ }
109
118
  getValue() {
110
119
  if (this.killed)
111
120
  return undefined;
@@ -7,4 +7,7 @@ export declare class OrderedSubscribeObject<T> extends SubscribeObject<T> implem
7
7
  set order(value: number);
8
8
  subscribe(observer: IListener<T> | ISetObservableValue, errorHandler?: IErrorCallback): IOrderedSubscriptionLike;
9
9
  once(): IOrderedSubscribe<T>;
10
+ take(n: number): IOrderedSubscribe<T>;
11
+ skip(n: number): IOrderedSetup<T>;
12
+ scan<K>(fn: (accumulator: K, value: T) => K, seed: K): IOrderedSetup<K>;
10
13
  }
@@ -25,5 +25,14 @@ class OrderedSubscribeObject extends SubscribeObject_1.SubscribeObject {
25
25
  once() {
26
26
  return super.once();
27
27
  }
28
+ take(n) {
29
+ return super.take(n);
30
+ }
31
+ skip(n) {
32
+ return super.skip(n);
33
+ }
34
+ scan(fn, seed) {
35
+ return super.scan(fn, seed);
36
+ }
28
37
  }
29
38
  exports.OrderedSubscribeObject = OrderedSubscribeObject;
@@ -6,15 +6,22 @@ export declare abstract class Pipe<T> implements ISubscribe<T> {
6
6
  abstract subscribe(listener: IListener<T> | ISetObservableValue, errorHandler?: IErrorCallback): ISubscriptionLike | undefined;
7
7
  private push;
8
8
  once(): ISubscribe<T>;
9
+ take(n: number): ISubscribe<T>;
10
+ skip(n: number): ISetup<T>;
9
11
  unsubscribeBy(condition: ICallback<T>): ISetup<T>;
10
12
  and(condition: ICallback<T>): ISetup<T>;
11
13
  allOf(conditions: ICallback<any>[]): ISetup<T>;
12
14
  choice(): PipeSwitchCase<T>;
13
15
  map<K>(condition: ICallback<T>): ISetup<K>;
16
+ scan<K>(fn: (accumulator: K, value: T) => K, seed: K): ISetup<K>;
17
+ tap(fn: ICallback<T>): ISetup<T>;
18
+ throttle(ms: number): ISetup<T>;
19
+ debounce(ms: number): ISetup<T>;
20
+ distinctUntilChanged(comparator?: (previous: T, current: T) => boolean): ISetup<T>;
14
21
  toJson(): ISetup<string>;
15
22
  fromJson<K>(): ISetup<K>;
16
23
  group(): IGroupSubscription<T>;
17
- processChain(listener: IListener<T>): void;
24
+ processChain(listener?: IListener<T>): void;
18
25
  }
19
26
  export declare class PipeSwitchCase<T> extends SwitchCase<T, Pipe<T>, IPipeCase<T>> implements ISubscribe<T> {
20
27
  subscribe(listener: IListener<T> | ISetObservableValue, errorHandler?: IErrorCallback): ISubscriptionLike | undefined;
@@ -4,7 +4,16 @@ exports.PipeSwitchCase = exports.Pipe = void 0;
4
4
  const AbstractSwitchCase_1 = require("./AbstractSwitchCase");
5
5
  class Pipe {
6
6
  chain = [];
7
- flow = { isBreak: false, isUnsubscribe: false, isAvailable: false, payload: null };
7
+ flow = {
8
+ isBreak: false,
9
+ isUnsubscribe: false,
10
+ isAvailable: false,
11
+ debounceMs: 0,
12
+ debounceTimer: 0,
13
+ debounceValue: undefined,
14
+ debounceIndex: 0,
15
+ payload: null
16
+ };
8
17
  push(callback) {
9
18
  this.chain.push(callback);
10
19
  return this;
@@ -15,6 +24,33 @@ class Pipe {
15
24
  data.isUnsubscribe = true;
16
25
  });
17
26
  }
27
+ take(n) {
28
+ if (n < 0)
29
+ n = 0;
30
+ let count = 0;
31
+ return this.push((data) => {
32
+ if (count >= n) {
33
+ data.isUnsubscribe = true;
34
+ return;
35
+ }
36
+ count++;
37
+ this.listener(data.payload);
38
+ if (count >= n)
39
+ data.isUnsubscribe = true;
40
+ });
41
+ }
42
+ skip(n) {
43
+ if (n < 0)
44
+ n = 0;
45
+ let count = 0;
46
+ return this.push((data) => {
47
+ if (count < n) {
48
+ count++;
49
+ return;
50
+ }
51
+ data.isAvailable = true;
52
+ });
53
+ }
18
54
  unsubscribeBy(condition) {
19
55
  return this.push((data) => {
20
56
  data.isAvailable = true;
@@ -41,6 +77,53 @@ class Pipe {
41
77
  data.isAvailable = true;
42
78
  });
43
79
  }
80
+ scan(fn, seed) {
81
+ let accumulator = seed;
82
+ return this.push((data) => {
83
+ accumulator = fn(accumulator, data.payload);
84
+ data.payload = accumulator;
85
+ data.isAvailable = true;
86
+ });
87
+ }
88
+ tap(fn) {
89
+ return this.push((data) => {
90
+ fn(data.payload);
91
+ data.isAvailable = true;
92
+ });
93
+ }
94
+ throttle(ms) {
95
+ let lastEmitTime = 0;
96
+ return this.push((data) => {
97
+ const now = Date.now();
98
+ if (now - lastEmitTime >= ms) {
99
+ lastEmitTime = now;
100
+ data.isAvailable = true;
101
+ }
102
+ });
103
+ }
104
+ debounce(ms) {
105
+ return this.push((data) => {
106
+ data.isAvailable = true;
107
+ data.debounceMs = ms;
108
+ });
109
+ }
110
+ distinctUntilChanged(comparator) {
111
+ let hasPrevious = false;
112
+ let previousValue;
113
+ return this.push((data) => {
114
+ const current = data.payload;
115
+ if (hasPrevious) {
116
+ const isSame = comparator
117
+ ? comparator(previousValue, current)
118
+ : previousValue === current;
119
+ if (isSame)
120
+ return;
121
+ }
122
+ hasPrevious = true;
123
+ previousValue = current;
124
+ data.isAvailable = true;
125
+ });
126
+ }
44
127
  toJson() {
45
128
  return this.push((data) => {
46
129
  data.payload = JSON.stringify(data.payload);
@@ -63,15 +146,51 @@ class Pipe {
63
146
  for (let i = 0; i < len; i++) {
64
147
  data.isUnsubscribe = false;
65
148
  data.isAvailable = false;
149
+ data.debounceMs = 0;
66
150
  chain[i](data);
67
151
  if (data.isUnsubscribe)
68
152
  return this.unsubscribe();
153
+ if (data.debounceMs > 0) {
154
+ data.debounceValue = data.payload;
155
+ data.debounceIndex = i + 1;
156
+ const continueChain = () => {
157
+ data.debounceTimer = 0;
158
+ data.payload = data.debounceValue;
159
+ data.isBreak = false;
160
+ for (let j = data.debounceIndex; j < len; j++) {
161
+ data.isUnsubscribe = false;
162
+ data.isAvailable = false;
163
+ data.debounceMs = 0;
164
+ chain[j](data);
165
+ if (data.isUnsubscribe)
166
+ return this.unsubscribe();
167
+ if (data.debounceMs > 0) {
168
+ data.debounceValue = data.payload;
169
+ data.debounceIndex = j + 1;
170
+ clearTimeout(data.debounceTimer);
171
+ data.debounceTimer = setTimeout(continueChain, data.debounceMs);
172
+ return;
173
+ }
174
+ if (!data.isAvailable)
175
+ return;
176
+ if (data.isBreak)
177
+ break;
178
+ }
179
+ if (listener)
180
+ listener(data.payload);
181
+ };
182
+ clearTimeout(data.debounceTimer);
183
+ data.debounceTimer = setTimeout(continueChain, data.debounceMs);
184
+ return;
185
+ }
69
186
  if (!data.isAvailable)
70
187
  return;
71
188
  if (data.isBreak)
72
189
  break;
73
190
  }
74
- return listener(data.payload);
191
+ data.isAvailable = true;
192
+ if (listener)
193
+ listener(data.payload);
75
194
  }
76
195
  }
77
196
  exports.Pipe = Pipe;
@@ -50,6 +50,7 @@ class SubscribeObject extends Pipe_1.Pipe {
50
50
  unsubscribe() {
51
51
  if (!this.observer)
52
52
  return;
53
+ clearTimeout(this.flow.debounceTimer);
53
54
  this.observer.unSubscribe(this);
54
55
  this.observer = null;
55
56
  this.listener = null;
@@ -73,53 +74,29 @@ class SubscribeObject extends Pipe_1.Pipe {
73
74
  this.errorHandler(value, err);
74
75
  }
75
76
  }
76
- if (hasGroupListeners) {
77
- for (let i = 0; i < this.listeners.length; i++) {
78
- try {
79
- this.listeners[i](value);
80
- }
81
- catch (err) {
82
- this.errorHandlers[i](value, err);
83
- }
84
- }
85
- }
86
77
  return;
87
78
  }
88
79
  try {
89
80
  this.flow.payload = value;
90
81
  this.flow.isBreak = false;
91
- if (listener) {
92
- this.processChain(listener);
93
- }
94
- else {
95
- const chain = this.chain;
96
- const data = this.flow;
97
- const len = chain.length;
98
- data.isAvailable = len === 0;
99
- for (let i = 0; i < len; i++) {
100
- data.isUnsubscribe = false;
101
- data.isAvailable = false;
102
- chain[i](data);
103
- if (data.isUnsubscribe) {
104
- this.unsubscribe();
105
- return;
106
- }
107
- if (!data.isAvailable)
108
- return;
109
- if (data.isBreak)
110
- break;
111
- }
112
- }
113
82
  if (hasGroupListeners) {
114
- const processedValue = this.flow.payload;
115
- for (let i = 0; i < this.listeners.length; i++) {
116
- try {
117
- this.listeners[i](processedValue);
118
- }
119
- catch (err) {
120
- this.errorHandlers[i](processedValue, err);
83
+ const groupListeners = this.listeners;
84
+ const groupErrorHandlers = this.errorHandlers;
85
+ this.processChain((value) => {
86
+ if (listener)
87
+ listener(value);
88
+ for (let i = 0; i < groupListeners.length; i++) {
89
+ try {
90
+ groupListeners[i](value);
91
+ }
92
+ catch (err) {
93
+ groupErrorHandlers[i](value, err);
94
+ }
121
95
  }
122
- }
96
+ });
97
+ }
98
+ else {
99
+ this.processChain(listener);
123
100
  }
124
101
  }
125
102
  catch (err) {
@@ -31,6 +31,24 @@ export type IOnce<T> = {
31
31
  export type IOrderedOnce<T> = {
32
32
  once(): IOrderedSubscribe<T>;
33
33
  };
34
+ export type ITake<T> = {
35
+ take(n: number): ISubscribe<T>;
36
+ };
37
+ export type IOrderedTake<T> = {
38
+ take(n: number): IOrderedSubscribe<T>;
39
+ };
40
+ export type ISkip<T> = {
41
+ skip(n: number): ISetup<T>;
42
+ };
43
+ export type IOrderedSkip<T> = {
44
+ skip(n: number): IOrderedSetup<T>;
45
+ };
46
+ export type IScan<T> = {
47
+ scan<K>(fn: (accumulator: K, value: T) => K, seed: K): ISetup<K>;
48
+ };
49
+ export type IOrderedScan<T> = {
50
+ scan<K>(fn: (accumulator: K, value: T) => K, seed: K): IOrderedSetup<K>;
51
+ };
34
52
  export type ISetObservableValue = {
35
53
  next(value: any): void;
36
54
  };
@@ -40,8 +58,8 @@ export type ISubscriptionLike = {
40
58
  export type IGroupSubscription<T> = ISubscriptionLike & {
41
59
  add(listener: IListener<T> | IListener<T>[], errorHandler?: IErrorCallback | IErrorCallback[]): IGroupSubscription<T>;
42
60
  };
43
- export type ISetup<T> = IUnsubscribeByPositive<T> & IEmitByPositive<T> & IOnce<T> & ISwitch<T> & ITransform<T> & ISerialisation & IGroup<T> & ISubscribe<T>;
44
- export type IOrderedSetup<T> = IOrderedUnsubscribeByPositive<T> & IOrderedEmitByPositive<T> & IOrderedOnce<T> & IOrderedSwitch<T> & IOrderedTransform<T> & IOrderedSerialisation & IOrderedGroup<T> & IOrderedSubscribe<T>;
61
+ export type ISetup<T> = IUnsubscribeByPositive<T> & IEmitByPositive<T> & IOnce<T> & ITake<T> & ISkip<T> & IScan<T> & ISwitch<T> & ITransform<T> & IThrottle<T> & IDebounce<T> & IDistinctUntilChanged<T> & ITap<T> & ISerialisation & IGroup<T> & ISubscribe<T>;
62
+ export type IOrderedSetup<T> = IOrderedUnsubscribeByPositive<T> & IOrderedEmitByPositive<T> & IOrderedOnce<T> & IOrderedTake<T> & IOrderedSkip<T> & IOrderedScan<T> & IOrderedSwitch<T> & IOrderedTransform<T> & IOrderedThrottle<T> & IOrderedDebounce<T> & IOrderedDistinctUntilChanged<T> & IOrderedTap<T> & IOrderedSerialisation & IOrderedGroup<T> & IOrderedSubscribe<T>;
45
63
  export type ISubscribeObject<T> = ISubscriptionLike & IPause & IOrder & ISend<T> & ISetup<T>;
46
64
  export type ISubscribeCounter = {
47
65
  size(): number;
@@ -100,6 +118,9 @@ export type IEmitByPositive<T> = {
100
118
  export type ITransform<T> = {
101
119
  map<K>(condition: ICallback<T>): ISetup<K>;
102
120
  };
121
+ export type IThrottle<T> = {
122
+ throttle(ms: number): ISetup<T>;
123
+ };
103
124
  export type ISerialisation = {
104
125
  toJson(): ISetup<string>;
105
126
  fromJson<K>(): ISetup<K>;
@@ -111,6 +132,27 @@ export type IOrderedEmitByPositive<T> = {
111
132
  export type IOrderedTransform<T> = {
112
133
  map<K>(condition: ICallback<T>): ISetup<K>;
113
134
  };
135
+ export type IOrderedThrottle<T> = {
136
+ throttle(ms: number): ISetup<T>;
137
+ };
138
+ export type IDebounce<T> = {
139
+ debounce(ms: number): ISetup<T>;
140
+ };
141
+ export type IOrderedDebounce<T> = {
142
+ debounce(ms: number): ISetup<T>;
143
+ };
144
+ export type IDistinctUntilChanged<T> = {
145
+ distinctUntilChanged(comparator?: (previous: T, current: T) => boolean): ISetup<T>;
146
+ };
147
+ export type IOrderedDistinctUntilChanged<T> = {
148
+ distinctUntilChanged(comparator?: (previous: T, current: T) => boolean): ISetup<T>;
149
+ };
150
+ export type ITap<T> = {
151
+ tap(fn: ICallback<T>): ISetup<T>;
152
+ };
153
+ export type IOrderedTap<T> = {
154
+ tap(fn: ICallback<T>): ISetup<T>;
155
+ };
114
156
  export type IOrderedSerialisation = {
115
157
  toJson(): ISetup<string>;
116
158
  fromJson<K>(): ISetup<K>;
@@ -141,6 +183,10 @@ export type IPipePayload = {
141
183
  isBreak: boolean;
142
184
  isUnsubscribe: boolean;
143
185
  isAvailable: boolean;
186
+ debounceMs: number;
187
+ debounceTimer: any;
188
+ debounceValue: any;
189
+ debounceIndex: number;
144
190
  payload: any;
145
191
  };
146
192
  export type IChainCallback = (data: IPipePayload) => void;
@@ -1,149 +0,0 @@
1
- # Benchmark Results: Bundle vs TypeScript vs RxJS
2
-
3
- Date: 2026-01-10
4
-
5
- ## Methodology
6
-
7
- Performance comparison of three variants:
8
-
9
- 1. **Bundle** - Minified JavaScript from `repo/evg_observable.js` (7.2 kB)
10
- 2. **TypeScript** - On-the-fly compilation via ts-node
11
- 3. **RxJS** - From node_modules (88 kB UMD bundle)
12
-
13
- **Size comparison:** EVG Observable is **12.2x smaller** than RxJS (7.2 kB vs 88 kB)
14
-
15
- ## Test Results
16
-
17
- Results averaged over 3 clean benchmark runs (no background processes):
18
-
19
- ### 1. Observable Creation
20
-
21
- | Variant | Ops/sec | Relative Performance |
22
- |---------|---------|---------------------|
23
- | Bundle | 122,701,000 | **baseline** |
24
- | TypeScript | 52,574,000 | 2.3x slower |
25
- | RxJS | 53,639,000 | 2.3x slower |
26
-
27
- **Winner: Bundle**
28
-
29
- ### 2. Emit 100 Values (1 subscriber)
30
-
31
- | Variant | Ops/sec | Relative Performance |
32
- |---------|---------|---------------------|
33
- | Bundle | 1,662,000 | **baseline** |
34
- | TypeScript | 1,079,000 | 1.5x slower |
35
- | RxJS | 239,000 | 7.0x slower |
36
-
37
- **Winner: Bundle**
38
-
39
- ### 3. Filter and Transform (pipe chain)
40
-
41
- | Variant | Ops/sec | Relative Performance |
42
- |---------|---------|---------------------|
43
- | Bundle | 340,000 | **baseline** |
44
- | TypeScript | 324,000 | ~equal (5% difference) |
45
- | RxJS | 149,000 | 2.3x slower |
46
-
47
- **Winner: Bundle** (marginal advantage)
48
-
49
- ### 4. Multiple Subscribers
50
-
51
- #### 10 Subscribers
52
-
53
- | Variant | Ops/sec | Relative Performance |
54
- |---------|---------|---------------------|
55
- | Bundle | 9,946,000 | **baseline** |
56
- | TypeScript | 8,119,000 | 1.2x slower |
57
- | RxJS | 3,500,000 | 2.8x slower |
58
-
59
- #### 100 Subscribers
60
-
61
- | Variant | Ops/sec | Relative Performance |
62
- |---------|---------|---------------------|
63
- | Bundle | 1,236,000 | **baseline** |
64
- | TypeScript | 991,000 | 1.2x slower |
65
- | RxJS | 432,000 | 2.9x slower |
66
-
67
- #### 1000 Subscribers
68
-
69
- | Variant | Ops/sec | Relative Performance |
70
- |---------|---------|---------------------|
71
- | Bundle | 124,000 | **baseline** |
72
- | TypeScript | 98,000 | 1.3x slower |
73
- | RxJS | 41,000 | 3.0x slower |
74
-
75
- **Winner: Bundle** (all cases)
76
-
77
- ### 5. Batch Emission - of(100)
78
-
79
- | Variant | Ops/sec | Relative Performance |
80
- |---------|---------|---------------------|
81
- | Bundle | 906,000 | **baseline** |
82
- | TypeScript | 752,000 | 1.2x slower |
83
- | RxJS | 176,000 | 5.1x slower |
84
-
85
- **Winner: Bundle**
86
-
87
- ### 6. Five Chained Filters
88
-
89
- | Variant | Ops/sec | Relative Performance |
90
- |---------|---------|---------------------|
91
- | Bundle | 18,900 | **baseline** |
92
- | TypeScript | 18,200 | ~equal (4% difference) |
93
- | RxJS | 9,200 | 2.1x slower |
94
-
95
- **Winner: Bundle** (marginal advantage)
96
-
97
- ### 7. Large Payload (complex objects)
98
-
99
- | Variant | Ops/sec | Relative Performance |
100
- |---------|---------|---------------------|
101
- | Bundle | 879,000 | **baseline** |
102
- | TypeScript | 755,000 | 1.2x slower |
103
- | RxJS | 184,000 | 4.8x slower |
104
-
105
- **Winner: Bundle**
106
-
107
- ## Summary
108
-
109
- ### Bundle vs TypeScript
110
-
111
- The minified bundle shows **consistent advantage** over TypeScript compilation:
112
-
113
- - **Object creation**: 2.3x faster - significant advantage
114
- - **Data emission**: 1.5x faster - noticeable advantage
115
- - **Filtering/transformation**: ~equal performance (within 5%)
116
- - **Multiple subscribers**: 1.2-1.3x faster - stable advantage
117
- - **Batch emission**: 1.2x faster
118
- - **Large objects**: 1.2x faster
119
-
120
- **Reasons for bundle's advantage:**
121
- 1. Minification and dead code elimination
122
- 2. Better V8 JIT optimization for compiled code
123
- 3. Reduced module loading overhead
124
- 4. Inlining of small functions
125
-
126
- ### EVG Observable vs RxJS
127
-
128
- Both variants (bundle and TypeScript) **significantly outperform RxJS**:
129
-
130
- - **Creation**: ~2.3x faster
131
- - **Simple emission**: 5-7x faster
132
- - **Filtering**: 2.3x faster
133
- - **Multiple subscribers**: 2.8-3.0x faster
134
- - **Batch emission**: 4.5-5.1x faster
135
- - **Complex filtering**: ~2.1x faster
136
- - **Large objects**: 4.7-4.8x faster
137
-
138
- ### Recommendations
139
-
140
- 1. **For production**: Use minified bundle for maximum performance
141
- 2. **For development**: TypeScript compilation provides nearly equal performance with type safety convenience
142
- 3. **Migration from RxJS**: Expect 2-7x performance improvement depending on usage patterns
143
-
144
- ## Technical Details
145
-
146
- - **Node.js**: v22.17.1
147
- - **Benchmark.js**: Standard settings
148
- - **Minimum runs**: 71-90 per test
149
- - **Margin of Error**: ±0.71% - ±5.21%
@@ -1,197 +0,0 @@
1
- # Benchmark Results: EVG Observable vs observable-fns
2
-
3
- Date: 2026-01-10
4
-
5
- ## Methodology
6
-
7
- Performance comparison of two lightweight Observable libraries:
8
-
9
- 1. **EVG Observable** - True hot observables with original architecture (7.2 kB minified)
10
- 2. **observable-fns** - Based on zen-observable, provides Subject for hot observables (10.8 kB minified)
11
-
12
- **Size comparison:** EVG Observable is **1.5x smaller** than observable-fns (7.2 kB vs 10.8 kB)
13
-
14
- Benchmarked on Node.js v22.17.1, results averaged over 3 clean runs (no background processes).
15
-
16
- ## Test Results
17
-
18
- ### 1. Observable Creation
19
-
20
- | Library | Ops/sec | Relative Performance |
21
- |---------|---------|---------------------|
22
- | EVG Observable | 54,138,000 | **baseline** |
23
- | observable-fns | 17,226,000 | 3.1x slower |
24
-
25
- **Winner: EVG Observable** (3.1x faster)
26
-
27
- ### 2. Single Emission Performance Matrix
28
-
29
- #### 1 Emission × Multiple Subscribers
30
-
31
- | Subscribers | EVG Observable | observable-fns | Advantage |
32
- |-------------|----------------|----------------|-----------|
33
- | 1 | 53,802,000 ops/sec | 36,757,000 | **1.5x faster** |
34
- | 10 | 14,318,000 ops/sec | 6,283,000 | **2.3x faster** |
35
- | 100 | 1,700,000 ops/sec | 733,000 | **2.3x faster** |
36
- | 1,000 | 176,000 ops/sec | 73,000 | **2.4x faster** |
37
- | 10,000 | 16,100 ops/sec | 6,900 | **2.3x faster** |
38
-
39
- **Winner: EVG Observable** (consistent 1.5-2.4x advantage)
40
-
41
- #### 10 Emissions × Multiple Subscribers
42
-
43
- | Subscribers | EVG Observable | observable-fns | Advantage |
44
- |-------------|----------------|----------------|-----------|
45
- | 1 | 9,881,000 ops/sec | 5,568,000 | **1.8x faster** |
46
- | 10 | 1,651,000 ops/sec | 677,000 | **2.4x faster** |
47
- | 100 | 176,000 ops/sec | 75,000 | **2.3x faster** |
48
- | 1,000 | 17,800 ops/sec | 7,400 | **2.4x faster** |
49
- | 10,000 | 1,600 ops/sec | 700 | **2.3x faster** |
50
-
51
- **Winner: EVG Observable** (consistent 1.8-2.4x advantage)
52
-
53
- #### 100 Emissions × Multiple Subscribers
54
-
55
- | Subscribers | EVG Observable | observable-fns | Advantage |
56
- |-------------|----------------|----------------|-----------|
57
- | 1 | 1,022,000 ops/sec | 552,000 | **1.9x faster** |
58
- | 10 | 169,000 ops/sec | 69,000 | **2.4x faster** |
59
- | 100 | 17,500 ops/sec | 7,300 | **2.4x faster** |
60
- | 1,000 | 1,770 ops/sec | 730 | **2.4x faster** |
61
- | 10,000 | 166 ops/sec | 65 | **2.6x faster** |
62
-
63
- **Winner: EVG Observable** (consistent 1.9-2.6x advantage)
64
-
65
- #### 1000 Emissions × Multiple Subscribers
66
-
67
- | Subscribers | EVG Observable | observable-fns | Advantage |
68
- |-------------|----------------|----------------|-----------|
69
- | 1 | 98,400 ops/sec | 54,400 | **1.8x faster** |
70
- | 10 | 16,900 ops/sec | 7,000 | **2.4x faster** |
71
- | 100 | 1,750 ops/sec | 730 | **2.4x faster** |
72
- | 1,000 | 179 ops/sec | 75 | **2.4x faster** |
73
- | 10,000 | 15.8 ops/sec | 6.1 | **2.6x faster** |
74
-
75
- **Winner: EVG Observable** (consistent 1.8-2.6x advantage)
76
-
77
- ### 3. Filter & Transform (pipe chain with 100 emissions)
78
-
79
- | Subscribers | EVG Observable | observable-fns | Advantage |
80
- |-------------|----------------|----------------|-----------|
81
- | 1 | 292,000 ops/sec | 127,000 | **2.3x faster** |
82
- | 10 | 275,000 ops/sec | 18,700 | **14.7x faster** |
83
- | 100 | 280,000 ops/sec | 1,880 | **149x faster** |
84
- | 1,000 | 273,000 ops/sec | 143 | **1,909x faster** |
85
- | 10,000 | 277,000 ops/sec | 13.7 | **20,219x faster** |
86
-
87
- **Winner: EVG Observable** (dramatic advantage at higher subscriber counts)
88
-
89
- **Analysis:** EVG Observable's pipe architecture is extremely efficient with multiple subscribers. observable-fns creates separate pipe chains per subscriber, causing exponential slowdown.
90
-
91
- ### 4. Five Chained Filters (100 emissions)
92
-
93
- | Subscribers | EVG Observable | observable-fns | Advantage |
94
- |-------------|----------------|----------------|-----------|
95
- | 1 | 119,000 ops/sec | 71,300 | **1.7x faster** |
96
- | 10 | 116,400 ops/sec | 7,280 | **16.0x faster** |
97
- | 100 | 113,500 ops/sec | 709 | **160x faster** |
98
- | 1,000 | 112,400 ops/sec | 57.6 | **1,951x faster** |
99
- | 10,000 | 114,100 ops/sec | 2.1 | **54,333x faster** |
100
-
101
- **Winner: EVG Observable** (exponential advantage at higher subscriber counts)
102
-
103
- **Analysis:** Complex pipe chains show EVG Observable's architectural superiority. The advantage grows exponentially with subscriber count.
104
-
105
- ### 5. Large Payload (complex objects, 100 emissions)
106
-
107
- | Library | Ops/sec | Relative Performance |
108
- |---------|---------|---------------------|
109
- | EVG Observable | 749,000 | **baseline** |
110
- | observable-fns | 549,000 | 1.4x slower |
111
-
112
- **Winner: EVG Observable** (1.4x faster)
113
-
114
- ### 6. Subscribe/Unsubscribe Churn (1000 cycles)
115
-
116
- | Library | Ops/sec | Relative Performance |
117
- |---------|---------|---------------------|
118
- | EVG Observable | 4,406 | **~equal** |
119
- | observable-fns | 4,537 | ~equal |
120
-
121
- **Winner: Tie** (within margin of error)
122
-
123
- **Analysis:** Both libraries have similar overhead for subscription management.
124
-
125
- ## Summary
126
-
127
- ### Key Findings
128
-
129
- 1. **Simple Emissions (1 subscriber)**: EVG Observable is **1.5-1.9x faster**
130
- - Consistent advantage across all emission counts
131
- - Both scale linearly
132
-
133
- 2. **Multiple Subscribers (10-10,000)**: EVG Observable is **2.3-2.6x faster**
134
- - Advantage increases slightly with subscriber count
135
- - EVG's true hot observable architecture shines
136
-
137
- 3. **Pipe Operations**: EVG Observable has **dramatic advantage**
138
- - 1 subscriber: 1.7-2.3x faster
139
- - 10 subscribers: 14.7-16.0x faster
140
- - 100 subscribers: 149-160x faster
141
- - 1,000 subscribers: 1,900-1,950x faster
142
- - 10,000 subscribers: 20,000-54,000x faster
143
-
144
- 4. **Observable Creation**: EVG Observable is **3.1x faster**
145
- - Lower instantiation overhead
146
-
147
- 5. **Large Payloads**: EVG Observable is **1.4x faster**
148
- - Efficient data passing
149
-
150
- ### Architectural Insights
151
-
152
- **EVG Observable's advantages:**
153
- - True hot observable architecture (single pipe chain for all subscribers)
154
- - Efficient subscription management for multi-subscriber scenarios
155
- - Optimized pipe operations that don't duplicate work
156
- - Lower memory overhead per subscriber
157
-
158
- **observable-fns characteristics:**
159
- - Based on zen-observable (cold observables)
160
- - Each subscriber creates separate pipe chain
161
- - Good for single-subscriber patterns
162
- - Struggles with multi-subscriber pipe operations
163
-
164
- ### Recommendations
165
-
166
- **Choose EVG Observable when:**
167
- - You have multiple subscribers (most real-world scenarios)
168
- - Using pipe operators (filter, map, etc.)
169
- - Performance is critical
170
- - Real-time broadcasting (WebSocket, SSE, event emitters)
171
- - Need hot observable semantics
172
-
173
- **Choose observable-fns when:**
174
- - You need zen-observable API compatibility
175
- - Single subscriber patterns
176
- - Cold observable semantics required
177
-
178
- ### Performance Summary Table
179
-
180
- | Operation Type | Typical Advantage | Best Case | Worst Case |
181
- |----------------|-------------------|-----------|------------|
182
- | Simple emission | 1.5-2.4x | 2.6x | 1.5x |
183
- | Pipe (1 sub) | 1.7-2.3x | 2.3x | 1.7x |
184
- | Pipe (10+ subs) | 15-20,000x | 54,333x | 14.7x |
185
- | Creation | 3.1x | 3.1x | 3.1x |
186
- | Large payloads | 1.4x | 1.4x | 1.4x |
187
- | Sub/Unsub | ~equal | - | - |
188
-
189
- ## Technical Details
190
-
191
- - **Node.js**: v22.17.1
192
- - **Benchmark.js**: Standard settings
193
- - **Runs**: 3 clean benchmark runs, averaged
194
- - **Per-test samples**: 70-94 runs per scenario
195
- - **Margin of Error**: ±1.2% - ±2.9% (typical)
196
- - **Total scenarios**: 33 benchmark scenarios
197
- - **Verification**: All pipe operations include result verification to ensure correct behavior
@@ -1,70 +0,0 @@
1
- # Breaking Changes - Performance Optimizations Branch
2
-
3
- ## Version 2.15.0
4
-
5
- ### Internal Field Renaming
6
-
7
- **Affected field:** `value` renamed to `_value`
8
-
9
- The internal field storing the current observable value has been renamed from `value` to `_value` to follow TypeScript conventions for protected/private members.
10
-
11
- **Before:**
12
- ```typescript
13
- export class Observable<T> {
14
- constructor(private value: T) {}
15
- }
16
- ```
17
-
18
- **After:**
19
- ```typescript
20
- export class Observable<T> {
21
- protected _value: T;
22
- constructor(value: T) {
23
- this._value = value;
24
- }
25
- }
26
- ```
27
-
28
- **Migration:**
29
- - If you were extending `Observable` and accessing `this.value`, change to `this._value`
30
- - Use the public `getValue()` method for reading the current value (recommended)
31
-
32
- ### Visibility Changes
33
-
34
- The following fields changed from `private` to `protected` to enable better inheritance:
35
-
36
- | Field | Before | After |
37
- |-------|--------|-------|
38
- | `enabled` | private | protected |
39
- | `filters` | private | protected |
40
- | `_value` (formerly `value`) | private | protected |
41
-
42
- **Impact:** This is not a breaking change for most users, but allows subclasses to access these fields.
43
-
44
- ### Recommended Migration Path
45
-
46
- Instead of accessing internal fields directly, use the public API:
47
-
48
- ```typescript
49
- // Instead of:
50
- // @ts-ignore
51
- const val = observable._value;
52
-
53
- // Use:
54
- const val = observable.getValue();
55
-
56
- // Instead of:
57
- // @ts-ignore
58
- const count = observable.subs.length;
59
-
60
- // Use:
61
- const count = observable.size();
62
- ```
63
-
64
- ### New Behavior
65
-
66
- 1. **`destroy()` now uses `Promise.resolve()`** instead of `setInterval()` for async cleanup when called during emission
67
- 2. **`unsubscribeAll()` is now safe to call during `next()`** - uses deferred cleanup mechanism
68
- 3. **Early exit optimization** - `next()` returns immediately if there are no subscribers
69
-
70
- These behavioral changes improve performance and memory management but should not require code changes.