synstate 0.1.0 → 0.1.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.
Files changed (154) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +124 -350
  3. package/assets/synstate-icon.png +0 -0
  4. package/dist/core/combine/combine.d.mts +32 -2
  5. package/dist/core/combine/combine.d.mts.map +1 -1
  6. package/dist/core/combine/combine.mjs +32 -2
  7. package/dist/core/combine/combine.mjs.map +1 -1
  8. package/dist/core/combine/merge.d.mts +30 -4
  9. package/dist/core/combine/merge.d.mts.map +1 -1
  10. package/dist/core/combine/merge.mjs +30 -4
  11. package/dist/core/combine/merge.mjs.map +1 -1
  12. package/dist/core/combine/zip.d.mts +28 -3
  13. package/dist/core/combine/zip.d.mts.map +1 -1
  14. package/dist/core/combine/zip.mjs +28 -3
  15. package/dist/core/combine/zip.mjs.map +1 -1
  16. package/dist/core/create/from-array.d.mts +21 -3
  17. package/dist/core/create/from-array.d.mts.map +1 -1
  18. package/dist/core/create/from-array.mjs +21 -3
  19. package/dist/core/create/from-array.mjs.map +1 -1
  20. package/dist/core/create/from-promise.d.mts +29 -7
  21. package/dist/core/create/from-promise.d.mts.map +1 -1
  22. package/dist/core/create/from-promise.mjs +29 -7
  23. package/dist/core/create/from-promise.mjs.map +1 -1
  24. package/dist/core/create/from-subscribable.d.mts +58 -0
  25. package/dist/core/create/from-subscribable.d.mts.map +1 -1
  26. package/dist/core/create/from-subscribable.mjs +58 -0
  27. package/dist/core/create/from-subscribable.mjs.map +1 -1
  28. package/dist/core/create/interval.d.mts +29 -4
  29. package/dist/core/create/interval.d.mts.map +1 -1
  30. package/dist/core/create/interval.mjs +29 -4
  31. package/dist/core/create/interval.mjs.map +1 -1
  32. package/dist/core/create/of.d.mts +22 -3
  33. package/dist/core/create/of.d.mts.map +1 -1
  34. package/dist/core/create/of.mjs +22 -3
  35. package/dist/core/create/of.mjs.map +1 -1
  36. package/dist/core/create/source.d.mts +20 -1
  37. package/dist/core/create/source.d.mts.map +1 -1
  38. package/dist/core/create/source.mjs.map +1 -1
  39. package/dist/core/create/timer.d.mts +23 -4
  40. package/dist/core/create/timer.d.mts.map +1 -1
  41. package/dist/core/create/timer.mjs +23 -4
  42. package/dist/core/create/timer.mjs.map +1 -1
  43. package/dist/core/operators/audit-time.d.mts +59 -0
  44. package/dist/core/operators/audit-time.d.mts.map +1 -1
  45. package/dist/core/operators/audit-time.mjs +59 -0
  46. package/dist/core/operators/audit-time.mjs.map +1 -1
  47. package/dist/core/operators/debounce-time.d.mts +22 -2
  48. package/dist/core/operators/debounce-time.d.mts.map +1 -1
  49. package/dist/core/operators/debounce-time.mjs +22 -2
  50. package/dist/core/operators/debounce-time.mjs.map +1 -1
  51. package/dist/core/operators/filter.d.mts +26 -1
  52. package/dist/core/operators/filter.d.mts.map +1 -1
  53. package/dist/core/operators/filter.mjs.map +1 -1
  54. package/dist/core/operators/map-with-index.d.mts +19 -4
  55. package/dist/core/operators/map-with-index.d.mts.map +1 -1
  56. package/dist/core/operators/map-with-index.mjs +19 -4
  57. package/dist/core/operators/map-with-index.mjs.map +1 -1
  58. package/dist/core/operators/merge-map.d.mts +47 -5
  59. package/dist/core/operators/merge-map.d.mts.map +1 -1
  60. package/dist/core/operators/merge-map.mjs +47 -5
  61. package/dist/core/operators/merge-map.mjs.map +1 -1
  62. package/dist/core/operators/pairwise.d.mts +30 -1
  63. package/dist/core/operators/pairwise.d.mts.map +1 -1
  64. package/dist/core/operators/pairwise.mjs +30 -1
  65. package/dist/core/operators/pairwise.mjs.map +1 -1
  66. package/dist/core/operators/scan.d.mts +23 -1
  67. package/dist/core/operators/scan.d.mts.map +1 -1
  68. package/dist/core/operators/scan.mjs +23 -1
  69. package/dist/core/operators/scan.mjs.map +1 -1
  70. package/dist/core/operators/skip-if-no-change.d.mts +25 -1
  71. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
  72. package/dist/core/operators/skip-if-no-change.mjs +25 -1
  73. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  74. package/dist/core/operators/skip-until.d.mts +50 -0
  75. package/dist/core/operators/skip-until.d.mts.map +1 -1
  76. package/dist/core/operators/skip-until.mjs +50 -0
  77. package/dist/core/operators/skip-until.mjs.map +1 -1
  78. package/dist/core/operators/skip-while.d.mts +48 -0
  79. package/dist/core/operators/skip-while.d.mts.map +1 -1
  80. package/dist/core/operators/skip-while.mjs +48 -0
  81. package/dist/core/operators/skip-while.mjs.map +1 -1
  82. package/dist/core/operators/switch-map.d.mts +39 -5
  83. package/dist/core/operators/switch-map.d.mts.map +1 -1
  84. package/dist/core/operators/switch-map.mjs +39 -5
  85. package/dist/core/operators/switch-map.mjs.map +1 -1
  86. package/dist/core/operators/take-until.d.mts +20 -1
  87. package/dist/core/operators/take-until.d.mts.map +1 -1
  88. package/dist/core/operators/take-until.mjs +20 -1
  89. package/dist/core/operators/take-until.mjs.map +1 -1
  90. package/dist/core/operators/take-while.d.mts +47 -0
  91. package/dist/core/operators/take-while.d.mts.map +1 -1
  92. package/dist/core/operators/take-while.mjs +47 -0
  93. package/dist/core/operators/take-while.mjs.map +1 -1
  94. package/dist/core/operators/throttle-time.d.mts +44 -5
  95. package/dist/core/operators/throttle-time.d.mts.map +1 -1
  96. package/dist/core/operators/throttle-time.mjs +44 -5
  97. package/dist/core/operators/throttle-time.mjs.map +1 -1
  98. package/dist/core/operators/with-buffered-from.d.mts +53 -0
  99. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  100. package/dist/core/operators/with-buffered-from.mjs +53 -0
  101. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  102. package/dist/core/operators/with-current-value-from.d.mts +55 -0
  103. package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
  104. package/dist/core/operators/with-current-value-from.mjs +55 -0
  105. package/dist/core/operators/with-current-value-from.mjs.map +1 -1
  106. package/dist/core/operators/with-initial-value.d.mts +24 -2
  107. package/dist/core/operators/with-initial-value.d.mts.map +1 -1
  108. package/dist/core/operators/with-initial-value.mjs +24 -2
  109. package/dist/core/operators/with-initial-value.mjs.map +1 -1
  110. package/dist/core/types/observable-family.d.mts +7 -7
  111. package/dist/utils/create-event-emitter.d.mts +20 -2
  112. package/dist/utils/create-event-emitter.d.mts.map +1 -1
  113. package/dist/utils/create-event-emitter.mjs +20 -2
  114. package/dist/utils/create-event-emitter.mjs.map +1 -1
  115. package/dist/utils/create-reducer.d.mts +13 -1
  116. package/dist/utils/create-reducer.d.mts.map +1 -1
  117. package/dist/utils/create-reducer.mjs +13 -1
  118. package/dist/utils/create-reducer.mjs.map +1 -1
  119. package/dist/utils/create-state.d.mts +24 -4
  120. package/dist/utils/create-state.d.mts.map +1 -1
  121. package/dist/utils/create-state.mjs +24 -4
  122. package/dist/utils/create-state.mjs.map +1 -1
  123. package/package.json +13 -12
  124. package/src/core/combine/combine.mts +32 -2
  125. package/src/core/combine/merge.mts +30 -4
  126. package/src/core/combine/zip.mts +28 -3
  127. package/src/core/create/from-array.mts +21 -3
  128. package/src/core/create/from-promise.mts +29 -7
  129. package/src/core/create/from-subscribable.mts +58 -0
  130. package/src/core/create/interval.mts +29 -4
  131. package/src/core/create/of.mts +22 -3
  132. package/src/core/create/source.mts +20 -1
  133. package/src/core/create/timer.mts +23 -4
  134. package/src/core/operators/audit-time.mts +59 -0
  135. package/src/core/operators/debounce-time.mts +22 -2
  136. package/src/core/operators/filter.mts +26 -1
  137. package/src/core/operators/map-with-index.mts +19 -4
  138. package/src/core/operators/merge-map.mts +47 -5
  139. package/src/core/operators/pairwise.mts +30 -1
  140. package/src/core/operators/scan.mts +23 -1
  141. package/src/core/operators/skip-if-no-change.mts +25 -1
  142. package/src/core/operators/skip-until.mts +50 -0
  143. package/src/core/operators/skip-while.mts +48 -0
  144. package/src/core/operators/switch-map.mts +39 -5
  145. package/src/core/operators/take-until.mts +20 -1
  146. package/src/core/operators/take-while.mts +47 -0
  147. package/src/core/operators/throttle-time.mts +44 -5
  148. package/src/core/operators/with-buffered-from.mts +53 -0
  149. package/src/core/operators/with-current-value-from.mts +55 -0
  150. package/src/core/operators/with-initial-value.mts +24 -2
  151. package/src/core/types/observable-family.mts +7 -7
  152. package/src/utils/create-event-emitter.mts +20 -2
  153. package/src/utils/create-reducer.mts +13 -1
  154. package/src/utils/create-state.mts +24 -4
@@ -27,17 +27,42 @@ import { withInitialValue } from './with-initial-value.mjs';
27
27
  *
28
28
  * @example
29
29
  * ```ts
30
+ * // Timeline:
31
+ * //
32
+ * // num$ 1 2 3 4 5 6
33
+ * // even$ 2 4 6
34
+ * //
35
+ * // Explanation:
36
+ * // - filter passes through only values that satisfy the predicate
37
+ * // - Only even numbers (2, 4, 6) are emitted
38
+ *
30
39
  * const num$ = source<number>();
31
40
  *
32
41
  * const even$ = num$.pipe(filter((x) => x % 2 === 0));
33
42
  *
43
+ * const mut_history: number[] = [];
44
+ *
34
45
  * even$.subscribe((x) => {
35
- * console.log(x);
46
+ * mut_history.push(x);
36
47
  * });
37
48
  *
38
49
  * num$.next(1); // nothing logged
39
50
  *
40
51
  * num$.next(2); // logs: 2
52
+ *
53
+ * assert.deepStrictEqual(mut_history, [2]);
54
+ *
55
+ * num$.next(3); // nothing logged
56
+ *
57
+ * num$.next(4); // logs: 4
58
+ *
59
+ * assert.deepStrictEqual(mut_history, [2, 4]);
60
+ *
61
+ * num$.next(5);
62
+ *
63
+ * num$.next(6);
64
+ *
65
+ * assert.deepStrictEqual(mut_history, [2, 4, 6]);
41
66
  * ```
42
67
  */
43
68
  export function filter<A, B extends A>(
@@ -26,17 +26,32 @@ import { withInitialValue } from './with-initial-value.mjs';
26
26
  *
27
27
  * @example
28
28
  * ```ts
29
- * const num$ = source<number>();
29
+ * // Timeline:
30
+ * //
31
+ * // num$ "a" "b" "c"
32
+ * // indexed$ "0: a" "1: b" "2: c"
33
+ * //
34
+ * // Explanation:
35
+ * // - mapWithIndex transforms each value along with its index
36
+ * // - Index starts at 0 and increments with each emission
37
+ *
38
+ * const num$ = source<string>();
30
39
  *
31
40
  * const indexed$ = num$.pipe(mapWithIndex((x, i) => `${i}: ${x}`));
32
41
  *
42
+ * const mut_history: string[] = [];
43
+ *
33
44
  * indexed$.subscribe((s) => {
34
- * console.log(s);
45
+ * mut_history.push(s);
35
46
  * });
36
47
  *
37
- * num$.next(10); // logs: 0: 10
48
+ * num$.next('a'); // 0: a
49
+ *
50
+ * num$.next('b'); // 1: b
51
+ *
52
+ * num$.next('c'); // 2: c
38
53
  *
39
- * num$.next(20); // logs: 1: 20
54
+ * assert.deepStrictEqual(mut_history, ['0: a', '1: b', '2: c']);
40
55
  * ```
41
56
  */
42
57
  export const mapWithIndex = <A, B>(
@@ -19,16 +19,58 @@ import {
19
19
  *
20
20
  * @example
21
21
  * ```ts
22
+ * // Timeline:
23
+ * //
24
+ * // ids$ 1 2 3
25
+ * // requests fetch(1) fetch(2) fetch(3)
26
+ * // users$ result1 result2 result3
27
+ * // (parallel) (parallel) (parallel)
28
+ * //
29
+ * // Explanation:
30
+ * // - mergeMap runs all inner observables in parallel
31
+ * // - Results are emitted as they arrive (may be out of order)
32
+ * // - Does NOT cancel previous requests
33
+ * // - All requests run concurrently and all results are emitted
34
+ *
22
35
  * const ids$ = source<number>();
23
36
  *
24
- * const users$ = ids$.pipe(mergeMap((id) => fromPromise(fetchUser(id))));
37
+ * const users$ = ids$.pipe(
38
+ * mergeMap((id) => {
39
+ * const result$ = source<{ id: number }>();
40
+ *
41
+ * setTimeout(() => {
42
+ * result$.next({ id });
43
+ *
44
+ * result$.complete();
45
+ * }, 10);
46
+ *
47
+ * return result$;
48
+ * }),
49
+ * );
25
50
  *
26
- * users$.subscribe((user) => {
27
- * console.log(user);
51
+ * const mut_history: { id: number }[] = [];
52
+ *
53
+ * users$.subscribe((value) => {
54
+ * mut_history.push(value);
28
55
  * });
29
- * // All requests run in parallel, results merged as they arrive
30
56
  *
31
- * const fetchUser = async (id: number): Promise<unknown> => ({ id });
57
+ * ids$.next(1);
58
+ *
59
+ * ids$.next(2);
60
+ *
61
+ * ids$.next(3);
62
+ *
63
+ * await new Promise((resolve) => {
64
+ * setTimeout(resolve, 200);
65
+ * });
66
+ *
67
+ * assert.deepStrictEqual(mut_history.length, 3);
68
+ *
69
+ * assert.isTrue(mut_history.some((u) => u.id === 1));
70
+ *
71
+ * assert.isTrue(mut_history.some((u) => u.id === 2));
72
+ *
73
+ * assert.isTrue(mut_history.some((u) => u.id === 3));
32
74
  * ```
33
75
  *
34
76
  * @note To improve code readability, consider using `createState` instead of `mergeMap`,
@@ -16,19 +16,48 @@ import {
16
16
  *
17
17
  * @example
18
18
  * ```ts
19
+ * // Timeline:
20
+ * //
21
+ * // num$ 1 2 3 4
22
+ * // pairs$ [1,2] [2,3] [3,4]
23
+ * //
24
+ * // Explanation:
25
+ * // - pairwise emits the current and previous values as a tuple
26
+ * // - Nothing is emitted for the first value (no previous value yet)
27
+ * // - Useful for tracking changes between consecutive values
28
+ *
19
29
  * const num$ = source<number>();
20
30
  *
21
31
  * const pairs$ = num$.pipe(pairwise());
22
32
  *
33
+ * const mut_history: (readonly [number, number])[] = [];
34
+ *
23
35
  * pairs$.subscribe(([prev, curr]) => {
24
- * console.log(prev, curr);
36
+ * mut_history.push([prev, curr]);
25
37
  * });
26
38
  *
27
39
  * num$.next(1); // nothing logged
28
40
  *
41
+ * assert.deepStrictEqual(mut_history, []);
42
+ *
29
43
  * num$.next(2); // logs: 1, 2
30
44
  *
45
+ * assert.deepStrictEqual(mut_history, [[1, 2]]);
46
+ *
31
47
  * num$.next(3); // logs: 2, 3
48
+ *
49
+ * assert.deepStrictEqual(mut_history, [
50
+ * [1, 2],
51
+ * [2, 3],
52
+ * ]);
53
+ *
54
+ * num$.next(4); // logs: 3, 4
55
+ *
56
+ * assert.deepStrictEqual(mut_history, [
57
+ * [1, 2],
58
+ * [2, 3],
59
+ * [3, 4],
60
+ * ]);
32
61
  * ```
33
62
  */
34
63
  export const pairwise = <A,>(): DropInitialValueOperator<A, readonly [A, A]> =>
@@ -19,19 +19,41 @@ import {
19
19
  *
20
20
  * @example
21
21
  * ```ts
22
+ * // Timeline (accumulating sum):
23
+ * //
24
+ * // num$ 1 2 3 4 5
25
+ * // sum$ 1 3 6 10 15
26
+ * // | | | | |
27
+ * // 0+1 1+2 3+3 6+4 10+5
28
+ * //
29
+ * // Explanation:
30
+ * // - scan accumulates values over time using a reducer function
31
+ * // - Starting with seed value 0, each emission adds to the accumulator
32
+ * // - Similar to Array.reduce, but for streams
33
+ *
22
34
  * const num$ = source<number>();
23
35
  *
24
36
  * const sum$ = num$.pipe(scan((acc, curr) => acc + curr, 0));
25
37
  *
38
+ * const mut_history: number[] = [];
39
+ *
26
40
  * sum$.subscribe((x) => {
27
- * console.log(x);
41
+ * mut_history.push(x);
28
42
  * });
29
43
  *
44
+ * assert.deepStrictEqual(mut_history, [0]);
45
+ *
30
46
  * num$.next(1); // logs: 1
31
47
  *
48
+ * assert.deepStrictEqual(mut_history, [0, 1]);
49
+ *
32
50
  * num$.next(2); // logs: 3
33
51
  *
52
+ * assert.deepStrictEqual(mut_history, [0, 1, 3]);
53
+ *
34
54
  * num$.next(3); // logs: 6
55
+ *
56
+ * assert.deepStrictEqual(mut_history, [0, 1, 3, 6]);
35
57
  * ```
36
58
  */
37
59
  export const scan =
@@ -17,19 +17,43 @@ import {
17
17
  *
18
18
  * @example
19
19
  * ```ts
20
+ * // Timeline:
21
+ * //
22
+ * // num$ 1 1 2 2 2 3
23
+ * // distinct$ 1 2 3
24
+ * //
25
+ * // Explanation:
26
+ * // - skipIfNoChange filters out consecutive duplicate values
27
+ * // - Uses strict equality (===) for comparison
28
+ * // - Only emits when the value actually changes
29
+ *
20
30
  * const num$ = source<number>();
21
31
  *
22
32
  * const distinct$ = num$.pipe(skipIfNoChange());
23
33
  *
34
+ * const mut_history: number[] = [];
35
+ *
24
36
  * distinct$.subscribe((x) => {
25
- * console.log(x);
37
+ * mut_history.push(x);
26
38
  * });
27
39
  *
28
40
  * num$.next(1); // logs: 1
29
41
  *
42
+ * assert.deepStrictEqual(mut_history, [1]);
43
+ *
30
44
  * num$.next(1); // nothing logged
31
45
  *
46
+ * assert.deepStrictEqual(mut_history, [1]);
47
+ *
32
48
  * num$.next(2); // logs: 2
49
+ *
50
+ * assert.deepStrictEqual(mut_history, [1, 2]);
51
+ *
52
+ * num$.next(2); // nothing logged
53
+ *
54
+ * num$.next(3); // logs: 3
55
+ *
56
+ * assert.deepStrictEqual(mut_history, [1, 2, 3]);
33
57
  * ```
34
58
  */
35
59
  export const skipIfNoChange = <A,>(
@@ -7,6 +7,56 @@ import {
7
7
  type UpdaterSymbol,
8
8
  } from '../types/index.mjs';
9
9
 
10
+ /**
11
+ * Skips all values from the source observable until the notifier observable emits.
12
+ *
13
+ * @template A - The type of values from the source
14
+ * @param notifier - An observable that signals when to start emitting
15
+ * @returns An operator that skips values until the notifier emits
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // Timeline:
20
+ * //
21
+ * // num$ 1 2 3 start 4 5 6
22
+ * // startNotifier X
23
+ * // skipped$ 4 5 6
24
+ * // |------ skipped -------|
25
+ * //
26
+ * // Explanation:
27
+ * // - skipUntil ignores all values until the notifier emits
28
+ * // - After the notifier emits, all subsequent values are passed through
29
+ * // - Opposite of takeUntil (which completes when notifier emits)
30
+ *
31
+ * const num$ = source<number>();
32
+ *
33
+ * const [startNotifier, start_] = createEventEmitter();
34
+ *
35
+ * const skipped$ = num$.pipe(skipUntil(startNotifier));
36
+ *
37
+ * const mut_history: number[] = [];
38
+ *
39
+ * skipped$.subscribe((x) => {
40
+ * mut_history.push(x);
41
+ * });
42
+ *
43
+ * num$.next(1); // nothing logged
44
+ *
45
+ * num$.next(2); // nothing logged
46
+ *
47
+ * assert.deepStrictEqual(mut_history, []);
48
+ *
49
+ * start_();
50
+ *
51
+ * num$.next(4); // logs: 4
52
+ *
53
+ * assert.deepStrictEqual(mut_history, [4]);
54
+ *
55
+ * num$.next(5); // logs: 5
56
+ *
57
+ * assert.deepStrictEqual(mut_history, [4, 5]);
58
+ * ```
59
+ */
10
60
  export const skipUntil =
11
61
  <A,>(notifier: Observable<unknown>): DropInitialValueOperator<A, A> =>
12
62
  (parentObservable) =>
@@ -13,6 +13,54 @@ import {
13
13
  type UpdaterSymbol,
14
14
  } from '../types/index.mjs';
15
15
 
16
+ /**
17
+ * Skips values from the source observable while the predicate returns true.
18
+ * Once the predicate returns false, all subsequent values pass through.
19
+ *
20
+ * @template A - The type of values from the source
21
+ * @param predicate - Function to test each value
22
+ * @returns An operator that skips values while the predicate is true
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * // Timeline:
27
+ * //
28
+ * // num$ 1 2 3 4 5 6 7
29
+ * // skipped$ 5 6 7
30
+ * // |---- skip -----|
31
+ * //
32
+ * // Explanation:
33
+ * // - skipWhile skips values while the predicate returns true
34
+ * // - Once the predicate returns false, all subsequent values pass through
35
+ * // - Unlike filter, the predicate is never checked again after the first false
36
+ *
37
+ * const num$ = source<number>();
38
+ *
39
+ * const skipped$ = num$.pipe(skipWhile((x) => x < 5));
40
+ *
41
+ * const mut_history: number[] = [];
42
+ *
43
+ * skipped$.subscribe((x) => {
44
+ * mut_history.push(x);
45
+ * });
46
+ *
47
+ * num$.next(1); // nothing logged
48
+ *
49
+ * num$.next(2); // nothing logged
50
+ *
51
+ * num$.next(5); // logs: 5
52
+ *
53
+ * assert.deepStrictEqual(mut_history, [5]);
54
+ *
55
+ * num$.next(6); // logs: 6
56
+ *
57
+ * assert.deepStrictEqual(mut_history, [5, 6]);
58
+ *
59
+ * num$.next(7); // logs: 7
60
+ *
61
+ * assert.deepStrictEqual(mut_history, [5, 6, 7]);
62
+ * ```
63
+ */
16
64
  export const skipWhile =
17
65
  <A,>(
18
66
  predicate: (value: A, index: SafeUint | -1) => boolean,
@@ -19,18 +19,52 @@ import {
19
19
  *
20
20
  * @example
21
21
  * ```ts
22
+ * // Timeline:
23
+ * //
24
+ * // searchQuery$ "a" "ab" "abc"
25
+ * // requests fetch1 fetch2 fetch3
26
+ * // results$ cancel cancel result3
27
+ * // fetch1 fetch2
28
+ * //
29
+ * // Explanation:
30
+ * // - switchMap cancels previous inner observables when a new value arrives
31
+ * // - Only the result from the latest search query is emitted
32
+ * // - Previous ongoing requests are cancelled
33
+ * // - Ideal for search-as-you-type scenarios
34
+ *
22
35
  * const searchQuery$ = source<string>();
23
36
  *
24
37
  * const results$ = searchQuery$.pipe(
25
- * switchMap((query) => fromPromise(fetchResults(query))),
38
+ * switchMap((query) => {
39
+ * const result$ = source<string[]>();
40
+ *
41
+ * setTimeout(() => {
42
+ * result$.next([query]);
43
+ *
44
+ * result$.complete();
45
+ * }, 10);
46
+ *
47
+ * return result$;
48
+ * }),
26
49
  * );
27
50
  *
28
- * results$.subscribe((results) => {
29
- * console.log(results);
51
+ * const mut_history: string[][] = [];
52
+ *
53
+ * results$.subscribe((value) => {
54
+ * mut_history.push(value);
55
+ * });
56
+ *
57
+ * searchQuery$.next('a');
58
+ *
59
+ * searchQuery$.next('ab');
60
+ *
61
+ * searchQuery$.next('abc');
62
+ *
63
+ * await new Promise((resolve) => {
64
+ * setTimeout(resolve, 200);
30
65
  * });
31
- * // Only the latest search results are emitted, previous searches are cancelled
32
66
  *
33
- * const fetchResults = async (_query: string): Promise<readonly unknown[]> => [];
67
+ * assert.deepStrictEqual(mut_history, [['abc']]);
34
68
  * ```
35
69
  *
36
70
  * @note To improve code readability, consider using `createState` instead of `switchMap`,
@@ -17,23 +17,42 @@ import {
17
17
  *
18
18
  * @example
19
19
  * ```ts
20
+ * // Timeline:
21
+ * //
22
+ * // num$ 1 2 stop 3 (ignored)
23
+ * // stopNotifier X
24
+ * // limited$ 1 2 |------- (completed)
25
+ * //
26
+ * // Explanation:
27
+ * // - takeUntil completes the observable when the notifier emits
28
+ * // - After stop() is called, no further values are emitted
29
+ * // - Useful for cleanup and cancellation patterns
30
+ *
20
31
  * const num$ = source<number>();
21
32
  *
22
33
  * const [stopNotifier, stop_] = createEventEmitter();
23
34
  *
24
35
  * const limited$ = num$.pipe(takeUntil(stopNotifier));
25
36
  *
37
+ * const mut_history: number[] = [];
38
+ *
26
39
  * limited$.subscribe((x) => {
27
- * console.log(x);
40
+ * mut_history.push(x);
28
41
  * });
29
42
  *
30
43
  * num$.next(1); // logs: 1
31
44
  *
45
+ * assert.deepStrictEqual(mut_history, [1]);
46
+ *
32
47
  * num$.next(2); // logs: 2
33
48
  *
49
+ * assert.deepStrictEqual(mut_history, [1, 2]);
50
+ *
34
51
  * stop_();
35
52
  *
36
53
  * num$.next(3); // nothing logged (completed)
54
+ *
55
+ * assert.deepStrictEqual(mut_history, [1, 2]);
37
56
  * ```
38
57
  */
39
58
  export const takeUntil = <A,>(
@@ -16,6 +16,53 @@ import {
16
16
  } from '../types/index.mjs';
17
17
  import { withInitialValue } from './with-initial-value.mjs';
18
18
 
19
+ /**
20
+ * Emits values from the source observable while the predicate returns true.
21
+ * Completes immediately when the predicate returns false.
22
+ *
23
+ * @template A - The type of values from the source
24
+ * @param predicate - Function to test each value
25
+ * @returns An operator that takes values while the predicate is true
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Timeline:
30
+ * //
31
+ * // num$ 1 2 3 4 5 6 (ignored)
32
+ * // taken$ 1 2 3 4 | (completes)
33
+ * //
34
+ * // Explanation:
35
+ * // - takeWhile emits values while the predicate returns true
36
+ * // - Completes immediately when the predicate returns false
37
+ * // - No further values are emitted after completion
38
+ *
39
+ * const num$ = source<number>();
40
+ *
41
+ * const taken$ = num$.pipe(takeWhile((x) => x < 5));
42
+ *
43
+ * const mut_history: number[] = [];
44
+ *
45
+ * taken$.subscribe((x) => {
46
+ * mut_history.push(x);
47
+ * });
48
+ *
49
+ * num$.next(1); // logs: 1
50
+ *
51
+ * assert.deepStrictEqual(mut_history, [1]);
52
+ *
53
+ * num$.next(2); // logs: 2
54
+ *
55
+ * assert.deepStrictEqual(mut_history, [1, 2]);
56
+ *
57
+ * num$.next(5); // nothing logged (completes)
58
+ *
59
+ * assert.deepStrictEqual(mut_history, [1, 2]);
60
+ *
61
+ * num$.next(6); // nothing logged (already completed)
62
+ *
63
+ * assert.deepStrictEqual(mut_history, [1, 2]);
64
+ * ```
65
+ */
19
66
  export const takeWhile =
20
67
  <A,>(
21
68
  predicate: (value: A, index: SafeUint | -1) => boolean,
@@ -17,14 +17,53 @@ import {
17
17
  *
18
18
  * @example
19
19
  * ```ts
20
- * const scroll$ = source<Event>();
20
+ * // Timeline (1000ms throttle):
21
+ * //
22
+ * // Time(ms) 0 100 200 300 ... 1000 1100 1200 ... 2000 2100
23
+ * // scroll$ e1 e2 e3 e4 e5 e6 e7 e8 e9
24
+ * // throttled$ e1 e5 e8
25
+ * // |-------1000ms------> |------1000ms------> |------1000ms------>
26
+ * //
27
+ * // Explanation:
28
+ * // - throttleTime emits the first value immediately, then ignores subsequent values
29
+ * // for the specified duration (1000ms)
30
+ * // - At 0ms: e1 is emitted immediately
31
+ * // - At 100-300ms: e2, e3, e4 are ignored (within 1000ms window)
32
+ * // - At 1000ms: e5 is emitted (1000ms has passed since e1)
33
+ * // - At 1100-1200ms: e6, e7 are ignored
34
+ * // - At 2000ms: e8 is emitted (1000ms has passed since e5)
21
35
  *
22
- * const throttled$ = scroll$.pipe(throttleTime(1000));
36
+ * const scroll$ = source<number>();
23
37
  *
24
- * throttled$.subscribe((event_) => {
25
- * console.log(event_);
38
+ * const throttled$ = scroll$.pipe(throttleTime(200));
39
+ *
40
+ * const mut_history: number[] = [];
41
+ *
42
+ * throttled$.subscribe((value) => {
43
+ * mut_history.push(value);
26
44
  * });
27
- * // Emits at most once per second
45
+ *
46
+ * scroll$.next(1);
47
+ *
48
+ * assert.deepStrictEqual(mut_history, [1]);
49
+ *
50
+ * await new Promise((resolve) => {
51
+ * setTimeout(resolve, 50);
52
+ * });
53
+ *
54
+ * scroll$.next(2);
55
+ *
56
+ * scroll$.next(3);
57
+ *
58
+ * assert.deepStrictEqual(mut_history, [1]);
59
+ *
60
+ * await new Promise((resolve) => {
61
+ * setTimeout(resolve, 200);
62
+ * });
63
+ *
64
+ * scroll$.next(4);
65
+ *
66
+ * assert.deepStrictEqual(mut_history, [1, 4]);
28
67
  * ```
29
68
  */
30
69
  export const throttleTime = <A,>(
@@ -8,6 +8,59 @@ import {
8
8
  } from '../types/index.mjs';
9
9
  import { maxDepth } from '../utils/index.mjs';
10
10
 
11
+ /**
12
+ * Buffers values from the source observable and emits them along with the parent value
13
+ * when the parent emits. The buffer is cleared after each emission.
14
+ *
15
+ * @template A - The type of values from the parent observable
16
+ * @template B - The type of values from the source observable
17
+ * @param observable - The observable whose values will be buffered
18
+ * @returns An operator that emits tuples of [parentValue, bufferedValues]
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // Timeline:
23
+ * //
24
+ * // data$ d1 d2 d3 d4 d5 d6 d7 d8
25
+ * // trigger$ T1 T2 T3
26
+ * // result$ [T1,[d1,d2,d3]] [T2,[d4,d5,d6]] [T3,[d7,d8]]
27
+ * //
28
+ * // Explanation:
29
+ * // - withBufferedFrom collects values from the source observable
30
+ * // - When the trigger emits, it emits a tuple of [triggerValue, bufferedValues]
31
+ * // - Buffer is cleared after each emission
32
+ * // - Useful for batching data collection triggered by events
33
+ *
34
+ * const data$ = source<string>();
35
+ *
36
+ * const trigger$ = source<number>();
37
+ *
38
+ * const result$ = trigger$.pipe(withBufferedFrom(data$));
39
+ *
40
+ * const mut_history: (readonly [number, readonly string[]])[] = [];
41
+ *
42
+ * result$.subscribe(([triggerValue, bufferedData]) => {
43
+ * mut_history.push([triggerValue, bufferedData]);
44
+ * });
45
+ *
46
+ * data$.next('a');
47
+ *
48
+ * data$.next('b');
49
+ *
50
+ * trigger$.next(1);
51
+ *
52
+ * assert.deepStrictEqual(mut_history, [[1, ['a', 'b']]]);
53
+ *
54
+ * data$.next('c');
55
+ *
56
+ * trigger$.next(2);
57
+ *
58
+ * assert.deepStrictEqual(mut_history, [
59
+ * [1, ['a', 'b']],
60
+ * [2, ['c']],
61
+ * ]);
62
+ * ```
63
+ */
11
64
  export const withBufferedFrom = <A, B>(
12
65
  observable: Observable<B>,
13
66
  ): KeepInitialValueOperator<A, readonly [A, readonly B[]]> =>