synstate 3.4.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 +13 -8
- 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.map +1 -1
- package/dist/core/combine/merge.mjs +26 -14
- package/dist/core/combine/merge.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts.map +1 -1
- package/dist/core/combine/zip.mjs +43 -25
- package/dist/core/combine/zip.mjs.map +1 -1
- package/dist/core/create/counter.d.mts.map +1 -1
- package/dist/core/create/counter.mjs +65 -44
- 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 +37 -24
- 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.map +1 -1
- package/dist/core/create/source.mjs +27 -17
- 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 -0
- package/dist/core/operators/merge-map.d.mts.map +1 -1
- package/dist/core/operators/merge-map.mjs +52 -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 +40 -26
- 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 -0
- package/dist/core/operators/switch-map.d.mts.map +1 -1
- package/dist/core/operators/switch-map.mjs +56 -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 +36 -29
- 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 +43 -39
- 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 +1 -0
- package/dist/core/types/observable-family.d.mts.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-reducer.d.mts +7 -7
- package/dist/utils/create-reducer.mjs +2 -2
- package/dist/utils/create-reducer.mjs.map +1 -1
- package/dist/utils/create-state.d.mts +2 -2
- 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/{base → class}/write-during-propagation.test.mts +3 -3
- package/src/core/combine/combine.mts +67 -55
- package/src/core/combine/merge.mts +33 -23
- package/src/core/combine/zip.mts +95 -81
- package/src/core/create/counter.mts +95 -65
- 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 +62 -45
- package/src/core/create/from-subscribable.test.mts +8 -3
- package/src/core/create/just.mts +4 -5
- package/src/core/create/source.mts +29 -20
- 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 -43
- 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 +55 -41
- package/src/core/operators/skip-while.mts +49 -31
- package/src/core/operators/switch-map.mts +74 -35
- package/src/core/operators/take-until-skip-until-teardown.test.mts +18 -12
- package/src/core/operators/take-until.mts +45 -41
- 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 +42 -44
- package/src/core/operators/with-buffered-from.test.mts +4 -3
- 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 +2 -0
- 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-reducer.mts +9 -9
- package/src/utils/create-reducer.test.mts +31 -14
- package/src/utils/create-state.mts +2 -2
- package/src/utils/create-state.test.mts +12 -14
- package/dist/core/base/create-child-observable.d.mts +0 -72
- package/dist/core/base/create-child-observable.d.mts.map +0 -1
- package/dist/core/base/create-child-observable.mjs +0 -285
- package/dist/core/base/create-child-observable.mjs.map +0 -1
- package/dist/core/base/create-observable-base.d.mts +0 -149
- package/dist/core/base/create-observable-base.d.mts.map +0 -1
- package/dist/core/base/create-observable-base.mjs +0 -273
- package/dist/core/base/create-observable-base.mjs.map +0 -1
- package/dist/core/base/create-root-observable.d.mts +0 -46
- package/dist/core/base/create-root-observable.d.mts.map +0 -1
- package/dist/core/base/create-root-observable.mjs +0 -80
- 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 -485
- package/src/core/base/create-observable-base.mts +0 -568
- package/src/core/base/create-root-observable.mts +0 -147
- 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,61 @@
|
|
|
1
|
+
import type { Scope } from '../types/index.mjs';
|
|
2
|
+
|
|
3
|
+
/** What a scope owns: an observable, as far as the scope needs to know it. */
|
|
4
|
+
export type ScopeMember = Readonly<{
|
|
5
|
+
isCompleted: boolean;
|
|
6
|
+
dispose: () => void;
|
|
7
|
+
}>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The side of a scope the rest of the core talks to, on top of its public
|
|
11
|
+
* face {@link Scope}. Kept out of the public API (`core/utils/**` is excluded
|
|
12
|
+
* from index generation), so that only `createScope` and its siblings decide
|
|
13
|
+
* what a scope owns.
|
|
14
|
+
*/
|
|
15
|
+
export type ScopeInternals = Scope &
|
|
16
|
+
Readonly<{
|
|
17
|
+
/** Takes `node` into the scope. */
|
|
18
|
+
adopt: (node: ScopeMember) => void;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Tells the scope that one of the nodes it adopted completed on its own,
|
|
22
|
+
* so that the scope can drop it and keep no reference to it. The node is
|
|
23
|
+
* completed (`isCompleted`) by the time it calls this.
|
|
24
|
+
*/
|
|
25
|
+
release: () => void;
|
|
26
|
+
|
|
27
|
+
addCleanup: (cleanup: () => void) => void;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The scope whose `run` is executing. A field rather than a module-level
|
|
32
|
+
* `let`, so that the one place writing it is visibly this object.
|
|
33
|
+
*/
|
|
34
|
+
const mut_slot: { current: ScopeInternals | undefined } = {
|
|
35
|
+
current: undefined,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const getCurrentScopeInternals = (): ScopeInternals | undefined =>
|
|
39
|
+
mut_slot.current;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Calls `body` with `scope` as the current scope (`undefined`: none) and
|
|
43
|
+
* restores the previous one afterwards, also when `body` throws.
|
|
44
|
+
*/
|
|
45
|
+
export const runWithScope = <R,>(
|
|
46
|
+
scope: ScopeInternals | undefined,
|
|
47
|
+
body: () => R,
|
|
48
|
+
): R => {
|
|
49
|
+
const prev = mut_slot.current;
|
|
50
|
+
|
|
51
|
+
mut_slot.current = scope;
|
|
52
|
+
|
|
53
|
+
// `finally` is the point: an exception from `body` must not leave its scope
|
|
54
|
+
// current for whatever runs next. Nothing is caught.
|
|
55
|
+
// @sumi-expect-error exceptions/no-try
|
|
56
|
+
try {
|
|
57
|
+
return body();
|
|
58
|
+
} finally {
|
|
59
|
+
mut_slot.current = prev;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -1,31 +1,21 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
while (true) {
|
|
11
|
-
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
12
|
-
yield Symbol(mut_i.toString()) as T;
|
|
13
|
-
|
|
14
|
-
mut_i += 1;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const observableIdMaker = idMaker<ObservableId>();
|
|
19
|
-
|
|
20
|
-
const subscriberIdMaker = idMaker<SubscriberId>();
|
|
21
|
-
|
|
22
|
-
const updateTokenMaker = idMaker<UpdateToken>();
|
|
23
|
-
|
|
24
|
-
export const issueObservableId = (): ObservableId =>
|
|
25
|
-
observableIdMaker.next().value;
|
|
26
|
-
|
|
27
|
-
export const issueSubscriberId = (): SubscriberId =>
|
|
28
|
-
subscriberIdMaker.next().value;
|
|
29
|
-
|
|
1
|
+
import type { UpdateToken } from '../types/index.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The token of a new update, which the update's root or async child passes to
|
|
5
|
+
* every node it reaches. A `Symbol` with no description, issued once per
|
|
6
|
+
* update and never per node, so its cost is spread over every node the
|
|
7
|
+
* update reaches. Unique without any counter, so the tokens of two copies of
|
|
8
|
+
* synstate in one program never coincide, and nothing is kept in a global.
|
|
9
|
+
*/
|
|
30
10
|
export const issueUpdateToken = (): UpdateToken =>
|
|
31
|
-
|
|
11
|
+
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
12
|
+
Symbol() as UpdateToken;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The token a node holds before its first update: one value shared by every
|
|
16
|
+
* node of this copy, since no update is ever issued it, so creating a node
|
|
17
|
+
* issues nothing.
|
|
18
|
+
*/
|
|
19
|
+
export const notUpdatedYet: UpdateToken =
|
|
20
|
+
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
21
|
+
Symbol('synstate/not-updated-yet') as UpdateToken;
|
package/src/core/utils/index.mts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { Arr, Optional, pipe } from 'ts-data-forge';
|
|
2
1
|
import type { Observable } from '../types/index.mjs';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
/** The greatest `depth` among `parents`, or 0 when there are none. */
|
|
4
|
+
export const maxDepth = (parents: readonly Observable<unknown>[]): number => {
|
|
5
|
+
let mut_max = 0;
|
|
6
|
+
|
|
7
|
+
for (const p of parents) {
|
|
8
|
+
if (p.depth > mut_max) {
|
|
9
|
+
mut_max = p.depth;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return mut_max;
|
|
14
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
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
|
+
* a `subscribe` method is not enough, since plain values may have one — and,
|
|
5
|
+
* as a type, makes the public observable types impossible to satisfy with an
|
|
6
|
+
* object literal: an imitation passed to an operator would lack the internal
|
|
7
|
+
* members propagation reads and fail at run time instead of at compile time.
|
|
8
|
+
*
|
|
9
|
+
* `Symbol.for`, so that an observable made by another copy of synstate in the
|
|
10
|
+
* same program is still recognized.
|
|
11
|
+
*/
|
|
12
|
+
export const observableBrand: unique symbol = Symbol.for('synstate/observable');
|
|
@@ -7,8 +7,8 @@ import { createState } from './create-state.mjs';
|
|
|
7
7
|
* Extends `createState` with boolean-specific helpers like `toggle`, `setTrue`, and `setFalse`.
|
|
8
8
|
*
|
|
9
9
|
* @param initialState - The initial boolean value
|
|
10
|
-
* @param options - {@link StateOptions}: `equals
|
|
11
|
-
* updates that change the state
|
|
10
|
+
* @param options - {@link StateOptions}: `equals` (default `Object.is`),
|
|
11
|
+
* to pass on only the updates that change the state
|
|
12
12
|
* @returns A 2-element tuple: `[state, { setTrue, setFalse, toggle, setState, updateState, resetState, getSnapshot, initialState }]`
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { createBooleanState } from './create-boolean-state.mjs';
|
|
2
2
|
|
|
3
3
|
describe(createBooleanState, () => {
|
|
4
|
-
test('
|
|
5
|
-
const [state, { setTrue, setFalse }] = createBooleanState(false
|
|
6
|
-
equals: Object.is,
|
|
7
|
-
});
|
|
4
|
+
test('setting what it already holds passes nothing on when no `equals` is given', () => {
|
|
5
|
+
const [state, { setTrue, setFalse }] = createBooleanState(false);
|
|
8
6
|
|
|
9
7
|
const mut_seen: boolean[] = [];
|
|
10
8
|
|
|
@@ -20,4 +18,20 @@ describe(createBooleanState, () => {
|
|
|
20
18
|
|
|
21
19
|
assert.deepStrictEqual(mut_seen, [false, true]);
|
|
22
20
|
});
|
|
21
|
+
|
|
22
|
+
test('passes on every update, equal or not, with `equals: () => false`', () => {
|
|
23
|
+
const [state, { setTrue }] = createBooleanState(true, {
|
|
24
|
+
equals: () => false,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const mut_seen: boolean[] = [];
|
|
28
|
+
|
|
29
|
+
state.subscribe((value) => {
|
|
30
|
+
mut_seen.push(value);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
setTrue();
|
|
34
|
+
|
|
35
|
+
assert.deepStrictEqual(mut_seen, [true, true]);
|
|
36
|
+
});
|
|
23
37
|
});
|
|
@@ -9,8 +9,8 @@ import { source, type InitializedObservable } from '../core/index.mjs';
|
|
|
9
9
|
* @template A - The type of actions
|
|
10
10
|
* @param reducer - A pure function that takes current state and action, returns new state
|
|
11
11
|
* @param initialState - The initial value of the state
|
|
12
|
-
* @param options - {@link StateOptions}: `equals
|
|
13
|
-
* dispatches that change the state
|
|
12
|
+
* @param options - {@link StateOptions}: `equals` (default `Object.is`),
|
|
13
|
+
* to pass on only the dispatches that change the state
|
|
14
14
|
* @returns A 3-element tuple: `[state, dispatch, { getSnapshot, initialState }]`.
|
|
15
15
|
* `dispatch` returns the state it leaves; once `state` is completed, that
|
|
16
16
|
* is the state held.
|
|
@@ -54,7 +54,7 @@ import { source, type InitializedObservable } from '../core/index.mjs';
|
|
|
54
54
|
* (s, action: Readonly<{ type: 'add'; amount: number }>) =>
|
|
55
55
|
* s + action.amount,
|
|
56
56
|
* 0,
|
|
57
|
-
* { equals:
|
|
57
|
+
* { equals: () => false },
|
|
58
58
|
* );
|
|
59
59
|
*
|
|
60
60
|
* const countHistory: number[] = [];
|
|
@@ -63,11 +63,11 @@ import { source, type InitializedObservable } from '../core/index.mjs';
|
|
|
63
63
|
* countHistory.push(value);
|
|
64
64
|
* });
|
|
65
65
|
*
|
|
66
|
-
* dispatch({ type: 'add', amount: 0 }); // 0 again
|
|
66
|
+
* dispatch({ type: 'add', amount: 0 }); // 0 again, passed on all the same
|
|
67
67
|
*
|
|
68
68
|
* dispatch({ type: 'add', amount: 2 });
|
|
69
69
|
*
|
|
70
|
-
* assert.deepStrictEqual(countHistory, [0, 2]);
|
|
70
|
+
* assert.deepStrictEqual(countHistory, [0, 0, 2]);
|
|
71
71
|
* ```
|
|
72
72
|
*
|
|
73
73
|
* @note Do not call `dispatch` from a subscriber or a projection while another
|
|
@@ -91,7 +91,7 @@ export const createReducer = <S, A>(
|
|
|
91
91
|
] => {
|
|
92
92
|
const state = source<S>(initialState);
|
|
93
93
|
|
|
94
|
-
const equals = options?.equals;
|
|
94
|
+
const equals = options?.equals ?? Object.is;
|
|
95
95
|
|
|
96
96
|
const dispatch = (action: A): S => {
|
|
97
97
|
const currentState = state.getSnapshot().value;
|
|
@@ -99,7 +99,7 @@ export const createReducer = <S, A>(
|
|
|
99
99
|
const nextState = reducer(currentState, action);
|
|
100
100
|
|
|
101
101
|
// Once completed, `next` keeps the state held and returns it.
|
|
102
|
-
return equals
|
|
102
|
+
return equals(currentState, nextState)
|
|
103
103
|
? currentState
|
|
104
104
|
: state.next(nextState);
|
|
105
105
|
};
|
|
@@ -124,8 +124,8 @@ export type StateOptions<S> = Readonly<{
|
|
|
124
124
|
* `skipIfNoChange`: an update passed on here reaches whatever reads the
|
|
125
125
|
* state directly before any operator can thin it out.
|
|
126
126
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
127
|
+
* Defaults to `Object.is`, so writing the value already held passes
|
|
128
|
+
* nothing on. Pass `() => false` to pass on every update, equal or not.
|
|
129
129
|
*
|
|
130
130
|
* @param current - The state held now
|
|
131
131
|
* @param next - The state the update would set
|
|
@@ -2,34 +2,51 @@ import type { FixedLengthTuple } from 'ts-type-forge';
|
|
|
2
2
|
import { createReducer } from './create-reducer.mjs';
|
|
3
3
|
|
|
4
4
|
describe(createReducer, () => {
|
|
5
|
-
test('
|
|
6
|
-
const [state, dispatch] = createReducer(keepOrAdd, 0);
|
|
5
|
+
test('does not pass on a dispatch whose next state is `Object.is`-equal to the current one when no `equals` is given', () => {
|
|
6
|
+
const [state, dispatch, { getSnapshot }] = createReducer(keepOrAdd, 0);
|
|
7
7
|
|
|
8
8
|
const seen = record(state);
|
|
9
9
|
|
|
10
|
-
dispatch({ type: 'keep' });
|
|
10
|
+
assert.strictEqual(dispatch({ type: 'keep' }), 0);
|
|
11
11
|
|
|
12
|
-
dispatch({ type: 'add', amount: 1 });
|
|
12
|
+
assert.strictEqual(dispatch({ type: 'add', amount: 1 }), 1);
|
|
13
13
|
|
|
14
|
-
assert.
|
|
14
|
+
assert.strictEqual(dispatch({ type: 'add', amount: 0 }), 1);
|
|
15
|
+
|
|
16
|
+
assert.deepStrictEqual(seen, [0, 1]);
|
|
17
|
+
|
|
18
|
+
assert.strictEqual(getSnapshot(), 1);
|
|
15
19
|
});
|
|
16
20
|
|
|
17
|
-
test('with `
|
|
18
|
-
const [state, dispatch
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
test('compares with `Object.is` by default, so `NaN` equals itself and `-0` differs from `0`', () => {
|
|
22
|
+
const [state, dispatch] = createReducer(
|
|
23
|
+
(_current: number, next: number) => next,
|
|
24
|
+
0,
|
|
25
|
+
);
|
|
21
26
|
|
|
22
27
|
const seen = record(state);
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
dispatch(-0);
|
|
25
30
|
|
|
26
|
-
|
|
31
|
+
dispatch(Number.NaN);
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
dispatch(Number.NaN);
|
|
29
34
|
|
|
30
|
-
assert.deepStrictEqual(seen, [0,
|
|
35
|
+
assert.deepStrictEqual(seen, [0, -0, Number.NaN]);
|
|
36
|
+
});
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
test('passes on every dispatch, equal or not, with `equals: () => false`', () => {
|
|
39
|
+
const [state, dispatch] = createReducer(keepOrAdd, 0, {
|
|
40
|
+
equals: () => false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const seen = record(state);
|
|
44
|
+
|
|
45
|
+
dispatch({ type: 'keep' });
|
|
46
|
+
|
|
47
|
+
dispatch({ type: 'add', amount: 1 });
|
|
48
|
+
|
|
49
|
+
assert.deepStrictEqual(seen, [0, 0, 1]);
|
|
33
50
|
});
|
|
34
51
|
|
|
35
52
|
test('keeps the current object when `equals` finds the next one equal to it', () => {
|
|
@@ -28,8 +28,8 @@ const reducer = <S,>(state: S, action: Action<S>): S => {
|
|
|
28
28
|
*
|
|
29
29
|
* @template S - The type of the state
|
|
30
30
|
* @param initialState - The initial value of the state
|
|
31
|
-
* @param options - {@link StateOptions}: `equals
|
|
32
|
-
* updates that change the state
|
|
31
|
+
* @param options - {@link StateOptions}: `equals` (default `Object.is`),
|
|
32
|
+
* to pass on only the updates that change the state
|
|
33
33
|
* @returns A 3-element tuple: `[state, setState, { updateState, resetState, getSnapshot, initialState }]`
|
|
34
34
|
*
|
|
35
35
|
* @example
|
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
import { createState } from './create-state.mjs';
|
|
2
2
|
|
|
3
3
|
describe(createState, () => {
|
|
4
|
-
test('
|
|
5
|
-
const [state, setState] = createState(0);
|
|
6
|
-
|
|
7
|
-
const seen = record(state);
|
|
8
|
-
|
|
9
|
-
setState(0);
|
|
10
|
-
|
|
11
|
-
assert.deepStrictEqual(seen, [0, 0]);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test('with `equals`, setState, updateState and resetState do not pass on a value equal to the current one', () => {
|
|
15
|
-
const [state, setState, { updateState, resetState }] = createState(0, {
|
|
16
|
-
equals: Object.is,
|
|
17
|
-
});
|
|
4
|
+
test('setState, updateState and resetState do not pass on a value `Object.is`-equal to the current one when no `equals` is given', () => {
|
|
5
|
+
const [state, setState, { updateState, resetState }] = createState(0);
|
|
18
6
|
|
|
19
7
|
const seen = record(state);
|
|
20
8
|
|
|
@@ -54,6 +42,16 @@ describe(createState, () => {
|
|
|
54
42
|
assert.strictEqual(getSnapshot(), 1);
|
|
55
43
|
});
|
|
56
44
|
|
|
45
|
+
test('passes on every update, equal or not, with `equals: () => false`', () => {
|
|
46
|
+
const [state, setState] = createState(0, { equals: () => false });
|
|
47
|
+
|
|
48
|
+
const seen = record(state);
|
|
49
|
+
|
|
50
|
+
setState(0);
|
|
51
|
+
|
|
52
|
+
assert.deepStrictEqual(seen, [0, 0]);
|
|
53
|
+
});
|
|
54
|
+
|
|
57
55
|
// What `equals` is for: whatever reads the state, directly or through
|
|
58
56
|
// anything derived from it, is told only of a real change, and an equal
|
|
59
57
|
// value written over the current one leaves the object that is there.
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { type Optional, type Some } from 'ts-data-forge';
|
|
2
|
-
import { type AsyncChildObservable, type InitializedSyncChildObservable, type NonEmptyUnknownList, type SyncChildObservable, type UpdateToken, type Wrap } from '../types/index.mjs';
|
|
3
|
-
/**
|
|
4
|
-
* The internal tools a sync-child leaf factory (`map`, `filter`, `zip`, …)
|
|
5
|
-
* receives — the replacement for the `protected` methods a subclass of
|
|
6
|
-
* `SyncChildObservableClass` used to inherit.
|
|
7
|
-
*/
|
|
8
|
-
export type SyncChildObservableTools<A> = Readonly<{
|
|
9
|
-
/** Emits `nextValue` under `updateToken` and notifies subscribers. */
|
|
10
|
-
setNext: (nextValue: A, updateToken: UpdateToken) => void;
|
|
11
|
-
getSnapshot: () => Optional<A>;
|
|
12
|
-
/** Completes this observable (leaf teardown included). */
|
|
13
|
-
complete: () => void;
|
|
14
|
-
}>;
|
|
15
|
-
/**
|
|
16
|
-
* The internal tools an async-child leaf factory (`debounce`, `switchMap`, …)
|
|
17
|
-
* receives. Async children additionally start their own update propagation.
|
|
18
|
-
*/
|
|
19
|
-
export type AsyncChildObservableTools<A> = Readonly<{
|
|
20
|
-
/** Emits `nextValue` and propagates the update to all descendants. */
|
|
21
|
-
startUpdate: (nextValue: A) => void;
|
|
22
|
-
}> & SyncChildObservableTools<A>;
|
|
23
|
-
type ChildObservableConfigBase<P extends NonEmptyUnknownList> = Readonly<{
|
|
24
|
-
parents: Wrap<P>;
|
|
25
|
-
depth?: number;
|
|
26
|
-
/**
|
|
27
|
-
* Leaf teardown (clear timers, unsubscribe inner observables, …), run at
|
|
28
|
-
* the beginning of `complete()` — the closure replacement for
|
|
29
|
-
* `override complete() { teardown(); super.complete(); }`.
|
|
30
|
-
*/
|
|
31
|
-
onComplete?: () => void;
|
|
32
|
-
/**
|
|
33
|
-
* Whether this observable can emit nothing more although some parent is
|
|
34
|
-
* still live, asked whenever a parent completes. `true` completes it then,
|
|
35
|
-
* as all parents completing does (`zip` once a completed source's queue is
|
|
36
|
-
* empty).
|
|
37
|
-
*/
|
|
38
|
-
canEmitNoMore?: () => boolean;
|
|
39
|
-
}>;
|
|
40
|
-
export type ChildObservableConfig<A, P extends NonEmptyUnknownList> = ChildObservableConfigBase<P> & Readonly<{
|
|
41
|
-
initialValue: Optional<A>;
|
|
42
|
-
}>;
|
|
43
|
-
export type InitializedChildObservableConfig<A, P extends NonEmptyUnknownList> = ChildObservableConfigBase<P> & Readonly<{
|
|
44
|
-
initialValue: Some<A>;
|
|
45
|
-
}>;
|
|
46
|
-
/**
|
|
47
|
-
* Creates a sync-child observable — the classless replacement for extending
|
|
48
|
-
* `SyncChildObservableClass`.
|
|
49
|
-
*
|
|
50
|
-
* `createTryUpdate` receives the internal tools and returns the `tryUpdate`
|
|
51
|
-
* implementation (the former `override tryUpdate`). It runs during
|
|
52
|
-
* construction, before the observable is registered to its parents; leaf
|
|
53
|
-
* state can live either in the enclosing factory scope or inside the
|
|
54
|
-
* `createTryUpdate` closure.
|
|
55
|
-
*/
|
|
56
|
-
export declare const createSyncChildObservable: <A, P extends NonEmptyUnknownList>({ parents, depth, initialValue, onComplete, canEmitNoMore, }: ChildObservableConfig<A, P>, createTryUpdate: (tools: SyncChildObservableTools<A>) => (updateToken: UpdateToken) => void) => SyncChildObservable<A, P>;
|
|
57
|
-
/**
|
|
58
|
-
* Creates a sync-child observable that always holds a value.
|
|
59
|
-
*
|
|
60
|
-
* Runtime-wise this is exactly `createSyncChildObservable`; only the types of
|
|
61
|
-
* `getSnapshot` and `pipe` are narrowed, which is what the
|
|
62
|
-
* `InitializedSyncChildObservableClass` overrides existed for.
|
|
63
|
-
*/
|
|
64
|
-
export declare const createInitializedSyncChildObservable: <A, P extends NonEmptyUnknownList>(config: InitializedChildObservableConfig<A, P>, createTryUpdate: (tools: SyncChildObservableTools<A>) => (updateToken: UpdateToken) => void) => InitializedSyncChildObservable<A, P>;
|
|
65
|
-
/**
|
|
66
|
-
* Creates an async-child observable — the classless replacement for extending
|
|
67
|
-
* `AsyncChildObservableClass`. See {@link createSyncChildObservable} for the
|
|
68
|
-
* `createTryUpdate` contract.
|
|
69
|
-
*/
|
|
70
|
-
export declare const createAsyncChildObservable: <A, P extends NonEmptyUnknownList>({ parents, depth, initialValue, onComplete, canEmitNoMore, }: ChildObservableConfig<A, P>, createTryUpdate: (tools: AsyncChildObservableTools<A>) => (updateToken: UpdateToken) => void) => AsyncChildObservable<A, P>;
|
|
71
|
-
export {};
|
|
72
|
-
//# sourceMappingURL=create-child-observable.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"create-child-observable.d.mts","sourceRoot":"","sources":["../../../src/core/base/create-child-observable.mts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,QAAQ,EAAE,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,EAGL,KAAK,oBAAoB,EAEzB,KAAK,8BAA8B,EACnC,KAAK,mBAAmB,EAGxB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,IAAI,EACV,MAAM,oBAAoB,CAAC;AAQ5B;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,CAAC,CAAC,IAAI,QAAQ,CAAC;IACjD,sEAAsE;IACtE,OAAO,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,WAAW,KAAK,IAAI,CAAC;IAE1D,WAAW,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC;IAE/B,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI,QAAQ,CAAC;IAClD,sEAAsE;IACtE,WAAW,EAAE,CAAC,SAAS,EAAE,CAAC,KAAK,IAAI,CAAC;CACrC,CAAC,GACA,wBAAwB,CAAC,CAAC,CAAC,CAAC;AAE9B,KAAK,yBAAyB,CAAC,CAAC,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IACvE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAEjB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IAExB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC;CAC/B,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,CAC/B,CAAC,EACD,CAAC,SAAS,mBAAmB,IAC3B,yBAAyB,CAAC,CAAC,CAAC,GAC9B,QAAQ,CAAC;IACP,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;CAC3B,CAAC,CAAC;AAEL,MAAM,MAAM,gCAAgC,CAC1C,CAAC,EACD,CAAC,SAAS,mBAAmB,IAC3B,yBAAyB,CAAC,CAAC,CAAC,GAC9B,QAAQ,CAAC;IACP,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;CACvB,CAAC,CAAC;AAEL;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GAAI,CAAC,EAAE,CAAC,SAAS,mBAAmB,gEAOrE,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC,mBACb,CACf,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAC/B,CAAC,WAAW,EAAE,WAAW,KAAK,IAAI,KACtC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAoF1B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oCAAoC,GAC/C,CAAC,EACD,CAAC,SAAS,mBAAmB,UAErB,gCAAgC,CAAC,CAAC,EAAE,CAAC,CAAC,mBAC7B,CACf,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAC/B,CAAC,WAAW,EAAE,WAAW,KAAK,IAAI,KACtC,8BAA8B,CAAC,CAAC,EAAE,CAAC,CASrC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,GAAI,CAAC,EAAE,CAAC,SAAS,mBAAmB,gEAOtE,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC,mBACb,CACf,KAAK,EAAE,yBAAyB,CAAC,CAAC,CAAC,KAChC,CAAC,WAAW,EAAE,WAAW,KAAK,IAAI,KACtC,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAwF3B,CAAC"}
|