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,260 @@
|
|
|
1
|
+
// Sumi bans classes, `this`, method syntax and writes to `this` outside a
|
|
2
|
+
// `mut_` binding. A scope is a class for the reason the observables are (see
|
|
3
|
+
// `ScopeClass` below), so those four 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
|
|
8
|
+
import type { Scope } from '../types/index.mjs';
|
|
9
|
+
import {
|
|
10
|
+
getCurrentScopeInternals,
|
|
11
|
+
runWithScope,
|
|
12
|
+
type ScopeInternals,
|
|
13
|
+
type ScopeMember,
|
|
14
|
+
} from '../utils/index.mjs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a {@link Scope}. Observables created synchronously inside
|
|
18
|
+
* `scope.run(body)` belong to it, and `scope.dispose()` disposes them without
|
|
19
|
+
* completing the shared observables they were derived from.
|
|
20
|
+
*
|
|
21
|
+
* With `parent`, the new scope is disposed together with it; without one it
|
|
22
|
+
* stands alone and lives until its own `dispose()`. A parent that is already
|
|
23
|
+
* disposed gives a scope that is born disposed, with a warning.
|
|
24
|
+
*
|
|
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
|
+
* lives as long as the page.
|
|
28
|
+
*
|
|
29
|
+
* `run` and `dispose` read the scope they are called on, so call them on it
|
|
30
|
+
* (`scope.dispose()`) rather than taking them off it.
|
|
31
|
+
*
|
|
32
|
+
* @param parent - The scope whose disposal also disposes the new one
|
|
33
|
+
* @returns The new scope
|
|
34
|
+
*/
|
|
35
|
+
export const createScope = (parent?: Scope): Scope =>
|
|
36
|
+
new ScopeClass(parent instanceof ScopeClass ? parent : undefined);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns the scope whose `run` is executing, or `undefined` outside every
|
|
40
|
+
* `run` and inside {@link untracked}.
|
|
41
|
+
*
|
|
42
|
+
* To put work that runs later (after an `await`, in a callback) into the
|
|
43
|
+
* current scope, take it here while still synchronous and wrap the later work
|
|
44
|
+
* in its `run`.
|
|
45
|
+
*
|
|
46
|
+
* @returns The current scope, if any
|
|
47
|
+
*/
|
|
48
|
+
export const getCurrentScope = (): Scope | undefined =>
|
|
49
|
+
getCurrentScopeInternals();
|
|
50
|
+
|
|
51
|
+
/**
|
|
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
|
+
* are, newest first.
|
|
55
|
+
*
|
|
56
|
+
* Outside every scope it warns and registers nothing.
|
|
57
|
+
*
|
|
58
|
+
* @param cleanup - The function to call
|
|
59
|
+
*/
|
|
60
|
+
export const onScopeDispose = (cleanup: () => void): void => {
|
|
61
|
+
const current = getCurrentScopeInternals();
|
|
62
|
+
|
|
63
|
+
if (current === undefined) {
|
|
64
|
+
console.warn(
|
|
65
|
+
'synstate: onScopeDispose() was called outside any scope; the cleanup will never run.',
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
current.addCleanup(cleanup);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Calls `body` with no current scope and returns what it returns, so that the
|
|
76
|
+
* observables it creates belong to no scope and outlive the surrounding one.
|
|
77
|
+
*
|
|
78
|
+
* @param body - The function to call
|
|
79
|
+
* @returns What `body` returns
|
|
80
|
+
*/
|
|
81
|
+
export const untracked = <R,>(body: () => R): R =>
|
|
82
|
+
runWithScope(undefined, body);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A scope is created for every row and branch of a UI, so creating one has to
|
|
86
|
+
* be cheap. It used to be a set of closures with a `Set` of children, a `Set`
|
|
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
|
+
* first entry arrives.
|
|
90
|
+
*
|
|
91
|
+
* Whatever leaves a list on its own (a child scope disposed by itself, an
|
|
92
|
+
* observable that completed) is not looked up and spliced out: it only counts
|
|
93
|
+
* itself gone, and once at least half of a list is gone the list is rebuilt
|
|
94
|
+
* from what is left. Disposal walks the lists newest first, as before.
|
|
95
|
+
*
|
|
96
|
+
* The class itself is not exported; `createScope` is the way to make one.
|
|
97
|
+
*/
|
|
98
|
+
class ScopeClass implements ScopeInternals {
|
|
99
|
+
readonly #parent: ScopeClass | undefined;
|
|
100
|
+
// Pushed to in place: a scope gains children, cleanups and nodes one at a
|
|
101
|
+
// time, and copying the list for each would make a big scope quadratic.
|
|
102
|
+
// @sumi-expect-error readonly/require-readonly-type
|
|
103
|
+
#mut_children: ScopeClass[] | undefined;
|
|
104
|
+
#mut_goneChildren: number;
|
|
105
|
+
// @sumi-expect-error readonly/require-readonly-type
|
|
106
|
+
#mut_cleanups: (() => void)[] | undefined;
|
|
107
|
+
// @sumi-expect-error readonly/require-readonly-type
|
|
108
|
+
#mut_nodes: ScopeMember[] | undefined;
|
|
109
|
+
#mut_goneNodes: number;
|
|
110
|
+
#mut_isDisposed: boolean;
|
|
111
|
+
|
|
112
|
+
constructor(parent: ScopeClass | undefined) {
|
|
113
|
+
this.#parent = parent;
|
|
114
|
+
|
|
115
|
+
this.#mut_children = undefined;
|
|
116
|
+
|
|
117
|
+
this.#mut_goneChildren = 0;
|
|
118
|
+
|
|
119
|
+
this.#mut_cleanups = undefined;
|
|
120
|
+
|
|
121
|
+
this.#mut_nodes = undefined;
|
|
122
|
+
|
|
123
|
+
this.#mut_goneNodes = 0;
|
|
124
|
+
|
|
125
|
+
this.#mut_isDisposed = false;
|
|
126
|
+
|
|
127
|
+
if (parent !== undefined) {
|
|
128
|
+
if (parent.#mut_isDisposed) {
|
|
129
|
+
console.warn(
|
|
130
|
+
'synstate: createScope() was given a disposed parent; the new scope is born disposed.',
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
this.#mut_isDisposed = true;
|
|
134
|
+
} else if (parent.#mut_children === undefined) {
|
|
135
|
+
parent.#mut_children = [this];
|
|
136
|
+
} else {
|
|
137
|
+
parent.#mut_children.push(this);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Called by a child scope that was disposed on its own. */
|
|
143
|
+
#childGone(): void {
|
|
144
|
+
const children = this.#mut_children;
|
|
145
|
+
|
|
146
|
+
if (children === undefined) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.#mut_goneChildren += 1;
|
|
151
|
+
|
|
152
|
+
if (!isMostlyGone(this.#mut_goneChildren, children.length)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.#mut_children = children.filter((c) => !c.#mut_isDisposed);
|
|
157
|
+
|
|
158
|
+
this.#mut_goneChildren = 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
run<R>(body: () => R): R {
|
|
162
|
+
if (this.#mut_isDisposed) {
|
|
163
|
+
console.warn(
|
|
164
|
+
'synstate: run() was called on a disposed scope; what it creates belongs to no scope.',
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
return runWithScope(undefined, body);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return runWithScope(this, body);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
dispose(): void {
|
|
174
|
+
if (this.#mut_isDisposed) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Set first: the children, cleanups and nodes below call back into this
|
|
179
|
+
// scope (`childGone`, `release`), which has nothing to do once it is
|
|
180
|
+
// disposing everything anyway.
|
|
181
|
+
this.#mut_isDisposed = true;
|
|
182
|
+
|
|
183
|
+
const children = this.#mut_children;
|
|
184
|
+
|
|
185
|
+
if (children !== undefined) {
|
|
186
|
+
this.#mut_children = undefined;
|
|
187
|
+
|
|
188
|
+
for (let mut_i = children.length - 1; mut_i >= 0; mut_i -= 1) {
|
|
189
|
+
children[mut_i]?.dispose();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const cleanups = this.#mut_cleanups;
|
|
194
|
+
|
|
195
|
+
if (cleanups !== undefined) {
|
|
196
|
+
this.#mut_cleanups = undefined;
|
|
197
|
+
|
|
198
|
+
for (let mut_i = cleanups.length - 1; mut_i >= 0; mut_i -= 1) {
|
|
199
|
+
cleanups[mut_i]?.();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const nodes = this.#mut_nodes;
|
|
204
|
+
|
|
205
|
+
if (nodes !== undefined) {
|
|
206
|
+
this.#mut_nodes = undefined;
|
|
207
|
+
|
|
208
|
+
for (let mut_i = nodes.length - 1; mut_i >= 0; mut_i -= 1) {
|
|
209
|
+
nodes[mut_i]?.dispose();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (this.#parent !== undefined) {
|
|
214
|
+
this.#parent.#childGone();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
adopt(node: ScopeMember): void {
|
|
219
|
+
if (this.#mut_nodes === undefined) {
|
|
220
|
+
this.#mut_nodes = [node];
|
|
221
|
+
} else {
|
|
222
|
+
this.#mut_nodes.push(node);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
release(): void {
|
|
227
|
+
const nodes = this.#mut_nodes;
|
|
228
|
+
|
|
229
|
+
if (nodes === undefined) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
this.#mut_goneNodes += 1;
|
|
234
|
+
|
|
235
|
+
if (!isMostlyGone(this.#mut_goneNodes, nodes.length)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this.#mut_nodes = nodes.filter((n) => !n.isCompleted);
|
|
240
|
+
|
|
241
|
+
this.#mut_goneNodes = 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
addCleanup(cleanup: () => void): void {
|
|
245
|
+
if (this.#mut_cleanups === undefined) {
|
|
246
|
+
this.#mut_cleanups = [cleanup];
|
|
247
|
+
} else {
|
|
248
|
+
this.#mut_cleanups.push(cleanup);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Whether a list is worth rebuilding: at least half of it is gone, and enough
|
|
255
|
+
* of it that the rebuild is paid for by the entries it drops.
|
|
256
|
+
*/
|
|
257
|
+
const isMostlyGone = (gone: number, length: number): boolean =>
|
|
258
|
+
gone >= minGoneToCompact && gone * 2 >= length;
|
|
259
|
+
|
|
260
|
+
const minGoneToCompact = 16;
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// cspell:ignore unstub
|
|
2
|
+
import { expectType } from 'ts-data-forge';
|
|
3
|
+
import type { Mock } from 'vitest';
|
|
4
|
+
import { toNode } from '../class/index.mjs';
|
|
5
|
+
import { source } from '../create/index.mjs';
|
|
6
|
+
import { map } from '../operators/index.mjs';
|
|
7
|
+
import type { Scope } from '../types/index.mjs';
|
|
8
|
+
import {
|
|
9
|
+
createScope,
|
|
10
|
+
getCurrentScope,
|
|
11
|
+
onScopeDispose,
|
|
12
|
+
untracked,
|
|
13
|
+
} from './scope.mjs';
|
|
14
|
+
|
|
15
|
+
describe(createScope, () => {
|
|
16
|
+
test('types', () => {
|
|
17
|
+
expectType<ReturnType<typeof createScope>, Scope>('=');
|
|
18
|
+
|
|
19
|
+
expectType<ReturnType<typeof getCurrentScope>, Scope | undefined>('=');
|
|
20
|
+
|
|
21
|
+
expectType<ReturnType<typeof untracked<number>>, number>('=');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('run returns what fn returns and makes the scope current only while it runs', () => {
|
|
25
|
+
const scope = createScope();
|
|
26
|
+
|
|
27
|
+
assert.isUndefined(getCurrentScope());
|
|
28
|
+
|
|
29
|
+
const result = scope.run(() => {
|
|
30
|
+
assert.strictEqual(getCurrentScope(), scope);
|
|
31
|
+
|
|
32
|
+
return 42;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
assert.strictEqual(result, 42);
|
|
36
|
+
|
|
37
|
+
assert.isUndefined(getCurrentScope());
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('the outer scope is current again after a nested run, and after fn throws', () => {
|
|
41
|
+
const outer = createScope();
|
|
42
|
+
|
|
43
|
+
const inner = createScope(outer);
|
|
44
|
+
|
|
45
|
+
outer.run(() => {
|
|
46
|
+
inner.run(() => {
|
|
47
|
+
assert.strictEqual(getCurrentScope(), inner);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
assert.strictEqual(getCurrentScope(), outer);
|
|
51
|
+
|
|
52
|
+
assert.throws(() => {
|
|
53
|
+
// throws a SyntaxError
|
|
54
|
+
inner.run(() => JSON.parse('{'));
|
|
55
|
+
}, SyntaxError);
|
|
56
|
+
|
|
57
|
+
assert.strictEqual(getCurrentScope(), outer);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
assert.isUndefined(getCurrentScope());
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('an observable created inside run is disposed with the scope; a shared parent outside it is not', () => {
|
|
64
|
+
const store$ = source<number>();
|
|
65
|
+
|
|
66
|
+
const scope = createScope();
|
|
67
|
+
|
|
68
|
+
const doubled$ = scope.run(() => store$.pipe(map((x) => x * 2)));
|
|
69
|
+
|
|
70
|
+
const mut_history: number[] = [];
|
|
71
|
+
|
|
72
|
+
doubled$.subscribe((x) => {
|
|
73
|
+
mut_history.push(x);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
store$.next(1);
|
|
77
|
+
|
|
78
|
+
assert.deepStrictEqual(mut_history, [2]);
|
|
79
|
+
|
|
80
|
+
scope.dispose();
|
|
81
|
+
|
|
82
|
+
assert.isTrue(doubled$.isCompleted);
|
|
83
|
+
|
|
84
|
+
assert.isFalse(store$.isCompleted);
|
|
85
|
+
|
|
86
|
+
assert.isFalse(toNode(store$).hasChild);
|
|
87
|
+
|
|
88
|
+
store$.next(2);
|
|
89
|
+
|
|
90
|
+
assert.deepStrictEqual(mut_history, [2]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('an observable created outside any scope belongs to nothing', () => {
|
|
94
|
+
const store$ = source<number>();
|
|
95
|
+
|
|
96
|
+
const doubled$ = store$.pipe(map((x) => x * 2));
|
|
97
|
+
|
|
98
|
+
const scope = createScope();
|
|
99
|
+
|
|
100
|
+
scope.run(() => {
|
|
101
|
+
// a derived node created elsewhere is not adopted by being used here
|
|
102
|
+
doubled$.subscribe(() => {});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
scope.dispose();
|
|
106
|
+
|
|
107
|
+
assert.isFalse(doubled$.isCompleted);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('nodes go to the innermost scope', () => {
|
|
111
|
+
const store$ = source<number>();
|
|
112
|
+
|
|
113
|
+
const outer = createScope();
|
|
114
|
+
|
|
115
|
+
const inner = createScope(outer);
|
|
116
|
+
|
|
117
|
+
const [a$, b$] = outer.run(() => {
|
|
118
|
+
const a = store$.pipe(map((x) => x + 1));
|
|
119
|
+
|
|
120
|
+
const b = inner.run(() => store$.pipe(map((x) => x + 2)));
|
|
121
|
+
|
|
122
|
+
return [a, b] as const;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
inner.dispose();
|
|
126
|
+
|
|
127
|
+
assert.isFalse(a$.isCompleted);
|
|
128
|
+
|
|
129
|
+
assert.isTrue(b$.isCompleted);
|
|
130
|
+
|
|
131
|
+
outer.dispose();
|
|
132
|
+
|
|
133
|
+
assert.isTrue(a$.isCompleted);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('dispose runs child scopes, then cleanups in reverse, then owned nodes in reverse creation order', () => {
|
|
137
|
+
const store$ = source<number>();
|
|
138
|
+
|
|
139
|
+
const parent = createScope();
|
|
140
|
+
|
|
141
|
+
const child1 = createScope(parent);
|
|
142
|
+
|
|
143
|
+
const child2 = createScope(parent);
|
|
144
|
+
|
|
145
|
+
const mut_order: string[] = [];
|
|
146
|
+
|
|
147
|
+
child1.run(() => {
|
|
148
|
+
onScopeDispose(() => {
|
|
149
|
+
mut_order.push('child1');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
child2.run(() => {
|
|
154
|
+
onScopeDispose(() => {
|
|
155
|
+
mut_order.push('child2');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
parent.run(() => {
|
|
160
|
+
const first$ = store$.pipe(map((x) => x));
|
|
161
|
+
|
|
162
|
+
first$.subscribe(
|
|
163
|
+
() => {},
|
|
164
|
+
() => {
|
|
165
|
+
mut_order.push('node first');
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
onScopeDispose(() => {
|
|
170
|
+
mut_order.push('cleanup 1');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const second$ = store$.pipe(map((x) => x));
|
|
174
|
+
|
|
175
|
+
second$.subscribe(
|
|
176
|
+
() => {},
|
|
177
|
+
() => {
|
|
178
|
+
mut_order.push('node second');
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
onScopeDispose(() => {
|
|
183
|
+
mut_order.push('cleanup 2');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
parent.dispose();
|
|
188
|
+
|
|
189
|
+
assert.deepStrictEqual(mut_order, [
|
|
190
|
+
'child2',
|
|
191
|
+
'child1',
|
|
192
|
+
'cleanup 2',
|
|
193
|
+
'cleanup 1',
|
|
194
|
+
'node second',
|
|
195
|
+
'node first',
|
|
196
|
+
]);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('dispose is idempotent', () => {
|
|
200
|
+
const scope = createScope();
|
|
201
|
+
|
|
202
|
+
const mut_calls: number[] = [];
|
|
203
|
+
|
|
204
|
+
scope.run(() => {
|
|
205
|
+
onScopeDispose(() => {
|
|
206
|
+
mut_calls.push(1);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
scope.dispose();
|
|
211
|
+
|
|
212
|
+
scope.dispose();
|
|
213
|
+
|
|
214
|
+
assert.deepStrictEqual(mut_calls, [1]);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('a disposed child scope leaves its parent, so the parent does not dispose it again', () => {
|
|
218
|
+
const parent = createScope();
|
|
219
|
+
|
|
220
|
+
const child = createScope(parent);
|
|
221
|
+
|
|
222
|
+
const mut_calls: string[] = [];
|
|
223
|
+
|
|
224
|
+
child.run(() => {
|
|
225
|
+
onScopeDispose(() => {
|
|
226
|
+
mut_calls.push('child');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
child.dispose();
|
|
231
|
+
|
|
232
|
+
parent.dispose();
|
|
233
|
+
|
|
234
|
+
assert.deepStrictEqual(mut_calls, ['child']);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('a node that completes on its own is released by its scope', () => {
|
|
238
|
+
const store$ = source<number>();
|
|
239
|
+
|
|
240
|
+
const scope = createScope();
|
|
241
|
+
|
|
242
|
+
const mut_completions: number[] = [];
|
|
243
|
+
|
|
244
|
+
const doubled$ = scope.run(() => store$.pipe(map((x) => x * 2)));
|
|
245
|
+
|
|
246
|
+
doubled$.subscribe(
|
|
247
|
+
() => {},
|
|
248
|
+
() => {
|
|
249
|
+
mut_completions.push(1);
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
doubled$.complete();
|
|
254
|
+
|
|
255
|
+
scope.dispose();
|
|
256
|
+
|
|
257
|
+
assert.deepStrictEqual(mut_completions, [1]);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('after many child scopes are disposed on their own, the parent disposes the rest once, newest first', () => {
|
|
261
|
+
const parent = createScope();
|
|
262
|
+
|
|
263
|
+
const mut_order: number[] = [];
|
|
264
|
+
|
|
265
|
+
const children = Array.from({ length: 100 }, (_, i) => {
|
|
266
|
+
const child = createScope(parent);
|
|
267
|
+
|
|
268
|
+
child.run(() => {
|
|
269
|
+
onScopeDispose(() => {
|
|
270
|
+
mut_order.push(i);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return child;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Every child whose index is not a multiple of 3 goes on its own: 66 of
|
|
278
|
+
// them, enough for the parent to rebuild its list of children.
|
|
279
|
+
for (const [i, child] of children.entries()) {
|
|
280
|
+
if (i % 3 !== 0) {
|
|
281
|
+
child.dispose();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const disposedAlone = mut_order.length;
|
|
286
|
+
|
|
287
|
+
parent.dispose();
|
|
288
|
+
|
|
289
|
+
assert.strictEqual(disposedAlone, 66);
|
|
290
|
+
|
|
291
|
+
assert.deepStrictEqual(
|
|
292
|
+
mut_order.slice(disposedAlone),
|
|
293
|
+
Array.from({ length: 34 }, (_, k) => (33 - k) * 3),
|
|
294
|
+
);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('after many owned nodes complete on their own, the scope disposes the rest once, newest first', () => {
|
|
298
|
+
const store$ = source<number>();
|
|
299
|
+
|
|
300
|
+
const scope = createScope();
|
|
301
|
+
|
|
302
|
+
const mut_completions: number[] = [];
|
|
303
|
+
|
|
304
|
+
const nodes = scope.run(() =>
|
|
305
|
+
Array.from({ length: 100 }, (_, i) => {
|
|
306
|
+
const node$ = store$.pipe(map((x) => x + i));
|
|
307
|
+
|
|
308
|
+
node$.subscribe(
|
|
309
|
+
() => {},
|
|
310
|
+
() => {
|
|
311
|
+
mut_completions.push(i);
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return node$;
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
for (const [i, node$] of nodes.entries()) {
|
|
320
|
+
if (i % 3 !== 0) {
|
|
321
|
+
node$.complete();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const completedAlone = mut_completions.length;
|
|
326
|
+
|
|
327
|
+
scope.dispose();
|
|
328
|
+
|
|
329
|
+
assert.strictEqual(completedAlone, 66);
|
|
330
|
+
|
|
331
|
+
assert.deepStrictEqual(
|
|
332
|
+
mut_completions.slice(completedAlone),
|
|
333
|
+
Array.from({ length: 34 }, (_, k) => (33 - k) * 3),
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('nodes created inside untracked belong to no scope', () => {
|
|
338
|
+
const store$ = source<number>();
|
|
339
|
+
|
|
340
|
+
const scope = createScope();
|
|
341
|
+
|
|
342
|
+
const [owned$, free$] = scope.run(() => {
|
|
343
|
+
const owned = store$.pipe(map((x) => x));
|
|
344
|
+
|
|
345
|
+
const free = untracked(() => {
|
|
346
|
+
assert.isUndefined(getCurrentScope());
|
|
347
|
+
|
|
348
|
+
return store$.pipe(map((x) => x));
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
assert.strictEqual(getCurrentScope(), scope);
|
|
352
|
+
|
|
353
|
+
return [owned, free] as const;
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
scope.dispose();
|
|
357
|
+
|
|
358
|
+
assert.isTrue(owned$.isCompleted);
|
|
359
|
+
|
|
360
|
+
assert.isFalse(free$.isCompleted);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test('a node created after await belongs to no scope', async () => {
|
|
364
|
+
const store$ = source<number>();
|
|
365
|
+
|
|
366
|
+
const scope = createScope();
|
|
367
|
+
|
|
368
|
+
const promise = scope.run(async () => {
|
|
369
|
+
await Promise.resolve();
|
|
370
|
+
|
|
371
|
+
assert.isUndefined(getCurrentScope());
|
|
372
|
+
|
|
373
|
+
return store$.pipe(map((x) => x));
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const late$ = await promise;
|
|
377
|
+
|
|
378
|
+
scope.dispose();
|
|
379
|
+
|
|
380
|
+
assert.isFalse(late$.isCompleted);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test('run on a disposed scope warns, still runs fn, and owns nothing it creates', () => {
|
|
384
|
+
const warn = stubConsoleWarn();
|
|
385
|
+
|
|
386
|
+
const store$ = source<number>();
|
|
387
|
+
|
|
388
|
+
const scope = createScope();
|
|
389
|
+
|
|
390
|
+
scope.dispose();
|
|
391
|
+
|
|
392
|
+
const free$ = scope.run(() => {
|
|
393
|
+
assert.isUndefined(getCurrentScope());
|
|
394
|
+
|
|
395
|
+
return store$.pipe(map((x) => x));
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
assert.isFalse(free$.isCompleted);
|
|
399
|
+
|
|
400
|
+
expect(warn).toHaveBeenCalledExactlyOnceWith(
|
|
401
|
+
expect.stringContaining('disposed scope'),
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
vi.unstubAllGlobals();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test('onScopeDispose outside any scope warns and registers nothing', () => {
|
|
408
|
+
const warn = stubConsoleWarn();
|
|
409
|
+
|
|
410
|
+
onScopeDispose(() => {});
|
|
411
|
+
|
|
412
|
+
expect(warn).toHaveBeenCalledExactlyOnceWith(
|
|
413
|
+
expect.stringContaining('outside any scope'),
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
vi.unstubAllGlobals();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('a scope created under a disposed parent is born disposed', () => {
|
|
420
|
+
const warn = stubConsoleWarn();
|
|
421
|
+
|
|
422
|
+
const parent = createScope();
|
|
423
|
+
|
|
424
|
+
parent.dispose();
|
|
425
|
+
|
|
426
|
+
const child = createScope(parent);
|
|
427
|
+
|
|
428
|
+
expect(warn).toHaveBeenCalledExactlyOnceWith(
|
|
429
|
+
expect.stringContaining('disposed parent'),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
child.run(() => {
|
|
433
|
+
assert.isUndefined(getCurrentScope());
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
expect(warn).toHaveBeenCalledTimes(2);
|
|
437
|
+
|
|
438
|
+
vi.unstubAllGlobals();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test('disposing a parent disposes nodes of its child scopes', () => {
|
|
442
|
+
const store$ = source<number>();
|
|
443
|
+
|
|
444
|
+
const parent = createScope();
|
|
445
|
+
|
|
446
|
+
const child = createScope(parent);
|
|
447
|
+
|
|
448
|
+
const owned$ = child.run(() => store$.pipe(map((x) => x)));
|
|
449
|
+
|
|
450
|
+
parent.dispose();
|
|
451
|
+
|
|
452
|
+
assert.isTrue(owned$.isCompleted);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
const stubConsoleWarn = (): Mock => {
|
|
457
|
+
const warn = vi.fn();
|
|
458
|
+
|
|
459
|
+
vi.stubGlobal('console', { ...console, warn });
|
|
460
|
+
|
|
461
|
+
return warn;
|
|
462
|
+
};
|
package/src/core/types/id.mts
CHANGED