synstate 3.3.0 → 4.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 +14 -9
- package/dist/core/authoring.d.mts +7 -0
- package/dist/core/authoring.d.mts.map +1 -0
- package/dist/core/authoring.mjs +7 -0
- package/dist/core/authoring.mjs.map +1 -0
- package/dist/core/class/child-observable-class.d.mts +93 -0
- package/dist/core/class/child-observable-class.d.mts.map +1 -0
- package/dist/core/class/child-observable-class.mjs +248 -0
- package/dist/core/class/child-observable-class.mjs.map +1 -0
- package/dist/core/class/create-child-observable.d.mts +159 -0
- package/dist/core/class/create-child-observable.d.mts.map +1 -0
- package/dist/core/class/create-child-observable.mjs +183 -0
- package/dist/core/class/create-child-observable.mjs.map +1 -0
- package/dist/core/class/create-root-observable.d.mts +37 -0
- package/dist/core/class/create-root-observable.d.mts.map +1 -0
- package/dist/core/class/create-root-observable.mjs +52 -0
- package/dist/core/class/create-root-observable.mjs.map +1 -0
- package/dist/core/class/index.d.mts +9 -0
- package/dist/core/class/index.d.mts.map +1 -0
- package/dist/core/class/index.mjs +9 -0
- package/dist/core/class/index.mjs.map +1 -0
- package/dist/core/class/manager-observable-class.d.mts +26 -0
- package/dist/core/class/manager-observable-class.d.mts.map +1 -0
- package/dist/core/class/manager-observable-class.mjs +116 -0
- package/dist/core/class/manager-observable-class.mjs.map +1 -0
- package/dist/core/class/observable-base-class.d.mts +97 -0
- package/dist/core/class/observable-base-class.d.mts.map +1 -0
- package/dist/core/class/observable-base-class.mjs +219 -0
- package/dist/core/class/observable-base-class.mjs.map +1 -0
- package/dist/core/class/observable-node.d.mts +40 -0
- package/dist/core/class/observable-node.d.mts.map +1 -0
- package/dist/core/class/observable-node.mjs +10 -0
- package/dist/core/class/observable-node.mjs.map +1 -0
- package/dist/core/class/propagation-state.d.mts +24 -0
- package/dist/core/class/propagation-state.d.mts.map +1 -0
- package/dist/core/class/propagation-state.mjs +66 -0
- package/dist/core/class/propagation-state.mjs.map +1 -0
- package/dist/core/class/root-observable-class.d.mts +23 -0
- package/dist/core/class/root-observable-class.d.mts.map +1 -0
- package/dist/core/class/root-observable-class.mjs +58 -0
- package/dist/core/class/root-observable-class.mjs.map +1 -0
- package/dist/core/combine/combine.d.mts.map +1 -1
- package/dist/core/combine/combine.mjs +25 -14
- package/dist/core/combine/combine.mjs.map +1 -1
- package/dist/core/combine/merge.d.mts +0 -3
- package/dist/core/combine/merge.d.mts.map +1 -1
- package/dist/core/combine/merge.mjs +24 -15
- package/dist/core/combine/merge.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts +4 -2
- package/dist/core/combine/zip.d.mts.map +1 -1
- package/dist/core/combine/zip.mjs +54 -18
- package/dist/core/combine/zip.mjs.map +1 -1
- package/dist/core/create/counter.d.mts +9 -3
- package/dist/core/create/counter.d.mts.map +1 -1
- package/dist/core/create/counter.mjs +69 -35
- 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 +26 -18
- 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 +30 -21
- package/dist/core/create/from-promise.mjs.map +1 -1
- package/dist/core/create/from-subscribable.d.mts.map +1 -1
- package/dist/core/create/from-subscribable.mjs +48 -12
- package/dist/core/create/from-subscribable.mjs.map +1 -1
- package/dist/core/create/just.d.mts.map +1 -1
- package/dist/core/create/just.mjs +4 -2
- package/dist/core/create/just.mjs.map +1 -1
- package/dist/core/create/source.d.mts +7 -0
- package/dist/core/create/source.d.mts.map +1 -1
- package/dist/core/create/source.mjs +27 -13
- package/dist/core/create/source.mjs.map +1 -1
- package/dist/core/create/timer.d.mts.map +1 -1
- package/dist/core/create/timer.mjs +40 -25
- package/dist/core/create/timer.mjs.map +1 -1
- package/dist/core/index.d.mts +2 -0
- package/dist/core/index.d.mts.map +1 -1
- package/dist/core/index.mjs +2 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/operators/audit.d.mts +3 -0
- package/dist/core/operators/audit.d.mts.map +1 -1
- package/dist/core/operators/audit.mjs +56 -28
- package/dist/core/operators/audit.mjs.map +1 -1
- package/dist/core/operators/debounce.d.mts +3 -0
- package/dist/core/operators/debounce.d.mts.map +1 -1
- package/dist/core/operators/debounce.mjs +57 -26
- package/dist/core/operators/debounce.mjs.map +1 -1
- package/dist/core/operators/filter.d.mts.map +1 -1
- package/dist/core/operators/filter.mjs +35 -21
- package/dist/core/operators/filter.mjs.map +1 -1
- package/dist/core/operators/index.d.mts +1 -0
- package/dist/core/operators/index.d.mts.map +1 -1
- package/dist/core/operators/index.mjs +1 -0
- package/dist/core/operators/index.mjs.map +1 -1
- package/dist/core/operators/map.d.mts.map +1 -1
- package/dist/core/operators/map.mjs +31 -17
- package/dist/core/operators/map.mjs.map +1 -1
- package/dist/core/operators/merge-map.d.mts +5 -3
- package/dist/core/operators/merge-map.d.mts.map +1 -1
- package/dist/core/operators/merge-map.mjs +49 -26
- package/dist/core/operators/merge-map.mjs.map +1 -1
- package/dist/core/operators/pairwise.d.mts.map +1 -1
- package/dist/core/operators/pairwise.mjs +31 -20
- package/dist/core/operators/pairwise.mjs.map +1 -1
- package/dist/core/operators/scan.d.mts.map +1 -1
- package/dist/core/operators/scan.mjs +27 -14
- package/dist/core/operators/scan.mjs.map +1 -1
- package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
- package/dist/core/operators/skip-if-no-change.mjs +33 -19
- package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
- package/dist/core/operators/skip-until.d.mts.map +1 -1
- package/dist/core/operators/skip-until.mjs +43 -20
- package/dist/core/operators/skip-until.mjs.map +1 -1
- package/dist/core/operators/skip-while.d.mts.map +1 -1
- package/dist/core/operators/skip-while.mjs +35 -20
- package/dist/core/operators/skip-while.mjs.map +1 -1
- package/dist/core/operators/switch-map.d.mts +5 -3
- package/dist/core/operators/switch-map.d.mts.map +1 -1
- package/dist/core/operators/switch-map.mjs +53 -25
- package/dist/core/operators/switch-map.mjs.map +1 -1
- package/dist/core/operators/take-until.d.mts.map +1 -1
- package/dist/core/operators/take-until.mjs +38 -19
- package/dist/core/operators/take-until.mjs.map +1 -1
- package/dist/core/operators/take-while.d.mts.map +1 -1
- package/dist/core/operators/take-while.mjs +36 -22
- package/dist/core/operators/take-while.mjs.map +1 -1
- package/dist/core/{predefined/operators → operators}/take.d.mts +7 -3
- package/dist/core/operators/take.d.mts.map +1 -0
- package/dist/core/operators/take.mjs +102 -0
- package/dist/core/operators/take.mjs.map +1 -0
- package/dist/core/operators/throttle.d.mts.map +1 -1
- package/dist/core/operators/throttle.mjs +41 -26
- 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 +44 -31
- package/dist/core/operators/with-buffered-from.mjs.map +1 -1
- package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
- package/dist/core/operators/with-current-value-from.mjs +36 -21
- package/dist/core/operators/with-current-value-from.mjs.map +1 -1
- package/dist/core/operators/with-initial-value.d.mts.map +1 -1
- package/dist/core/operators/with-initial-value.mjs +24 -12
- package/dist/core/operators/with-initial-value.mjs.map +1 -1
- package/dist/core/predefined/operators/index.d.mts +0 -1
- package/dist/core/predefined/operators/index.d.mts.map +1 -1
- package/dist/core/predefined/operators/index.mjs +0 -1
- package/dist/core/predefined/operators/index.mjs.map +1 -1
- package/dist/core/scope/index.d.mts +2 -0
- package/dist/core/scope/index.d.mts.map +1 -0
- package/dist/core/scope/index.mjs +2 -0
- package/dist/core/scope/index.mjs.map +1 -0
- package/dist/core/scope/scope.d.mts +51 -0
- package/dist/core/scope/scope.d.mts.map +1 -0
- package/dist/core/scope/scope.mjs +194 -0
- package/dist/core/scope/scope.mjs.map +1 -0
- package/dist/core/types/id.d.mts +0 -2
- package/dist/core/types/id.d.mts.map +1 -1
- package/dist/core/types/index.d.mts +1 -0
- package/dist/core/types/index.d.mts.map +1 -1
- package/dist/core/types/index.mjs +1 -0
- package/dist/core/types/index.mjs.map +1 -1
- package/dist/core/types/observable-family.d.mts +6 -1
- 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 +40 -28
- package/dist/core/types/observable.d.mts.map +1 -1
- package/dist/core/types/observable.mjs +24 -0
- package/dist/core/types/observable.mjs.map +1 -1
- package/dist/core/types/scope.d.mts +27 -0
- package/dist/core/types/scope.d.mts.map +1 -0
- package/dist/core/types/scope.mjs +2 -0
- package/dist/core/types/scope.mjs.map +1 -0
- package/dist/core/utils/current-scope.d.mts +30 -0
- package/dist/core/utils/current-scope.d.mts.map +1 -0
- package/dist/core/utils/current-scope.mjs +26 -0
- package/dist/core/utils/current-scope.mjs.map +1 -0
- package/dist/core/utils/id-maker.d.mts +14 -3
- package/dist/core/utils/id-maker.d.mts.map +1 -1
- package/dist/core/utils/id-maker.mjs +18 -14
- package/dist/core/utils/id-maker.mjs.map +1 -1
- package/dist/core/utils/index.d.mts +2 -1
- package/dist/core/utils/index.d.mts.map +1 -1
- package/dist/core/utils/index.mjs +2 -1
- package/dist/core/utils/index.mjs.map +1 -1
- package/dist/core/utils/max-depth.d.mts +1 -0
- package/dist/core/utils/max-depth.d.mts.map +1 -1
- package/dist/core/utils/max-depth.mjs +9 -7
- package/dist/core/utils/max-depth.mjs.map +1 -1
- package/dist/core/utils/observable-brand.d.mts +13 -0
- package/dist/core/utils/observable-brand.d.mts.map +1 -0
- package/dist/core/utils/observable-brand.mjs +13 -0
- package/dist/core/utils/observable-brand.mjs.map +1 -0
- package/dist/utils/create-boolean-state.d.mts +2 -2
- package/dist/utils/create-event-emitter.d.mts +2 -2
- package/dist/utils/create-reducer.d.mts +17 -8
- package/dist/utils/create-reducer.d.mts.map +1 -1
- package/dist/utils/create-reducer.mjs +14 -6
- package/dist/utils/create-reducer.mjs.map +1 -1
- package/dist/utils/create-state.d.mts +9 -2
- package/dist/utils/create-state.d.mts.map +1 -1
- package/dist/utils/create-state.mjs +7 -0
- package/dist/utils/create-state.mjs.map +1 -1
- package/package.json +16 -16
- package/src/core/authoring.mts +19 -0
- package/src/core/class/child-observable-class.mts +374 -0
- package/src/core/{base → class}/circular-dependency-comparison.test.mts +46 -38
- package/src/core/{base → class}/circular-dependency.test.mts +38 -2
- package/src/core/class/complete-propagation.test.mts +264 -0
- package/src/core/class/create-child-observable.mts +370 -0
- package/src/core/class/create-root-observable.mts +107 -0
- package/src/core/class/index.mts +8 -0
- package/src/core/class/manager-observable-class.mts +166 -0
- package/src/core/class/observable-base-class.mts +315 -0
- package/src/core/{base → class}/observable-base.test.mts +1 -1
- package/src/core/class/observable-node.mts +54 -0
- package/src/core/class/propagation-order.test.mts +92 -0
- package/src/core/class/propagation-state.mts +80 -0
- package/src/core/class/remove-completed.test.mts +369 -0
- package/src/core/class/root-observable-class.mts +85 -0
- package/src/core/class/write-during-propagation.test.mts +234 -0
- package/src/core/combine/combine.mts +67 -55
- package/src/core/combine/merge.mts +33 -26
- package/src/core/combine/zip.mts +103 -58
- package/src/core/create/counter.mts +105 -54
- package/src/core/create/counter.test.mts +82 -0
- package/src/core/create/from-abortable-promise.mts +54 -42
- package/src/core/create/from-promise.mts +38 -29
- package/src/core/create/from-subscribable.mts +68 -23
- package/src/core/create/from-subscribable.test.mts +134 -0
- package/src/core/create/just.mts +4 -5
- package/src/core/create/source.mts +37 -13
- package/src/core/create/source.test.mts +41 -0
- package/src/core/create/timer.mts +51 -32
- package/src/core/index.mts +2 -0
- package/src/core/operators/audit.mts +79 -45
- package/src/core/operators/debounce-audit-complete.test.mts +121 -0
- package/src/core/operators/debounce.mts +81 -40
- package/src/core/operators/filter.mts +48 -31
- package/src/core/operators/index.mts +1 -0
- package/src/core/operators/inner-ownership.test.mts +280 -0
- package/src/core/operators/map.mts +49 -32
- package/src/core/operators/merge-map.mts +81 -46
- package/src/core/operators/pairwise.mts +40 -30
- package/src/core/operators/scan.mts +41 -24
- package/src/core/operators/skip-if-no-change.mts +45 -31
- package/src/core/operators/skip-until.mts +59 -33
- package/src/core/operators/skip-while.mts +49 -31
- package/src/core/operators/switch-map.mts +74 -38
- package/src/core/operators/take-until-skip-until-teardown.test.mts +133 -0
- package/src/core/operators/take-until.mts +49 -31
- package/src/core/operators/take-while.mts +50 -33
- package/src/core/operators/take.mts +138 -0
- package/src/core/operators/throttle.mts +55 -38
- package/src/core/operators/with-buffered-from.mts +45 -35
- package/src/core/operators/with-buffered-from.test.mts +57 -0
- package/src/core/operators/with-current-value-from.mts +39 -25
- package/src/core/operators/with-initial-value.mts +30 -21
- package/src/core/predefined/operators/index.mts +0 -1
- package/src/core/scope/index.mts +1 -0
- package/src/core/scope/scope.mts +260 -0
- package/src/core/scope/scope.test.mts +462 -0
- package/src/core/types/id.mts +0 -4
- package/src/core/types/index.mts +1 -0
- package/src/core/types/observable-family.mts +7 -1
- package/src/core/types/observable.mts +77 -42
- package/src/core/types/scope.mts +27 -0
- package/src/core/utils/current-scope.mts +61 -0
- package/src/core/utils/id-maker.mts +20 -30
- package/src/core/utils/index.mts +2 -1
- package/src/core/utils/max-depth.mts +12 -9
- package/src/core/utils/observable-brand.mts +12 -0
- package/src/utils/create-boolean-state.mts +2 -2
- package/src/utils/create-boolean-state.test.mts +18 -4
- package/src/utils/create-event-emitter.mts +2 -2
- package/src/utils/create-reducer.mts +22 -16
- package/src/utils/create-reducer.test.mts +43 -14
- package/src/utils/create-state.mts +9 -2
- package/src/utils/create-state.test.mts +32 -14
- package/dist/core/base/create-child-observable.d.mts +0 -65
- package/dist/core/base/create-child-observable.d.mts.map +0 -1
- package/dist/core/base/create-child-observable.mjs +0 -267
- package/dist/core/base/create-child-observable.mjs.map +0 -1
- package/dist/core/base/create-observable-base.d.mts +0 -147
- package/dist/core/base/create-observable-base.d.mts.map +0 -1
- package/dist/core/base/create-observable-base.mjs +0 -255
- package/dist/core/base/create-observable-base.mjs.map +0 -1
- package/dist/core/base/create-root-observable.d.mts +0 -37
- package/dist/core/base/create-root-observable.d.mts.map +0 -1
- package/dist/core/base/create-root-observable.mjs +0 -57
- package/dist/core/base/create-root-observable.mjs.map +0 -1
- package/dist/core/base/index.d.mts +0 -3
- package/dist/core/base/index.d.mts.map +0 -1
- package/dist/core/base/index.mjs +0 -3
- package/dist/core/base/index.mjs.map +0 -1
- package/dist/core/predefined/operators/take.d.mts.map +0 -1
- package/dist/core/predefined/operators/take.mjs +0 -44
- package/dist/core/predefined/operators/take.mjs.map +0 -1
- package/dist/core/utils/utils.d.mts +0 -6
- package/dist/core/utils/utils.d.mts.map +0 -1
- package/dist/core/utils/utils.mjs +0 -37
- package/dist/core/utils/utils.mjs.map +0 -1
- package/src/core/base/create-child-observable.mts +0 -457
- package/src/core/base/create-observable-base.mts +0 -548
- package/src/core/base/create-root-observable.mts +0 -113
- package/src/core/base/index.mts +0 -2
- package/src/core/predefined/operators/take.mts +0 -48
- package/src/core/utils/utils.mts +0 -56
- package/src/core/utils/utils.test.mts +0 -88
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { Result } from 'ts-data-forge';
|
|
2
|
+
import { panic } from 'ts-std-forge';
|
|
3
|
+
import type { FixedLengthTuple } from 'ts-type-forge';
|
|
4
|
+
import { createState } from '../../utils/index.mjs';
|
|
5
|
+
import { combine } from '../combine/index.mjs';
|
|
6
|
+
import { fromSubscribable, source } from '../create/index.mjs';
|
|
7
|
+
import { map, switchMap } from '../operators/index.mjs';
|
|
8
|
+
import { writeDuringPropagationWarning } from './root-observable-class.mjs';
|
|
9
|
+
|
|
10
|
+
describe('a write to a root observable while an update is propagating (#2336)', () => {
|
|
11
|
+
test('warns when a subscriber writes to another state, and still propagates the write immediately', async () => {
|
|
12
|
+
const [a$, setA] = createState(0);
|
|
13
|
+
|
|
14
|
+
const [b$, setB] = createState(0);
|
|
15
|
+
|
|
16
|
+
const doubled$ = a$.pipe(map((a) => a * 10));
|
|
17
|
+
|
|
18
|
+
a$.subscribe((a) => {
|
|
19
|
+
setB(a * 2);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const mut_history: FixedLengthTuple<2, number>[] = [];
|
|
23
|
+
|
|
24
|
+
combine([doubled$, b$]).subscribe((v) => {
|
|
25
|
+
mut_history.push(v);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const warn = await captureWarnings(() => {
|
|
29
|
+
setA(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
assert.deepStrictEqual(warn, [writeDuringPropagationWarning]);
|
|
33
|
+
|
|
34
|
+
// The behavior is unchanged: b$ propagates before doubled$ is updated, so
|
|
35
|
+
// the intermediate state [0, 2] is observed.
|
|
36
|
+
assert.deepStrictEqual(mut_history, [
|
|
37
|
+
[0, 0],
|
|
38
|
+
[0, 2],
|
|
39
|
+
[10, 2],
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
assert.strictEqual(b$.getSnapshot().value, 2);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('warns when a projection writes to a state', async () => {
|
|
46
|
+
const a$ = source<number>(0);
|
|
47
|
+
|
|
48
|
+
const [, setB] = createState(0);
|
|
49
|
+
|
|
50
|
+
a$.pipe(
|
|
51
|
+
map((a) => {
|
|
52
|
+
setB(a);
|
|
53
|
+
|
|
54
|
+
return a;
|
|
55
|
+
}),
|
|
56
|
+
).subscribe(() => {});
|
|
57
|
+
|
|
58
|
+
const warn = await captureWarnings(() => {
|
|
59
|
+
a$.next(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
assert.deepStrictEqual(warn, [writeDuringPropagationWarning]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('warns when a subscriber of an asynchronous child writes to a state', async () => {
|
|
66
|
+
const outer$ = source<number>(0);
|
|
67
|
+
|
|
68
|
+
const inner$ = source<number>(0);
|
|
69
|
+
|
|
70
|
+
const [, setB] = createState(0);
|
|
71
|
+
|
|
72
|
+
outer$.pipe(switchMap(() => inner$)).subscribe((v) => {
|
|
73
|
+
if (v > 0) {
|
|
74
|
+
setB(v);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const warnOnSwitch = await captureWarnings(() => {
|
|
79
|
+
outer$.next(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
assert.deepStrictEqual(warnOnSwitch, []);
|
|
83
|
+
|
|
84
|
+
const warn = await captureWarnings(() => {
|
|
85
|
+
inner$.next(1);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
assert.deepStrictEqual(warn, [writeDuringPropagationWarning]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('warns once per observable', async () => {
|
|
92
|
+
const a$ = source<number>(0);
|
|
93
|
+
|
|
94
|
+
const [, setB] = createState(0);
|
|
95
|
+
|
|
96
|
+
const [, setC] = createState(0);
|
|
97
|
+
|
|
98
|
+
a$.subscribe((a) => {
|
|
99
|
+
if (a <= 0) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setB(a);
|
|
104
|
+
|
|
105
|
+
setB(a + 1);
|
|
106
|
+
|
|
107
|
+
setC(a);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const warn = await captureWarnings(() => {
|
|
111
|
+
a$.next(1);
|
|
112
|
+
|
|
113
|
+
a$.next(2);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
assert.deepStrictEqual(warn, [
|
|
117
|
+
writeDuringPropagationWarning,
|
|
118
|
+
writeDuringPropagationWarning,
|
|
119
|
+
]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('does not warn for writes outside propagation', async () => {
|
|
123
|
+
const [a$, setA] = createState(0);
|
|
124
|
+
|
|
125
|
+
const [b$, setB] = createState(0);
|
|
126
|
+
|
|
127
|
+
a$.subscribe((a) => {
|
|
128
|
+
queueMicrotask(() => {
|
|
129
|
+
setB(a);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const warn = await captureWarnings(async () => {
|
|
134
|
+
setA(1);
|
|
135
|
+
|
|
136
|
+
await Promise.resolve();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
assert.deepStrictEqual(warn, []);
|
|
140
|
+
|
|
141
|
+
assert.strictEqual(b$.getSnapshot().value, 1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('does not warn when an operator emits an inner observable synchronously', async () => {
|
|
145
|
+
const outer$ = source<number>(0);
|
|
146
|
+
|
|
147
|
+
const mut_history: number[] = [];
|
|
148
|
+
|
|
149
|
+
outer$.pipe(switchMap((x) => source(x * 10))).subscribe((v) => {
|
|
150
|
+
mut_history.push(v);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const warn = await captureWarnings(() => {
|
|
154
|
+
outer$.next(1);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
assert.deepStrictEqual(warn, []);
|
|
158
|
+
|
|
159
|
+
assert.deepStrictEqual(mut_history, [10]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('does not warn when a root created during propagation emits while it is created', async () => {
|
|
163
|
+
const outer$ = source<number>(0);
|
|
164
|
+
|
|
165
|
+
const mut_history: number[] = [];
|
|
166
|
+
|
|
167
|
+
outer$
|
|
168
|
+
.pipe(
|
|
169
|
+
switchMap((x) =>
|
|
170
|
+
fromSubscribable<number>({
|
|
171
|
+
subscribe: (onNext) => {
|
|
172
|
+
onNext(x * 10);
|
|
173
|
+
|
|
174
|
+
return { unsubscribe: () => {} };
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
.subscribe((r) => {
|
|
180
|
+
if (Result.isOk(r)) {
|
|
181
|
+
mut_history.push(r.value);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const warn = await captureWarnings(() => {
|
|
186
|
+
outer$.next(1);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
assert.deepStrictEqual(warn, []);
|
|
190
|
+
|
|
191
|
+
assert.deepStrictEqual(mut_history, [10]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('a subscriber that throws does not leave the propagation flag set', async () => {
|
|
195
|
+
const a$ = source<number>(0);
|
|
196
|
+
|
|
197
|
+
const [, setB] = createState(0);
|
|
198
|
+
|
|
199
|
+
a$.subscribe((a) => {
|
|
200
|
+
if (a > 0) {
|
|
201
|
+
panic('boom');
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const warn = await captureWarnings(() => {
|
|
206
|
+
expect(() => {
|
|
207
|
+
a$.next(1);
|
|
208
|
+
}).toThrow('boom');
|
|
209
|
+
|
|
210
|
+
setB(1);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
assert.deepStrictEqual(warn, []);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Runs `run` with `console.warn` replaced by a mock, and returns the messages
|
|
219
|
+
* it was called with.
|
|
220
|
+
*/
|
|
221
|
+
const captureWarnings = async (
|
|
222
|
+
run: () => Promise<void> | void,
|
|
223
|
+
): Promise<readonly unknown[]> => {
|
|
224
|
+
const warn = vi.fn();
|
|
225
|
+
|
|
226
|
+
vi.stubGlobal('console', { ...console, warn });
|
|
227
|
+
|
|
228
|
+
await run();
|
|
229
|
+
|
|
230
|
+
// cspell:disable-next-line
|
|
231
|
+
vi.unstubAllGlobals();
|
|
232
|
+
|
|
233
|
+
return warn.mock.calls.map((args: readonly unknown[]) => args[0]);
|
|
234
|
+
};
|
|
@@ -1,6 +1,12 @@
|
|
|
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
|
+
// so those rules are waived here (D-63).
|
|
4
|
+
// @sumi-expect-error-file classes/no-class
|
|
5
|
+
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
|
+
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
1
7
|
import { Arr, expectType, Optional } from 'ts-data-forge';
|
|
2
8
|
import type { NonEmptyTuple, Tuple } from 'ts-type-forge';
|
|
3
|
-
import {
|
|
9
|
+
import { SyncChildObservableClass } from '../class/index.mjs';
|
|
4
10
|
import { source } from '../create/index.mjs';
|
|
5
11
|
import { withInitialValue } from '../operators/index.mjs';
|
|
6
12
|
import type {
|
|
@@ -11,6 +17,7 @@ import type {
|
|
|
11
17
|
NonEmptyUnknownList,
|
|
12
18
|
Observable,
|
|
13
19
|
SyncChildObservable,
|
|
20
|
+
UpdateToken,
|
|
14
21
|
Wrap,
|
|
15
22
|
} from '../types/index.mjs';
|
|
16
23
|
|
|
@@ -75,7 +82,9 @@ export const combine = <const OS extends NonEmptyTuple<Observable<unknown>>>(
|
|
|
75
82
|
parents: OS,
|
|
76
83
|
): CombineObservableRefined<OS> =>
|
|
77
84
|
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
78
|
-
|
|
85
|
+
new CombineObservableClass(
|
|
86
|
+
parents,
|
|
87
|
+
) as unknown as CombineObservableRefined<OS>;
|
|
79
88
|
|
|
80
89
|
/**
|
|
81
90
|
* Alias for `combine`.
|
|
@@ -83,61 +92,64 @@ export const combine = <const OS extends NonEmptyTuple<Observable<unknown>>>(
|
|
|
83
92
|
*/
|
|
84
93
|
export const combineLatest = combine;
|
|
85
94
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Optional
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
{
|
|
95
|
+
class CombineObservableClass<const A extends NonEmptyUnknownList>
|
|
96
|
+
extends SyncChildObservableClass<A, A>
|
|
97
|
+
implements CombineObservable<A>
|
|
98
|
+
{
|
|
99
|
+
constructor(parents: Wrap<A>) {
|
|
100
|
+
const parentsValues: Tuple.MapTo<Optional<A[number]>, A> = Arr.map(
|
|
101
|
+
parents,
|
|
102
|
+
(p) => p.getSnapshot(),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// The annotation on `initialValue` is what replaces the type assertion.
|
|
106
|
+
// `Arr.map` carries a single result element type, so it reports `A[number]`
|
|
107
|
+
// at every position, and `Tuple.MapTo<A[number], A>` — the uniform mapping
|
|
108
|
+
// — is *not* assignable back to `A`. The positional
|
|
109
|
+
// `Readonly<{ [K in keyof A]: A[K] }>` is, and TypeScript accepts the
|
|
110
|
+
// uniform result into it because the two mapped types share `keyof A`. So
|
|
111
|
+
// it is the annotation, not an assertion, that records the position-wise
|
|
112
|
+
// fact the checker cannot derive on its own.
|
|
113
|
+
const initialValue: Optional<Readonly<{ [K in keyof A]: A[K] }>> =
|
|
114
|
+
Arr.every(parentsValues, Optional.isSome)
|
|
115
|
+
? Optional.some(Arr.map(parentsValues, (c) => c.value))
|
|
116
|
+
: Optional.none;
|
|
117
|
+
|
|
118
|
+
super({
|
|
111
119
|
parents,
|
|
112
120
|
initialValue,
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.attach();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override tryUpdate(updateToken: UpdateToken): void {
|
|
127
|
+
const parents = this.parents;
|
|
128
|
+
|
|
129
|
+
if (parents.every((o) => o.updateToken !== updateToken)) {
|
|
130
|
+
return; // all parents are skipped
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Same shape-preserving chain as the initial value: `Arr.map` (not the
|
|
134
|
+
// native one, which drops the tuple shape) into the uniform
|
|
135
|
+
// `Tuple.MapTo`, then a positional annotation to get back to `A`.
|
|
136
|
+
const parentValues: Tuple.MapTo<Optional<A[number]>, A> = Arr.map(
|
|
137
|
+
parents,
|
|
138
|
+
(a) => a.getSnapshot(),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!Arr.every(parentValues, Optional.isSome)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const nextValue: Readonly<{ [K in keyof A]: A[K] }> = Arr.map(
|
|
146
|
+
parentValues,
|
|
147
|
+
(a) => a.value,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
this.setNext(nextValue, updateToken);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
141
153
|
|
|
142
154
|
if (import.meta.vitest !== undefined) {
|
|
143
155
|
test('type test', () => {
|
|
@@ -1,6 +1,12 @@
|
|
|
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
|
+
// so those rules are waived here (D-63).
|
|
4
|
+
// @sumi-expect-error-file classes/no-class
|
|
5
|
+
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
|
+
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
1
7
|
import { Optional, expectType } from 'ts-data-forge';
|
|
2
8
|
import type { ArrayElement, NonEmptyTuple } from 'ts-type-forge';
|
|
3
|
-
import {
|
|
9
|
+
import { SyncChildObservableClass } from '../class/index.mjs';
|
|
4
10
|
import { source } from '../create/index.mjs';
|
|
5
11
|
import type {
|
|
6
12
|
MergeObservable,
|
|
@@ -8,6 +14,7 @@ import type {
|
|
|
8
14
|
NonEmptyUnknownList,
|
|
9
15
|
Observable,
|
|
10
16
|
SyncChildObservable,
|
|
17
|
+
UpdateToken,
|
|
11
18
|
Wrap,
|
|
12
19
|
} from '../types/index.mjs';
|
|
13
20
|
|
|
@@ -58,42 +65,42 @@ import type {
|
|
|
58
65
|
*
|
|
59
66
|
* assert.deepStrictEqual(valueHistory, ['c1', 'k1', 'c2', 'k2']);
|
|
60
67
|
* ```
|
|
61
|
-
*
|
|
62
|
-
* @note To improve code readability, consider using `createState` instead of `merge`,
|
|
63
|
-
* subscribing to `parents` and calling `setState` within it.
|
|
64
68
|
*/
|
|
65
69
|
export const merge = <const OS extends NonEmptyTuple<Observable<unknown>>>(
|
|
66
70
|
parents: OS,
|
|
67
71
|
): MergeObservableRefined<OS> =>
|
|
68
72
|
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
69
|
-
|
|
73
|
+
new MergeObservableClass(parents) as MergeObservableRefined<OS>;
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
class MergeObservableClass<const P extends NonEmptyUnknownList>
|
|
76
|
+
extends SyncChildObservableClass<ArrayElement<P>, P>
|
|
77
|
+
implements MergeObservable<P>
|
|
78
|
+
{
|
|
79
|
+
constructor(parents: Wrap<P>) {
|
|
80
|
+
super({
|
|
76
81
|
parents,
|
|
77
82
|
initialValue: Optional.none,
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
(o) =>
|
|
83
|
-
o.updateToken === updateToken && Optional.isSome(o.getSnapshot()),
|
|
84
|
-
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
this.attach();
|
|
86
|
+
}
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
override tryUpdate(updateToken: UpdateToken): void {
|
|
89
|
+
const parentToUse = this.parents.find(
|
|
90
|
+
(o) => o.updateToken === updateToken && Optional.isSome(o.getSnapshot()),
|
|
91
|
+
);
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
if (parentToUse === undefined) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
const nextValue =
|
|
98
|
+
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
99
|
+
Optional.unwrap(parentToUse.getSnapshot()) as ArrayElement<P>;
|
|
100
|
+
|
|
101
|
+
this.setNext(nextValue, updateToken);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
97
104
|
|
|
98
105
|
if (import.meta.vitest !== undefined) {
|
|
99
106
|
test('type test', () => {
|
package/src/core/combine/zip.mts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
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
|
+
// so those rules are waived here (D-63).
|
|
4
|
+
// @sumi-expect-error-file classes/no-class
|
|
5
|
+
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
|
+
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
7
|
+
// @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
|
|
1
8
|
import { Arr, Optional, createQueue, expectType } from 'ts-data-forge';
|
|
2
9
|
import type { NonEmptyTuple, Tuple } from 'ts-type-forge';
|
|
3
|
-
import {
|
|
10
|
+
import { SyncChildObservableClass } from '../class/index.mjs';
|
|
4
11
|
import { source } from '../create/index.mjs';
|
|
5
12
|
import { withInitialValue } from '../operators/index.mjs';
|
|
6
13
|
import type {
|
|
@@ -10,6 +17,7 @@ import type {
|
|
|
10
17
|
Observable,
|
|
11
18
|
SyncChildObservable,
|
|
12
19
|
TupleToQueueTuple,
|
|
20
|
+
UpdateToken,
|
|
13
21
|
Wrap,
|
|
14
22
|
ZipObservable,
|
|
15
23
|
ZipObservableRefined,
|
|
@@ -18,7 +26,9 @@ import type {
|
|
|
18
26
|
/**
|
|
19
27
|
* Combines multiple observables by pairing their emissions by index.
|
|
20
28
|
* Waits for all sources to emit their nth value before emitting the nth tuple.
|
|
21
|
-
* Completes
|
|
29
|
+
* Completes once a completed source has no value left to pair, as RxJS's
|
|
30
|
+
* `zip` does: at once if it completes with nothing queued, otherwise after
|
|
31
|
+
* the tuple that takes its last queued value.
|
|
22
32
|
*
|
|
23
33
|
* @template OS - Tuple type of source observables
|
|
24
34
|
* @param parents - Array of observables to zip
|
|
@@ -35,7 +45,7 @@ import type {
|
|
|
35
45
|
* // Explanation:
|
|
36
46
|
* // - zip pairs values by their index from multiple sources
|
|
37
47
|
* // - Waits for all sources to emit at the same index
|
|
38
|
-
* // - Completes
|
|
48
|
+
* // - Completes once a completed source has no value left to pair
|
|
39
49
|
*
|
|
40
50
|
* const [letters$, setLetter] = createState<string>('A');
|
|
41
51
|
*
|
|
@@ -68,62 +78,97 @@ export const zip = <const OS extends NonEmptyTuple<Observable<unknown>>>(
|
|
|
68
78
|
parents: OS,
|
|
69
79
|
): ZipObservableRefined<OS> =>
|
|
70
80
|
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
parentsValues,
|
|
91
|
-
Optional.isSome,
|
|
92
|
-
)
|
|
93
|
-
? Optional.some(Arr.map(parentsValues, (c) => c.value))
|
|
94
|
-
: Optional.none;
|
|
95
|
-
|
|
96
|
-
const queues: TupleToQueueTuple<A> = Arr.map(parents, () => createQueue());
|
|
97
|
-
|
|
98
|
-
return createSyncChildObservable<A, A>(
|
|
99
|
-
{
|
|
81
|
+
new ZipObservableClass(parents) as unknown as ZipObservableRefined<OS>;
|
|
82
|
+
|
|
83
|
+
class ZipObservableClass<const A extends NonEmptyUnknownList>
|
|
84
|
+
extends SyncChildObservableClass<A, A>
|
|
85
|
+
implements ZipObservable<A>
|
|
86
|
+
{
|
|
87
|
+
readonly #queues: TupleToQueueTuple<A>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The token of each parent's latest value that zip has taken in, so that a
|
|
91
|
+
* value a parent emits in the update it completes in — its `tryComplete`
|
|
92
|
+
* reaches zip before its `tryUpdate` does — still counts as queued.
|
|
93
|
+
* Written in place, one slot per parent, on every update that reaches zip.
|
|
94
|
+
*/
|
|
95
|
+
// @sumi-expect-error readonly/require-readonly-type
|
|
96
|
+
readonly #mut_takenTokens: UpdateToken[];
|
|
97
|
+
|
|
98
|
+
constructor(parents: Wrap<A>) {
|
|
99
|
+
const parentsValues: Tuple.MapTo<Optional<A[number]>, A> = Arr.map(
|
|
100
100
|
parents,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
101
|
+
(p) => p.getSnapshot(),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// The annotation on `initialValue` is what replaces the type assertion.
|
|
105
|
+
// `Arr.map` carries a single result element type, so it reports `A[number]`
|
|
106
|
+
// at every position, and `Tuple.MapTo<A[number], A>` — the uniform mapping
|
|
107
|
+
// — is *not* assignable back to `A`. The positional
|
|
108
|
+
// `Readonly<{ [K in keyof A]: A[K] }>` is, and TypeScript accepts the
|
|
109
|
+
// uniform result into it because the two mapped types share `keyof A`. So
|
|
110
|
+
// it is the annotation, not an assertion, that records the position-wise
|
|
111
|
+
// fact the checker cannot derive on its own.
|
|
112
|
+
const initialValue: Optional<Readonly<{ [K in keyof A]: A[K] }>> =
|
|
113
|
+
Arr.every(parentsValues, Optional.isSome)
|
|
114
|
+
? Optional.some(Arr.map(parentsValues, (c) => c.value))
|
|
115
|
+
: Optional.none;
|
|
116
|
+
|
|
117
|
+
super({ parents, initialValue });
|
|
118
|
+
|
|
119
|
+
this.#queues = Arr.map(parents, () => createQueue());
|
|
120
|
+
|
|
121
|
+
this.#mut_takenTokens = Array.from(parents, (p) => p.updateToken);
|
|
122
|
+
|
|
123
|
+
this.attach();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override tryUpdate(updateToken: UpdateToken): void {
|
|
127
|
+
const queues = this.#queues;
|
|
128
|
+
|
|
129
|
+
for (const [index, par] of this.parents.entries()) {
|
|
130
|
+
const sn = par.getSnapshot();
|
|
131
|
+
|
|
132
|
+
if (!(par.updateToken === updateToken && Optional.isSome(sn))) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
queues[index]?.enqueue(sn.value);
|
|
137
|
+
|
|
138
|
+
this.#mut_takenTokens[index] = updateToken;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (queues.some((list) => list.isEmpty)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const nextValue =
|
|
146
|
+
// `Arr.map` has a single result element type, so it reports
|
|
147
|
+
// `A[number] | undefined` per position rather than `A[P]`.
|
|
148
|
+
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
149
|
+
Arr.map(queues, (q) => Optional.unwrap(q.dequeue())) as A;
|
|
150
|
+
|
|
151
|
+
this.setNext(nextValue, updateToken);
|
|
152
|
+
|
|
153
|
+
if (this.canEmitNoMore()) {
|
|
154
|
+
this.complete();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* No tuple can be completed any more once a completed source has nothing
|
|
160
|
+
* left to pair (#2343).
|
|
161
|
+
*/
|
|
162
|
+
override canEmitNoMore(): boolean {
|
|
163
|
+
return this.parents.some(
|
|
164
|
+
(par, index) =>
|
|
165
|
+
par.isCompleted &&
|
|
166
|
+
this.#queues[index]?.isEmpty === true &&
|
|
167
|
+
(par.updateToken === this.#mut_takenTokens[index] ||
|
|
168
|
+
Optional.isNone(par.getSnapshot())),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
127
172
|
|
|
128
173
|
if (import.meta.vitest !== undefined) {
|
|
129
174
|
test('type test', () => {
|