synstate 6.1.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 +3 -3
- package/dist/core/authoring.d.mts +3 -3
- package/dist/core/class/child-observable-class.d.mts +40 -27
- package/dist/core/class/child-observable-class.d.mts.map +1 -1
- package/dist/core/class/child-observable-class.mjs +78 -46
- package/dist/core/class/child-observable-class.mjs.map +1 -1
- package/dist/core/class/create-child-observable.d.mts +28 -30
- 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 +12 -12
- 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/manager-observable-class.d.mts +6 -6
- package/dist/core/class/manager-observable-class.mjs.map +1 -1
- package/dist/core/class/observable-base-class.d.mts +29 -31
- package/dist/core/class/observable-base-class.d.mts.map +1 -1
- package/dist/core/class/observable-base-class.mjs +0 -2
- package/dist/core/class/observable-base-class.mjs.map +1 -1
- package/dist/core/class/observable-node.d.mts +3 -4
- package/dist/core/class/observable-node.d.mts.map +1 -1
- package/dist/core/class/observable-node.mjs +0 -1
- package/dist/core/class/observable-node.mjs.map +1 -1
- package/dist/core/class/propagation-state.d.mts +6 -8
- package/dist/core/class/propagation-state.d.mts.map +1 -1
- package/dist/core/class/propagation-state.mjs +1 -3
- package/dist/core/class/propagation-state.mjs.map +1 -1
- package/dist/core/class/root-observable-class.d.mts +4 -4
- package/dist/core/combine/derive.d.mts +7 -8
- package/dist/core/combine/derive.d.mts.map +1 -1
- package/dist/core/combine/derive.mjs +2 -2
- package/dist/core/combine/derive.mjs.map +1 -1
- package/dist/core/combine/merge.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts +3 -3
- 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.d.mts +3 -3
- 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/from-subscribable.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/create/timer.mjs.map +1 -1
- package/dist/core/operators/audit.mjs.map +1 -1
- package/dist/core/operators/debounce.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/keep-equal-parts.d.mts +14 -15
- package/dist/core/operators/keep-equal-parts.d.mts.map +1 -1
- package/dist/core/operators/keep-equal-parts.mjs +0 -1
- package/dist/core/operators/keep-equal-parts.mjs.map +1 -1
- package/dist/core/operators/map.mjs.map +1 -1
- package/dist/core/operators/merge-map.d.mts +7 -3
- 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/pairwise.mjs.map +1 -1
- package/dist/core/operators/scan.mjs.map +1 -1
- package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
- package/dist/core/operators/skip-until.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-until.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/take.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/operators/with-current-value-from.mjs.map +1 -1
- package/dist/core/operators/with-initial-value.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/map-to.d.mts +3 -4
- package/dist/core/predefined/operators/map-to.d.mts.map +1 -1
- package/dist/core/predefined/operators/map-to.mjs +0 -1
- package/dist/core/predefined/operators/map-to.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/scope/scope.d.mts +4 -4
- package/dist/core/types/observable-family.d.mts.map +1 -1
- package/dist/core/types/observable-family.mjs.map +1 -1
- package/dist/core/types/observable.d.mts +5 -5
- package/dist/core/types/observable.mjs.map +1 -1
- package/dist/core/utils/current-scope.d.mts +6 -6
- package/dist/core/utils/id-maker.d.mts +2 -2
- 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/observable-brand.d.mts +5 -5
- 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/dist/utils/create-boolean-state.d.mts +2 -2
- package/dist/utils/create-reducer.d.mts +18 -18
- package/dist/utils/create-state.d.mts +2 -2
- package/dist/utils/create-state.mjs.map +1 -1
- package/dist/utils/state-source.d.mts +3 -3
- package/package.json +12 -11
- package/src/core/authoring.mts +3 -3
- package/src/core/class/child-observable-class.mts +131 -90
- package/src/core/class/circular-dependency-comparison.test.mts +2 -2
- package/src/core/class/create-child-observable.mts +35 -34
- package/src/core/class/create-root-observable.mts +13 -12
- package/src/core/class/manager-observable-class.mts +12 -12
- package/src/core/class/observable-base-class.mts +33 -35
- package/src/core/class/observable-node.mts +3 -4
- package/src/core/class/propagation-state.mts +27 -29
- package/src/core/class/remove-completed.test.mts +2 -2
- package/src/core/class/root-observable-class.mts +9 -9
- package/src/core/class/unobserved-write.test.mts +4 -4
- package/src/core/class/write-during-propagation.test.mts +75 -4
- package/src/core/combine/derive.mts +12 -12
- package/src/core/combine/merge.mts +2 -2
- package/src/core/combine/zip.mts +12 -9
- package/src/core/create/counter.mts +7 -7
- package/src/core/create/from-abortable-promise.mts +35 -25
- package/src/core/create/from-promise.mts +30 -20
- package/src/core/create/from-promise.test.mts +97 -0
- package/src/core/create/from-subscribable.mts +2 -2
- package/src/core/create/source.mts +7 -6
- package/src/core/create/timer.mts +2 -2
- package/src/core/operators/audit.mts +2 -2
- package/src/core/operators/debounce.mts +2 -2
- package/src/core/operators/filter.mts +6 -7
- 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/keep-equal-parts.mts +16 -17
- package/src/core/operators/map.mts +2 -2
- package/src/core/operators/merge-map.mts +59 -29
- package/src/core/operators/pairwise.mts +4 -4
- package/src/core/operators/scan.mts +2 -2
- package/src/core/operators/skip-if-no-change.mts +4 -4
- package/src/core/operators/skip-until.mts +4 -4
- package/src/core/operators/skip-while.mts +14 -10
- package/src/core/operators/skip-while.test.mts +59 -0
- package/src/core/operators/switch-map.mts +26 -15
- package/src/core/operators/take-until.mts +2 -2
- package/src/core/operators/take-while.mts +17 -19
- package/src/core/operators/take-while.test.mts +51 -0
- package/src/core/operators/take.mts +2 -2
- package/src/core/operators/throttle.mts +6 -5
- package/src/core/operators/with-buffered-from.mts +18 -5
- package/src/core/operators/with-buffered-from.test.mts +27 -0
- package/src/core/operators/with-current-value-from.mts +2 -2
- package/src/core/operators/with-initial-value.mts +2 -2
- package/src/core/predefined/operators/attach-index.mts +4 -5
- package/src/core/predefined/operators/map-to.mts +3 -4
- package/src/core/predefined/operators/skip.mts +2 -2
- package/src/core/scope/scope.mts +6 -6
- package/src/core/types/observable-family.mts +4 -5
- package/src/core/types/observable.mts +7 -7
- package/src/core/utils/current-scope.mts +8 -8
- package/src/core/utils/id-maker.mts +2 -2
- package/src/core/utils/index.mts +1 -0
- package/src/core/utils/observable-brand.mts +5 -5
- package/src/core/utils/rethrow-out-of-promise.mts +16 -0
- package/src/utils/create-boolean-state.mts +2 -2
- package/src/utils/create-reducer.mts +18 -18
- package/src/utils/create-state.mts +4 -4
- package/src/utils/create-state.test.mts +2 -2
- package/src/utils/state-source.mts +3 -3
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
// Sumi bans classes, `this`, method syntax and writes to `this` outside a
|
|
2
|
-
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
|
|
3
|
-
//
|
|
2
|
+
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
|
|
3
|
+
// those rules are waived here (D-63).
|
|
4
4
|
// @sumi-expect-error-file classes/no-class
|
|
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
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Sumi bans classes, `this`, method syntax and writes to `this` outside a
|
|
2
|
-
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
|
|
3
|
-
//
|
|
2
|
+
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
|
|
3
|
+
// those rules are waived here (D-63).
|
|
4
4
|
// @sumi-expect-error-file classes/no-class
|
|
5
5
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
6
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Sumi bans classes, `this`, method syntax and writes to `this` outside a
|
|
2
|
-
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
|
|
3
|
-
//
|
|
2
|
+
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
|
|
3
|
+
// those rules are waived here (D-63).
|
|
4
4
|
// @sumi-expect-error-file classes/no-class
|
|
5
5
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
6
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
@@ -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 {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// Sumi bans classes, `this`, method syntax and writes to `this` outside a
|
|
2
|
-
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
|
|
3
|
-
//
|
|
2
|
+
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
|
|
3
|
+
// those rules are waived here (D-63).
|
|
4
4
|
// @sumi-expect-error-file classes/no-class
|
|
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
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Sumi bans classes, `this`, method syntax and writes to `this` outside a
|
|
2
|
-
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
|
|
3
|
-
//
|
|
2
|
+
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
|
|
3
|
+
// those rules are waived here (D-63).
|
|
4
4
|
// @sumi-expect-error-file classes/no-class
|
|
5
5
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
6
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Sumi bans classes, `this`, method syntax and writes to `this` outside a
|
|
2
|
-
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
|
|
3
|
-
//
|
|
2
|
+
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
|
|
3
|
+
// those rules are waived here (D-63).
|
|
4
4
|
// @sumi-expect-error-file classes/no-class
|
|
5
5
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
6
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Sumi bans classes, `this`, method syntax and writes to `this` outside a
|
|
2
|
-
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
|
|
3
|
-
//
|
|
2
|
+
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why), so
|
|
3
|
+
// those rules are waived here (D-63).
|
|
4
4
|
// @sumi-expect-error-file classes/no-class
|
|
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
|
}
|
|
@@ -4,10 +4,9 @@ import type { KeepInitialValueOperator } from '../../types/index.mjs';
|
|
|
4
4
|
/**
|
|
5
5
|
* Maps all emitted values to a constant value, ignoring the source values.
|
|
6
6
|
* Emits on every source value although the value never changes — where
|
|
7
|
-
* `map(() => value)` would stop every result after the first as equal — so
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `'reset'`, not `string`.
|
|
7
|
+
* `map(() => value)` would stop every result after the first as equal — so it
|
|
8
|
+
* is the one for turning events into a value. The value's type is inferred as
|
|
9
|
+
* written, as with `as const`: `mapTo('reset')` gives `'reset'`, not `string`.
|
|
11
10
|
*
|
|
12
11
|
* @template A - The type of values from the source
|
|
13
12
|
* @template B - The type of the constant value
|
|
@@ -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/scope/scope.mts
CHANGED
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
* disposed gives a scope that is born disposed, with a warning.
|
|
24
24
|
*
|
|
25
25
|
* Intended for authors of libraries built on synstate rather than for
|
|
26
|
-
* application code; state defined at module level belongs to no scope and
|
|
27
|
-
*
|
|
26
|
+
* application code; state defined at module level belongs to no scope and lives
|
|
27
|
+
* as long as the page.
|
|
28
28
|
*
|
|
29
29
|
* `run` and `dispose` read the scope they are called on, so call them on it
|
|
30
30
|
* (`scope.dispose()`) rather than taking them off it.
|
|
@@ -50,8 +50,8 @@ export const getCurrentScope = (): Scope | undefined =>
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Registers `cleanup` to be called when the current scope is disposed. Cleanups
|
|
53
|
-
* run after the child scopes are disposed and before the owned observables
|
|
54
|
-
*
|
|
53
|
+
* run after the child scopes are disposed and before the owned observables are,
|
|
54
|
+
* newest first.
|
|
55
55
|
*
|
|
56
56
|
* Outside every scope it warns and registers nothing.
|
|
57
57
|
*
|
|
@@ -85,8 +85,8 @@ export const untracked = <R,>(body: () => R): R =>
|
|
|
85
85
|
* A scope is created for every row and branch of a UI, so creating one has to
|
|
86
86
|
* be cheap. It used to be a set of closures with a `Set` of children, a `Set`
|
|
87
87
|
* of owned observables and a record in a `WeakMap`; it is now one object whose
|
|
88
|
-
* methods live on the prototype and whose lists are arrays made when the
|
|
89
|
-
*
|
|
88
|
+
* methods live on the prototype and whose lists are arrays made when the first
|
|
89
|
+
* entry arrives.
|
|
90
90
|
*
|
|
91
91
|
* Whatever leaves a list on its own (a child scope disposed by itself, an
|
|
92
92
|
* observable that completed) is not looked up and spliced out: it only counts
|
|
@@ -65,11 +65,10 @@ export type ScanOperatorObservable<A, B> = InitializedSyncChildObservable<
|
|
|
65
65
|
|
|
66
66
|
// SyncChildObservable
|
|
67
67
|
|
|
68
|
-
// The helpers stay in a namespace so their names do not crowd the module
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
// they check.
|
|
68
|
+
// The helpers stay in a namespace so their names do not crowd the module scope.
|
|
69
|
+
// A namespace holding only types is erasable, so it passes `erasableSyntaxOnly`
|
|
70
|
+
// (TS1294); the `expectType` assertions are call statements, which is why they
|
|
71
|
+
// sit below it rather than beside the types they check.
|
|
73
72
|
namespace SynStateInternals {
|
|
74
73
|
type Cast<A> = A extends NonEmptyUnknownList ? A : never;
|
|
75
74
|
|
|
@@ -32,8 +32,8 @@ type CreateObservableType<A, Kind extends ObservableKind> = Readonly<{
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* One more than the deepest observable this one is derived from; 0 for a
|
|
35
|
-
* root. An update reaches observables in ascending depth, so every parent
|
|
36
|
-
*
|
|
35
|
+
* root. An update reaches observables in ascending depth, so every parent has
|
|
36
|
+
* emitted before its child reads it.
|
|
37
37
|
*/
|
|
38
38
|
depth: Kind extends 'root' ? 0 : number;
|
|
39
39
|
|
|
@@ -173,9 +173,9 @@ export type Operator<A, B> =
|
|
|
173
173
|
| KeepInitialValueOperator<A, B>;
|
|
174
174
|
|
|
175
175
|
/**
|
|
176
|
-
* Tells an observable made by synstate from any other value, by the brand
|
|
177
|
-
*
|
|
178
|
-
*
|
|
176
|
+
* Tells an observable made by synstate from any other value, by the brand every
|
|
177
|
+
* observable carries. Checking for a `subscribe` method instead would also
|
|
178
|
+
* accept plain values that happen to have one.
|
|
179
179
|
*
|
|
180
180
|
* @example
|
|
181
181
|
*
|
|
@@ -223,8 +223,8 @@ export type WrapInitialized<A extends readonly unknown[]> = Readonly<{
|
|
|
223
223
|
// type GetDestTypeOfOperator<Op extends Operator<unknown, unknown>> =
|
|
224
224
|
// Op extends Operator<unknown, infer B> ? B : never;
|
|
225
225
|
|
|
226
|
-
// Only the members meant for users are public; the rest live on the
|
|
227
|
-
//
|
|
226
|
+
// Only the members meant for users are public; the rest live on the internal
|
|
227
|
+
// node types in `core/class` (#2117).
|
|
228
228
|
expectType<
|
|
229
229
|
keyof Observable<unknown>,
|
|
230
230
|
| typeof observableBrand
|
|
@@ -7,10 +7,10 @@ export type ScopeMember = Readonly<{
|
|
|
7
7
|
}>;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* The side of a scope the rest of the core talks to, on top of its public
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* The side of a scope the rest of the core talks to, on top of its public face
|
|
11
|
+
* {@link Scope}. Kept out of the public API (`core/utils/**` is excluded from
|
|
12
|
+
* index generation), so that only `createScope` and its siblings decide what a
|
|
13
|
+
* scope owns.
|
|
14
14
|
*/
|
|
15
15
|
export type ScopeInternals = Scope &
|
|
16
16
|
Readonly<{
|
|
@@ -18,8 +18,8 @@ export type ScopeInternals = Scope &
|
|
|
18
18
|
adopt: (node: ScopeMember) => void;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Tells the scope that one of the nodes it adopted completed on its own,
|
|
22
|
-
*
|
|
21
|
+
* Tells the scope that one of the nodes it adopted completed on its own, so
|
|
22
|
+
* that the scope can drop it and keep no reference to it. The node is
|
|
23
23
|
* completed (`isCompleted`) by the time it calls this.
|
|
24
24
|
*/
|
|
25
25
|
release: () => void;
|
|
@@ -28,8 +28,8 @@ export type ScopeInternals = Scope &
|
|
|
28
28
|
}>;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* The scope whose `run` is executing. A field rather than a module-level
|
|
32
|
-
*
|
|
31
|
+
* The scope whose `run` is executing. A field rather than a module-level `let`,
|
|
32
|
+
* so that the one place writing it is visibly this object.
|
|
33
33
|
*/
|
|
34
34
|
const mut_slot: { current: ScopeInternals | undefined } = {
|
|
35
35
|
current: undefined,
|
|
@@ -12,8 +12,8 @@ export const notUpdatedYet: UpdateToken =
|
|
|
12
12
|
/**
|
|
13
13
|
* Issues the token of a write that opens no update, because nothing observes
|
|
14
14
|
* the observable written to (`setNextIfUnobserved`). Nothing is ever queued
|
|
15
|
-
* with it, so it is a token and not a queue, unlike the tokens
|
|
16
|
-
*
|
|
15
|
+
* with it, so it is a token and not a queue, unlike the tokens `openUpdate`
|
|
16
|
+
* issues.
|
|
17
17
|
*/
|
|
18
18
|
export const issueUnqueuedToken = (): UpdateToken =>
|
|
19
19
|
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
package/src/core/utils/index.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The key every observable carries, set to `true` by `assembleObservable`. It
|
|
3
|
-
* lets `isObservable` tell an observable from any other value at run time —
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* lets `isObservable` tell an observable from any other value at run time — a
|
|
4
|
+
* `subscribe` method is not enough, since plain values may have one — and, as a
|
|
5
|
+
* type, makes the public observable types impossible to satisfy with an object
|
|
6
|
+
* literal: an imitation passed to an operator would lack the internal members
|
|
7
|
+
* propagation reads and fail at run time instead of at compile time.
|
|
8
8
|
*
|
|
9
9
|
* `Symbol.for`, so that an observable made by another copy of synstate in the
|
|
10
10
|
* same program is still recognized.
|
|
@@ -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
|
+
};
|
|
@@ -18,8 +18,8 @@ import { createState } from './create-state.mjs';
|
|
|
18
18
|
* import { useObservableValue } from 'synstate-react-hooks';
|
|
19
19
|
*
|
|
20
20
|
* // Menu drawer open/close state.
|
|
21
|
-
* // setTrue and setFalse can be passed directly as callbacks
|
|
22
|
-
* //
|
|
21
|
+
* // setTrue and setFalse can be passed directly as callbacks — no need to
|
|
22
|
+
* // create wrapper functions like `() => setState(true)`.
|
|
23
23
|
* const [menuOpen$, { setTrue: openMenu, setFalse: closeMenu }] =
|
|
24
24
|
* createBooleanState(false);
|
|
25
25
|
*
|
|
@@ -111,18 +111,18 @@ export const createReducer = <S, A>(
|
|
|
111
111
|
*/
|
|
112
112
|
export type StateOptions<S> = Readonly<{
|
|
113
113
|
/**
|
|
114
|
-
* Whether the next state is the same as the current one. When it says so,
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* identity: `setState` and the like return the current state rather than
|
|
118
|
-
*
|
|
114
|
+
* Whether the next state is the same as the current one. When it says so, the
|
|
115
|
+
* update is not passed on — no subscriber, and nothing derived from the
|
|
116
|
+
* state, is told of it — and the current state is kept, along with its
|
|
117
|
+
* identity: `setState` and the like return the current state rather than the
|
|
118
|
+
* next.
|
|
119
119
|
*
|
|
120
120
|
* Decide here, where the state is held, rather than downstream with
|
|
121
|
-
* `skipIfNoChange`: an update passed on here reaches whatever reads the
|
|
122
|
-
*
|
|
121
|
+
* `skipIfNoChange`: an update passed on here reaches whatever reads the state
|
|
122
|
+
* directly before any operator can thin it out.
|
|
123
123
|
*
|
|
124
|
-
* Defaults to `Object.is`, so writing the value already held passes
|
|
125
|
-
*
|
|
124
|
+
* Defaults to `Object.is`, so writing the value already held passes nothing
|
|
125
|
+
* on. Pass `() => false` to pass on every update, equal or not.
|
|
126
126
|
*
|
|
127
127
|
* @param current - The state held now
|
|
128
128
|
* @param next - The state the update would set
|
|
@@ -132,8 +132,8 @@ export type StateOptions<S> = Readonly<{
|
|
|
132
132
|
/**
|
|
133
133
|
* Whether an update keeps the parts of the state it did not change. With
|
|
134
134
|
* `true`, the next state is compared with the current one before it is
|
|
135
|
-
* written, and every part of it deeply equal to the same part of the
|
|
136
|
-
*
|
|
135
|
+
* written, and every part of it deeply equal to the same part of the current
|
|
136
|
+
* state is replaced by that part (`replaceEqualDeep` of
|
|
137
137
|
* ts-data-forge): a reader of an unchanged part keeps the object it has, so
|
|
138
138
|
* `map`, comparing with `Object.is`, stops there. When the whole next state is
|
|
139
139
|
* deeply equal, the current state is kept and nothing is passed on, so
|
|
@@ -146,14 +146,14 @@ export type StateOptions<S> = Readonly<{
|
|
|
146
146
|
* arrays are walked into; a `Map`, a `Set`, a `Date` or a class instance is
|
|
147
147
|
* compared and kept whole.
|
|
148
148
|
*
|
|
149
|
-
* Prefer this over `keepEqualParts()` downstream: keeping the parts where
|
|
150
|
-
*
|
|
151
|
-
*
|
|
149
|
+
* Prefer this over `keepEqualParts()` downstream: keeping the parts where the
|
|
150
|
+
* state is written means no update with nothing in it is passed on at all,
|
|
151
|
+
* and nothing downstream has anything to undo.
|
|
152
152
|
*
|
|
153
|
-
* Defaults to `false`: the next state is written as given. With it on,
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
153
|
+
* Defaults to `false`: the next state is written as given. With it on, each
|
|
154
|
+
* update compares deeply every part of the next state that is not the very
|
|
155
|
+
* object of the current one; a state whose updates already keep what they do
|
|
156
|
+
* not change, as `{ ...prev, x }` does, gains nothing from it.
|
|
157
157
|
*/
|
|
158
158
|
keepEqualParts?: boolean | ReplaceEqualDeepOptions;
|
|
159
159
|
}>;
|
|
@@ -107,8 +107,8 @@ import { createStateSource } from './state-source.mjs';
|
|
|
107
107
|
*
|
|
108
108
|
* assert.strictEqual(report.getSnapshot().value, held);
|
|
109
109
|
*
|
|
110
|
-
* // An edited title: a new report and a new array, while the summary is
|
|
111
|
-
* //
|
|
110
|
+
* // An edited title: a new report and a new array, while the summary is the
|
|
111
|
+
* // one held, so `summary` is not told of it.
|
|
112
112
|
* setReport({
|
|
113
113
|
* summary: { open: 1 },
|
|
114
114
|
* pulls: [{ number: 1, title: 'one (edited)' }],
|
|
@@ -146,8 +146,8 @@ export const createState = <S,>(
|
|
|
146
146
|
options,
|
|
147
147
|
);
|
|
148
148
|
|
|
149
|
-
// Each compares with the state the write started from, as `dispatch`
|
|
150
|
-
//
|
|
149
|
+
// Each compares with the state the write started from, as `dispatch` does,
|
|
150
|
+
// and builds nothing on the way.
|
|
151
151
|
const updateState = (updateFn: (prev: S) => S): S => {
|
|
152
152
|
const currentState = getSnapshot();
|
|
153
153
|
|
|
@@ -54,8 +54,8 @@ describe(createState, () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
// What `equals` is for: whatever reads the state, directly or through
|
|
57
|
-
// anything derived from it, is told only of a real change, and an equal
|
|
58
|
-
//
|
|
57
|
+
// anything derived from it, is told only of a real change, and an equal value
|
|
58
|
+
// written over the current one leaves the object that is there.
|
|
59
59
|
test('keeps the current object when `equals` finds the next one equal to it', () => {
|
|
60
60
|
const [state, setState, { getSnapshot }] = createState<readonly number[]>(
|
|
61
61
|
[1, 2],
|
|
@@ -3,9 +3,9 @@ import { source, type InitializedSourceObservable } from '../core/index.mjs';
|
|
|
3
3
|
import type { StateOptions } from './create-reducer.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* What `createState` and `createReducer` are both built on: the source
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* What `createState` and `createReducer` are both built on: the source holding
|
|
7
|
+
* the state, its current value, and `commit`, the one way either writes it.
|
|
8
|
+
* `commit` writes `reduced` unless `equals` finds it the same as
|
|
9
9
|
* `currentState`, after keeping the parts of `currentState` it did not change
|
|
10
10
|
* when `keepEqualParts` asks for that, and returns the state written, or
|
|
11
11
|
* `currentState` when nothing was.
|