synstate 6.2.1 → 7.0.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 +1 -1
- package/dist/core/class/child-observable-class.d.mts +16 -2
- package/dist/core/class/child-observable-class.d.mts.map +1 -1
- package/dist/core/class/child-observable-class.mjs +37 -4
- package/dist/core/class/child-observable-class.mjs.map +1 -1
- package/dist/core/class/create-child-observable.d.mts +1 -1
- package/dist/core/class/create-child-observable.d.mts.map +1 -1
- package/dist/core/class/create-child-observable.mjs +6 -8
- package/dist/core/class/create-child-observable.mjs.map +1 -1
- package/dist/core/class/create-root-observable.d.mts +1 -1
- package/dist/core/class/create-root-observable.d.mts.map +1 -1
- package/dist/core/class/create-root-observable.mjs +1 -5
- package/dist/core/class/create-root-observable.mjs.map +1 -1
- package/dist/core/class/propagation-state.mjs +1 -1
- package/dist/core/class/propagation-state.mjs.map +1 -1
- package/dist/core/combine/derive.d.mts.map +1 -1
- package/dist/core/combine/derive.mjs +2 -1
- package/dist/core/combine/derive.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts.map +1 -1
- package/dist/core/combine/zip.mjs +5 -2
- package/dist/core/combine/zip.mjs.map +1 -1
- package/dist/core/create/counter.mjs +2 -2
- package/dist/core/create/counter.mjs.map +1 -1
- package/dist/core/create/from-abortable-promise.d.mts.map +1 -1
- package/dist/core/create/from-abortable-promise.mjs +10 -3
- package/dist/core/create/from-abortable-promise.mjs.map +1 -1
- package/dist/core/create/from-promise.d.mts.map +1 -1
- package/dist/core/create/from-promise.mjs +10 -3
- package/dist/core/create/from-promise.mjs.map +1 -1
- package/dist/core/create/source.d.mts.map +1 -1
- package/dist/core/create/source.mjs +2 -1
- package/dist/core/create/source.mjs.map +1 -1
- package/dist/core/operators/filter.d.mts +1 -1
- package/dist/core/operators/filter.d.mts.map +1 -1
- package/dist/core/operators/filter.mjs +2 -3
- package/dist/core/operators/filter.mjs.map +1 -1
- package/dist/core/operators/merge-map.d.mts +6 -2
- package/dist/core/operators/merge-map.d.mts.map +1 -1
- package/dist/core/operators/merge-map.mjs +47 -16
- package/dist/core/operators/merge-map.mjs.map +1 -1
- package/dist/core/operators/skip-while.d.mts +1 -1
- package/dist/core/operators/skip-while.d.mts.map +1 -1
- package/dist/core/operators/skip-while.mjs +9 -6
- package/dist/core/operators/skip-while.mjs.map +1 -1
- package/dist/core/operators/switch-map.d.mts +4 -0
- package/dist/core/operators/switch-map.d.mts.map +1 -1
- package/dist/core/operators/switch-map.mjs +16 -6
- package/dist/core/operators/switch-map.mjs.map +1 -1
- package/dist/core/operators/take-while.d.mts +2 -2
- package/dist/core/operators/take-while.d.mts.map +1 -1
- package/dist/core/operators/take-while.mjs +10 -8
- package/dist/core/operators/take-while.mjs.map +1 -1
- package/dist/core/operators/throttle.mjs +3 -2
- package/dist/core/operators/throttle.mjs.map +1 -1
- package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
- package/dist/core/operators/with-buffered-from.mjs +13 -3
- package/dist/core/operators/with-buffered-from.mjs.map +1 -1
- package/dist/core/predefined/operators/attach-index.d.mts +1 -1
- package/dist/core/predefined/operators/attach-index.d.mts.map +1 -1
- package/dist/core/predefined/operators/attach-index.mjs +2 -3
- package/dist/core/predefined/operators/attach-index.mjs.map +1 -1
- package/dist/core/predefined/operators/skip.d.mts.map +1 -1
- package/dist/core/predefined/operators/skip.mjs +2 -2
- package/dist/core/predefined/operators/skip.mjs.map +1 -1
- package/dist/core/utils/index.d.mts +1 -0
- package/dist/core/utils/index.d.mts.map +1 -1
- package/dist/core/utils/index.mjs +1 -0
- package/dist/core/utils/index.mjs.map +1 -1
- package/dist/core/utils/rethrow-out-of-promise.d.mts +10 -0
- package/dist/core/utils/rethrow-out-of-promise.d.mts.map +1 -0
- package/dist/core/utils/rethrow-out-of-promise.mjs +17 -0
- package/dist/core/utils/rethrow-out-of-promise.mjs.map +1 -0
- package/package.json +12 -11
- package/src/core/class/child-observable-class.mts +45 -3
- package/src/core/class/create-child-observable.mts +8 -5
- package/src/core/class/create-root-observable.mts +2 -1
- package/src/core/class/propagation-state.mts +1 -1
- package/src/core/class/write-during-propagation.test.mts +75 -4
- package/src/core/combine/derive.mts +2 -1
- package/src/core/combine/zip.mts +5 -2
- package/src/core/create/counter.mts +2 -2
- package/src/core/create/from-abortable-promise.mts +33 -23
- package/src/core/create/from-promise.mts +28 -18
- package/src/core/create/from-promise.test.mts +97 -0
- package/src/core/create/source.mts +2 -1
- package/src/core/operators/filter.mts +2 -3
- package/src/core/operators/flat-map-initial-value.test.mts +269 -0
- package/src/core/operators/flat-map-sync-inner.test.mts +4 -2
- package/src/core/operators/inner-ownership.test.mts +145 -2
- package/src/core/operators/merge-map.mts +56 -26
- package/src/core/operators/skip-while.mts +12 -8
- package/src/core/operators/skip-while.test.mts +59 -0
- package/src/core/operators/switch-map.mts +21 -10
- package/src/core/operators/take-while.mts +15 -17
- package/src/core/operators/take-while.test.mts +51 -0
- package/src/core/operators/throttle.mts +4 -3
- package/src/core/operators/with-buffered-from.mts +16 -3
- package/src/core/operators/with-buffered-from.test.mts +27 -0
- package/src/core/predefined/operators/attach-index.mts +2 -3
- package/src/core/predefined/operators/skip.mts +2 -2
- package/src/core/utils/index.mts +1 -0
- package/src/core/utils/rethrow-out-of-promise.mts +16 -0
|
@@ -21,6 +21,10 @@ import type {
|
|
|
21
21
|
* Projects each source value to an observable, subscribes to it, and emits its values.
|
|
22
22
|
* When a new value arrives from the source, the previous inner observable is unsubscribed.
|
|
23
23
|
*
|
|
24
|
+
* A value the source already holds when `switchMap` is created (a state, an
|
|
25
|
+
* observable with an initial value) is projected at once, as `map` and `filter`
|
|
26
|
+
* start from it. A completed source is not projected.
|
|
27
|
+
*
|
|
24
28
|
* The projection runs in a scope of its own (see `createScope`): what it
|
|
25
29
|
* creates is disposed when the next value switches away from it and when
|
|
26
30
|
* `switchMap` completes. An observable it only returns — one created outside
|
|
@@ -152,7 +156,9 @@ class SwitchMapObservableClass<A, B>
|
|
|
152
156
|
|
|
153
157
|
this.#mut_subscription = undefined;
|
|
154
158
|
|
|
155
|
-
this.
|
|
159
|
+
this.attachProjectingHeldValue(parentObservable, (value, updateToken) => {
|
|
160
|
+
this.#project(value, updateToken);
|
|
161
|
+
});
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
/** Releases the inner observable, and only the one this operator made. */
|
|
@@ -162,6 +168,19 @@ class SwitchMapObservableClass<A, B>
|
|
|
162
168
|
this.#mut_innerScope?.dispose();
|
|
163
169
|
}
|
|
164
170
|
|
|
171
|
+
/** Switches to the inner observable of `value`. */
|
|
172
|
+
#project(value: A, updateToken: UpdateToken): void {
|
|
173
|
+
this.#releaseInner();
|
|
174
|
+
|
|
175
|
+
const innerScope = createScope(this.#ownerScope);
|
|
176
|
+
|
|
177
|
+
this.#mut_innerScope = innerScope;
|
|
178
|
+
|
|
179
|
+
const observable = innerScope.run(() => this.#mapToObservable(value));
|
|
180
|
+
|
|
181
|
+
this.#mut_subscription = this.subscribeInner(observable, updateToken);
|
|
182
|
+
}
|
|
183
|
+
|
|
165
184
|
override tryUpdate(updateToken: UpdateToken): void {
|
|
166
185
|
const par = this.parents[0];
|
|
167
186
|
|
|
@@ -171,15 +190,7 @@ class SwitchMapObservableClass<A, B>
|
|
|
171
190
|
return; // skip update
|
|
172
191
|
}
|
|
173
192
|
|
|
174
|
-
this.#
|
|
175
|
-
|
|
176
|
-
const innerScope = createScope(this.#ownerScope);
|
|
177
|
-
|
|
178
|
-
this.#mut_innerScope = innerScope;
|
|
179
|
-
|
|
180
|
-
const observable = innerScope.run(() => this.#mapToObservable(sn.value));
|
|
181
|
-
|
|
182
|
-
this.#mut_subscription = this.subscribeInner(observable, updateToken);
|
|
193
|
+
this.#project(sn.value, updateToken);
|
|
183
194
|
}
|
|
184
195
|
|
|
185
196
|
protected override teardown(): void {
|
|
@@ -5,13 +5,7 @@
|
|
|
5
5
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
6
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
7
7
|
// @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
|
|
8
|
-
import {
|
|
9
|
-
Optional,
|
|
10
|
-
SafeUint,
|
|
11
|
-
asSafeUint,
|
|
12
|
-
expectType,
|
|
13
|
-
pipe,
|
|
14
|
-
} from 'ts-data-forge';
|
|
8
|
+
import { Optional, asSafeUint, expectType, type SafeUint } from 'ts-data-forge';
|
|
15
9
|
import { SyncChildObservableClass } from '../class/index.mjs';
|
|
16
10
|
import { source } from '../create/index.mjs';
|
|
17
11
|
import type {
|
|
@@ -73,7 +67,7 @@ import { withInitialValue } from './with-initial-value.mjs';
|
|
|
73
67
|
*
|
|
74
68
|
* assert.deepStrictEqual(valueHistory, [1, 2, 3, 4]);
|
|
75
69
|
*
|
|
76
|
-
* num$.next(1); //
|
|
70
|
+
* num$.next(1); // nothing logged (already completed)
|
|
77
71
|
*
|
|
78
72
|
* assert.deepStrictEqual(valueHistory, [1, 2, 3, 4]);
|
|
79
73
|
* ```
|
|
@@ -98,21 +92,26 @@ class TakeWhileObservableClass<A>
|
|
|
98
92
|
parentObservable: Observable<A>,
|
|
99
93
|
predicate: (value: A, index: SafeUint | -1) => boolean,
|
|
100
94
|
) {
|
|
95
|
+
const parentSnapshot = parentObservable.getSnapshot();
|
|
96
|
+
|
|
97
|
+
const isTaking =
|
|
98
|
+
Optional.isNone(parentSnapshot) || predicate(parentSnapshot.value, -1);
|
|
99
|
+
|
|
101
100
|
super({
|
|
102
101
|
parents: [parentObservable],
|
|
103
|
-
initialValue:
|
|
104
|
-
Optional.isNone(par)
|
|
105
|
-
? Optional.none
|
|
106
|
-
: predicate(par.value, -1)
|
|
107
|
-
? par
|
|
108
|
-
: Optional.none,
|
|
109
|
-
).value,
|
|
102
|
+
initialValue: isTaking ? parentSnapshot : Optional.none,
|
|
110
103
|
});
|
|
111
104
|
|
|
112
105
|
this.#mut_index = -1;
|
|
113
106
|
|
|
114
107
|
this.#predicate = predicate;
|
|
115
108
|
|
|
109
|
+
// A parent value the predicate rejects ends it, as any later one would;
|
|
110
|
+
// `attach` then leaves it out of the graph.
|
|
111
|
+
if (!isTaking) {
|
|
112
|
+
this.complete();
|
|
113
|
+
}
|
|
114
|
+
|
|
116
115
|
this.attach();
|
|
117
116
|
}
|
|
118
117
|
|
|
@@ -125,8 +124,7 @@ class TakeWhileObservableClass<A>
|
|
|
125
124
|
return; // skip update
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
this.#mut_index =
|
|
129
|
-
this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
|
|
127
|
+
this.#mut_index = asSafeUint(this.#mut_index + 1);
|
|
130
128
|
|
|
131
129
|
if (this.#predicate(sn.value, this.#mut_index)) {
|
|
132
130
|
this.setNext(sn.value, updateToken);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createState } from '../../utils/index.mjs';
|
|
2
|
+
import { takeWhile } from './take-while.mjs';
|
|
3
|
+
|
|
4
|
+
describe(takeWhile, () => {
|
|
5
|
+
test('completes at once when the initial value fails the predicate', () => {
|
|
6
|
+
const [s$, setS] = createState(5);
|
|
7
|
+
|
|
8
|
+
const taken$ = s$.pipe(takeWhile((x) => x < 3));
|
|
9
|
+
|
|
10
|
+
const mut_events: string[] = [];
|
|
11
|
+
|
|
12
|
+
taken$.subscribe(
|
|
13
|
+
(x) => {
|
|
14
|
+
mut_events.push(`next ${x}`);
|
|
15
|
+
},
|
|
16
|
+
() => {
|
|
17
|
+
mut_events.push('complete');
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
assert.isTrue(taken$.isCompleted);
|
|
22
|
+
|
|
23
|
+
setS(1);
|
|
24
|
+
|
|
25
|
+
assert.deepStrictEqual(mut_events, ['complete']);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('keeps taking after an initial value that passes the predicate', () => {
|
|
29
|
+
const [s$, setS] = createState(1);
|
|
30
|
+
|
|
31
|
+
const taken$ = s$.pipe(takeWhile((x) => x < 3));
|
|
32
|
+
|
|
33
|
+
const mut_history: number[] = [];
|
|
34
|
+
|
|
35
|
+
taken$.subscribe((x) => {
|
|
36
|
+
mut_history.push(x);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
setS(2);
|
|
40
|
+
|
|
41
|
+
assert.isFalse(taken$.isCompleted);
|
|
42
|
+
|
|
43
|
+
setS(5);
|
|
44
|
+
|
|
45
|
+
assert.isTrue(taken$.isCompleted);
|
|
46
|
+
|
|
47
|
+
setS(1);
|
|
48
|
+
|
|
49
|
+
assert.deepStrictEqual(mut_history, [1, 2]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -139,14 +139,15 @@ class ThrottleObservableClass<A>
|
|
|
139
139
|
return; // skip update
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
// The window opens before `setNext`, which can lead back here: a value a
|
|
143
|
+
// subscriber writes to the parent falls inside it.
|
|
144
144
|
this.#mut_isSkipping = true;
|
|
145
145
|
|
|
146
|
-
// set timer
|
|
147
146
|
this.#mut_timerId = setTimeout(() => {
|
|
148
147
|
this.#mut_isSkipping = false;
|
|
149
148
|
}, this.#milliSeconds);
|
|
149
|
+
|
|
150
|
+
this.setNext(sn.value, updateToken);
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
protected override teardown(): void {
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
6
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
7
7
|
// @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
|
|
8
|
-
import {
|
|
8
|
+
import { Optional, pipe } from 'ts-data-forge';
|
|
9
|
+
import * as Arr from 'ts-data-forge/array';
|
|
9
10
|
import { SyncChildObservableClass } from '../class/index.mjs';
|
|
10
11
|
import type {
|
|
11
12
|
KeepInitialValueOperator,
|
|
@@ -93,11 +94,13 @@ class WithBufferedFromObservableClass<A, B>
|
|
|
93
94
|
#mut_bufferedValues: readonly B[];
|
|
94
95
|
|
|
95
96
|
constructor(parentObservable: Observable<A>, observable: Observable<B>) {
|
|
97
|
+
const parentSnapshot = parentObservable.getSnapshot();
|
|
98
|
+
|
|
96
99
|
super({
|
|
97
100
|
parents: [parentObservable],
|
|
98
101
|
depth: 1 + maxDepth([parentObservable, observable]),
|
|
99
102
|
initialValue: pipe({
|
|
100
|
-
par:
|
|
103
|
+
par: parentSnapshot,
|
|
101
104
|
me: observable.getSnapshot(),
|
|
102
105
|
}).map(({ par, me }) =>
|
|
103
106
|
Optional.isNone(par)
|
|
@@ -115,6 +118,12 @@ class WithBufferedFromObservableClass<A, B>
|
|
|
115
118
|
this.#mut_bufferedValues = Arr.toPushed(this.#mut_bufferedValues, value);
|
|
116
119
|
});
|
|
117
120
|
|
|
121
|
+
// `subscribe` has just pushed the buffered observable's current value,
|
|
122
|
+
// which the initial tuple already carries.
|
|
123
|
+
if (Optional.isSome(parentSnapshot)) {
|
|
124
|
+
this.#mut_bufferedValues = [];
|
|
125
|
+
}
|
|
126
|
+
|
|
118
127
|
this.attach();
|
|
119
128
|
}
|
|
120
129
|
|
|
@@ -127,9 +136,13 @@ class WithBufferedFromObservableClass<A, B>
|
|
|
127
136
|
return; // skip update
|
|
128
137
|
}
|
|
129
138
|
|
|
130
|
-
|
|
139
|
+
const buffered = this.#mut_bufferedValues;
|
|
131
140
|
|
|
141
|
+
// Emptied before `setNext`, which can lead back here: a value a subscriber
|
|
142
|
+
// writes to the buffered observable belongs to the next batch.
|
|
132
143
|
this.#mut_bufferedValues = [];
|
|
144
|
+
|
|
145
|
+
this.setNext([sn.value, buffered], updateToken);
|
|
133
146
|
}
|
|
134
147
|
|
|
135
148
|
protected override teardown(): void {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createState } from '../../utils/index.mjs';
|
|
1
2
|
import { toNode } from '../class/index.mjs';
|
|
2
3
|
import { source } from '../create/index.mjs';
|
|
3
4
|
import { withBufferedFrom } from './with-buffered-from.mjs';
|
|
@@ -55,3 +56,29 @@ describe('withBufferedFrom teardown', () => {
|
|
|
55
56
|
assert.deepStrictEqual(mut_history, [[1, ['A', 'B']]]);
|
|
56
57
|
});
|
|
57
58
|
});
|
|
59
|
+
|
|
60
|
+
describe('withBufferedFrom initial value', () => {
|
|
61
|
+
test('reports the current value of the buffered observable once when both hold one (#2767)', () => {
|
|
62
|
+
const [data$] = createState('x');
|
|
63
|
+
|
|
64
|
+
const [trigger$, setTrigger] = createState(0);
|
|
65
|
+
|
|
66
|
+
const result$ = trigger$.pipe(withBufferedFrom(data$));
|
|
67
|
+
|
|
68
|
+
const mut_history: (readonly [number, readonly string[]])[] = [];
|
|
69
|
+
|
|
70
|
+
result$.subscribe((v) => {
|
|
71
|
+
mut_history.push(v);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
setTrigger(1);
|
|
75
|
+
|
|
76
|
+
setTrigger(2);
|
|
77
|
+
|
|
78
|
+
assert.deepStrictEqual(mut_history, [
|
|
79
|
+
[0, ['x']],
|
|
80
|
+
[1, []],
|
|
81
|
+
[2, []],
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
6
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
7
7
|
// @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
|
|
8
|
-
import { Optional,
|
|
8
|
+
import { Optional, asSafeUint, type SafeUint } from 'ts-data-forge';
|
|
9
9
|
import { SyncChildObservableClass } from '../../class/index.mjs';
|
|
10
10
|
import type {
|
|
11
11
|
KeepInitialValueOperator,
|
|
@@ -112,8 +112,7 @@ class WithIndexObservableClass<A> extends SyncChildObservableClass<
|
|
|
112
112
|
return; // skip update
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
this.#mut_index =
|
|
116
|
-
this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
|
|
115
|
+
this.#mut_index = asSafeUint(this.#mut_index + 1);
|
|
117
116
|
|
|
118
117
|
this.setNext([this.#mut_index, sn.value], updateToken);
|
|
119
118
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isPositiveSafeInt } from 'ts-data-forge';
|
|
2
2
|
import type { PositiveSafeIntWithSmallInt } from 'ts-type-forge';
|
|
3
3
|
import { skipWhile } from '../../operators/index.mjs';
|
|
4
4
|
import type { DropInitialValueOperator } from '../../types/index.mjs';
|
|
@@ -53,6 +53,6 @@ import type { DropInitialValueOperator } from '../../types/index.mjs';
|
|
|
53
53
|
export const skip = <A,>(
|
|
54
54
|
n: PositiveSafeIntWithSmallInt,
|
|
55
55
|
): DropInitialValueOperator<A, A> =>
|
|
56
|
-
!
|
|
56
|
+
!isPositiveSafeInt(n) ? idFn : skipWhile((_, index) => index + 1 <= n);
|
|
57
57
|
|
|
58
58
|
const idFn = <T,>(value: T): T => value;
|
package/src/core/utils/index.mts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throws `error` again from a task of its own, for a `.catch` after each step
|
|
3
|
+
* of a promise chain that runs subscribers (`fromPromise`): after the step that
|
|
4
|
+
* emits and after the one that completes, so that neither exception replaces
|
|
5
|
+
* the other. A subscriber's exception then surfaces as an uncaught error, as
|
|
6
|
+
* one thrown during an update a timer started does (`debounce`, `timer`),
|
|
7
|
+
* rather than as a rejection or not at all.
|
|
8
|
+
*/
|
|
9
|
+
export const rethrowOutOfPromise = (error: unknown): void => {
|
|
10
|
+
setTimeout(() => {
|
|
11
|
+
// The subscriber's own exception, passed on unchanged.
|
|
12
|
+
// @sumi-expect-error exceptions/no-throw
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error -- the subscriber's own exception, passed on unchanged
|
|
14
|
+
throw error;
|
|
15
|
+
}, 0);
|
|
16
|
+
};
|