@ersbeth/picoflow 0.2.4 → 1.0.1
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/.cursor/plans/update-js-e795d61b.plan.md +567 -0
- package/.gitlab-ci.yml +24 -0
- package/.vscode/settings.json +3 -3
- package/CHANGELOG.md +51 -0
- package/IMPLEMENTATION_GUIDE.md +1578 -0
- package/README.md +9 -134
- package/biome.json +32 -32
- package/dist/picoflow.js +610 -436
- package/dist/types/advanced/array.d.ts +0 -6
- package/dist/types/advanced/array.d.ts.map +1 -1
- package/dist/types/advanced/index.d.ts +5 -5
- package/dist/types/advanced/index.d.ts.map +1 -1
- package/dist/types/advanced/map.d.ts +114 -23
- package/dist/types/advanced/map.d.ts.map +1 -1
- package/dist/types/advanced/resource.d.ts +51 -12
- package/dist/types/advanced/resource.d.ts.map +1 -1
- package/dist/types/advanced/resourceAsync.d.ts +28 -13
- package/dist/types/advanced/resourceAsync.d.ts.map +1 -1
- package/dist/types/advanced/stream.d.ts +74 -16
- package/dist/types/advanced/stream.d.ts.map +1 -1
- package/dist/types/advanced/streamAsync.d.ts +69 -15
- package/dist/types/advanced/streamAsync.d.ts.map +1 -1
- package/dist/types/basic/constant.d.ts +44 -16
- package/dist/types/basic/constant.d.ts.map +1 -1
- package/dist/types/basic/derivation.d.ts +73 -24
- package/dist/types/basic/derivation.d.ts.map +1 -1
- package/dist/types/basic/disposable.d.ts +65 -6
- package/dist/types/basic/disposable.d.ts.map +1 -1
- package/dist/types/basic/effect.d.ts +27 -16
- package/dist/types/basic/effect.d.ts.map +1 -1
- package/dist/types/basic/index.d.ts +7 -8
- package/dist/types/basic/index.d.ts.map +1 -1
- package/dist/types/basic/observable.d.ts +62 -13
- package/dist/types/basic/observable.d.ts.map +1 -1
- package/dist/types/basic/signal.d.ts +35 -6
- package/dist/types/basic/signal.d.ts.map +1 -1
- package/dist/types/basic/state.d.ts +25 -4
- package/dist/types/basic/state.d.ts.map +1 -1
- package/dist/types/basic/trackingContext.d.ts +33 -0
- package/dist/types/basic/trackingContext.d.ts.map +1 -0
- package/dist/types/creators.d.ts +271 -26
- package/dist/types/creators.d.ts.map +1 -1
- package/dist/types/index.d.ts +60 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/solid/converters.d.ts +5 -5
- package/dist/types/solid/converters.d.ts.map +1 -1
- package/dist/types/solid/index.d.ts +2 -2
- package/dist/types/solid/index.d.ts.map +1 -1
- package/dist/types/solid/primitives.d.ts +96 -4
- package/dist/types/solid/primitives.d.ts.map +1 -1
- package/docs/.vitepress/config.mts +110 -0
- package/docs/api/classes/FlowArray.md +489 -0
- package/docs/api/classes/FlowConstant.md +350 -0
- package/docs/api/classes/FlowDerivation.md +334 -0
- package/docs/api/classes/FlowEffect.md +100 -0
- package/docs/api/classes/FlowMap.md +512 -0
- package/docs/api/classes/FlowObservable.md +306 -0
- package/docs/api/classes/FlowResource.md +380 -0
- package/docs/api/classes/FlowResourceAsync.md +362 -0
- package/docs/api/classes/FlowSignal.md +160 -0
- package/docs/api/classes/FlowState.md +368 -0
- package/docs/api/classes/FlowStream.md +367 -0
- package/docs/api/classes/FlowStreamAsync.md +364 -0
- package/docs/api/classes/SolidDerivation.md +75 -0
- package/docs/api/classes/SolidResource.md +91 -0
- package/docs/api/classes/SolidState.md +71 -0
- package/docs/api/classes/TrackingContext.md +33 -0
- package/docs/api/functions/array.md +58 -0
- package/docs/api/functions/constant.md +45 -0
- package/docs/api/functions/derivation.md +53 -0
- package/docs/api/functions/effect.md +49 -0
- package/docs/api/functions/from.md +220 -0
- package/docs/api/functions/isDisposable.md +49 -0
- package/docs/api/functions/map.md +57 -0
- package/docs/api/functions/resource.md +52 -0
- package/docs/api/functions/resourceAsync.md +50 -0
- package/docs/api/functions/signal.md +36 -0
- package/docs/api/functions/state.md +47 -0
- package/docs/api/functions/stream.md +53 -0
- package/docs/api/functions/streamAsync.md +50 -0
- package/docs/api/index.md +118 -0
- package/docs/api/interfaces/FlowDisposable.md +65 -0
- package/docs/api/interfaces/SolidObservable.md +19 -0
- package/docs/api/type-aliases/FlowArrayAction.md +49 -0
- package/docs/api/type-aliases/FlowStreamDisposer.md +15 -0
- package/docs/api/type-aliases/FlowStreamSetter.md +27 -0
- package/docs/api/type-aliases/FlowStreamUpdater.md +32 -0
- package/docs/api/type-aliases/NotPromise.md +18 -0
- package/docs/api/type-aliases/SolidGetter.md +17 -0
- package/docs/api/typedoc-sidebar.json +1 -0
- package/docs/examples/examples.md +2313 -0
- package/docs/examples/patterns.md +649 -0
- package/docs/guide/advanced/disposal.md +426 -0
- package/docs/guide/advanced/solidjs.md +221 -0
- package/docs/guide/advanced/upgrading.md +464 -0
- package/docs/guide/introduction/concepts.md +56 -0
- package/docs/guide/introduction/conventions.md +61 -0
- package/docs/guide/introduction/getting-started.md +134 -0
- package/docs/guide/introduction/lifecycle.md +371 -0
- package/docs/guide/primitives/array.md +400 -0
- package/docs/guide/primitives/constant.md +380 -0
- package/docs/guide/primitives/derivations.md +348 -0
- package/docs/guide/primitives/effects.md +458 -0
- package/docs/guide/primitives/map.md +387 -0
- package/docs/guide/primitives/overview.md +175 -0
- package/docs/guide/primitives/resources.md +858 -0
- package/docs/guide/primitives/signal.md +259 -0
- package/docs/guide/primitives/state.md +368 -0
- package/docs/guide/primitives/streams.md +931 -0
- package/docs/index.md +47 -0
- package/docs/public/logo.svg +1 -0
- package/package.json +57 -41
- package/src/advanced/array.ts +208 -210
- package/src/advanced/index.ts +7 -7
- package/src/advanced/map.ts +178 -68
- package/src/advanced/resource.ts +87 -43
- package/src/advanced/resourceAsync.ts +62 -42
- package/src/advanced/stream.ts +113 -50
- package/src/advanced/streamAsync.ts +120 -61
- package/src/basic/constant.ts +82 -49
- package/src/basic/derivation.ts +128 -84
- package/src/basic/disposable.ts +74 -15
- package/src/basic/effect.ts +85 -77
- package/src/basic/index.ts +7 -8
- package/src/basic/observable.ts +94 -36
- package/src/basic/signal.ts +133 -105
- package/src/basic/state.ts +46 -25
- package/src/basic/trackingContext.ts +45 -0
- package/src/creators.ts +297 -54
- package/src/index.ts +96 -43
- package/src/solid/converters.ts +186 -67
- package/src/solid/index.ts +8 -2
- package/src/solid/primitives.ts +167 -65
- package/test/array.test.ts +592 -612
- package/test/constant.test.ts +31 -33
- package/test/derivation.test.ts +531 -536
- package/test/effect.test.ts +21 -21
- package/test/map.test.ts +233 -137
- package/test/resource.test.ts +119 -121
- package/test/resourceAsync.test.ts +98 -100
- package/test/signal.test.ts +51 -55
- package/test/state.test.ts +186 -168
- package/test/stream.test.ts +189 -189
- package/test/streamAsync.test.ts +186 -186
- package/tsconfig.json +19 -18
- package/typedoc.json +37 -0
- package/vite.config.ts +23 -23
- package/vitest.config.ts +7 -7
- package/api/doc/index.md +0 -31
- package/api/doc/picoflow.array.md +0 -55
- package/api/doc/picoflow.constant.md +0 -55
- package/api/doc/picoflow.derivation.md +0 -55
- package/api/doc/picoflow.effect.md +0 -55
- package/api/doc/picoflow.flowarray._constructor_.md +0 -49
- package/api/doc/picoflow.flowarray._lastaction.md +0 -13
- package/api/doc/picoflow.flowarray.clear.md +0 -17
- package/api/doc/picoflow.flowarray.dispose.md +0 -55
- package/api/doc/picoflow.flowarray.get.md +0 -19
- package/api/doc/picoflow.flowarray.length.md +0 -13
- package/api/doc/picoflow.flowarray.md +0 -273
- package/api/doc/picoflow.flowarray.pop.md +0 -17
- package/api/doc/picoflow.flowarray.push.md +0 -53
- package/api/doc/picoflow.flowarray.set.md +0 -53
- package/api/doc/picoflow.flowarray.setitem.md +0 -69
- package/api/doc/picoflow.flowarray.shift.md +0 -17
- package/api/doc/picoflow.flowarray.splice.md +0 -85
- package/api/doc/picoflow.flowarray.unshift.md +0 -53
- package/api/doc/picoflow.flowarrayaction.md +0 -37
- package/api/doc/picoflow.flowconstant._constructor_.md +0 -49
- package/api/doc/picoflow.flowconstant.get.md +0 -25
- package/api/doc/picoflow.flowconstant.md +0 -88
- package/api/doc/picoflow.flowderivation._constructor_.md +0 -49
- package/api/doc/picoflow.flowderivation.get.md +0 -23
- package/api/doc/picoflow.flowderivation.md +0 -86
- package/api/doc/picoflow.flowdisposable.dispose.md +0 -55
- package/api/doc/picoflow.flowdisposable.md +0 -43
- package/api/doc/picoflow.floweffect._constructor_.md +0 -54
- package/api/doc/picoflow.floweffect.dispose.md +0 -21
- package/api/doc/picoflow.floweffect.disposed.md +0 -13
- package/api/doc/picoflow.floweffect.md +0 -131
- package/api/doc/picoflow.flowgetter.md +0 -15
- package/api/doc/picoflow.flowmap._lastdeleted.md +0 -21
- package/api/doc/picoflow.flowmap._lastset.md +0 -21
- package/api/doc/picoflow.flowmap.delete.md +0 -61
- package/api/doc/picoflow.flowmap.md +0 -133
- package/api/doc/picoflow.flowmap.setat.md +0 -77
- package/api/doc/picoflow.flowobservable.get.md +0 -19
- package/api/doc/picoflow.flowobservable.md +0 -68
- package/api/doc/picoflow.flowobservable.subscribe.md +0 -55
- package/api/doc/picoflow.flowresource._constructor_.md +0 -49
- package/api/doc/picoflow.flowresource.fetch.md +0 -27
- package/api/doc/picoflow.flowresource.get.md +0 -23
- package/api/doc/picoflow.flowresource.md +0 -100
- package/api/doc/picoflow.flowresourceasync._constructor_.md +0 -49
- package/api/doc/picoflow.flowresourceasync.fetch.md +0 -27
- package/api/doc/picoflow.flowresourceasync.get.md +0 -23
- package/api/doc/picoflow.flowresourceasync.md +0 -100
- package/api/doc/picoflow.flowsignal.dispose.md +0 -59
- package/api/doc/picoflow.flowsignal.disposed.md +0 -18
- package/api/doc/picoflow.flowsignal.md +0 -112
- package/api/doc/picoflow.flowsignal.trigger.md +0 -21
- package/api/doc/picoflow.flowstate.md +0 -52
- package/api/doc/picoflow.flowstate.set.md +0 -61
- package/api/doc/picoflow.flowstream._constructor_.md +0 -49
- package/api/doc/picoflow.flowstream.dispose.md +0 -21
- package/api/doc/picoflow.flowstream.get.md +0 -23
- package/api/doc/picoflow.flowstream.md +0 -100
- package/api/doc/picoflow.flowstreamasync._constructor_.md +0 -54
- package/api/doc/picoflow.flowstreamasync.dispose.md +0 -21
- package/api/doc/picoflow.flowstreamasync.get.md +0 -23
- package/api/doc/picoflow.flowstreamasync.md +0 -100
- package/api/doc/picoflow.flowstreamdisposer.md +0 -13
- package/api/doc/picoflow.flowstreamsetter.md +0 -13
- package/api/doc/picoflow.flowstreamupdater.md +0 -19
- package/api/doc/picoflow.flowwatcher.md +0 -15
- package/api/doc/picoflow.from.md +0 -55
- package/api/doc/picoflow.from_1.md +0 -55
- package/api/doc/picoflow.from_2.md +0 -55
- package/api/doc/picoflow.from_3.md +0 -55
- package/api/doc/picoflow.from_4.md +0 -55
- package/api/doc/picoflow.from_5.md +0 -55
- package/api/doc/picoflow.isdisposable.md +0 -55
- package/api/doc/picoflow.map.md +0 -59
- package/api/doc/picoflow.md +0 -544
- package/api/doc/picoflow.resource.md +0 -55
- package/api/doc/picoflow.resourceasync.md +0 -55
- package/api/doc/picoflow.signal.md +0 -19
- package/api/doc/picoflow.solidderivation._constructor_.md +0 -49
- package/api/doc/picoflow.solidderivation.get.md +0 -13
- package/api/doc/picoflow.solidderivation.md +0 -94
- package/api/doc/picoflow.solidgetter.md +0 -13
- package/api/doc/picoflow.solidobservable.get.md +0 -13
- package/api/doc/picoflow.solidobservable.md +0 -57
- package/api/doc/picoflow.solidresource._constructor_.md +0 -49
- package/api/doc/picoflow.solidresource.get.md +0 -13
- package/api/doc/picoflow.solidresource.latest.md +0 -13
- package/api/doc/picoflow.solidresource.md +0 -157
- package/api/doc/picoflow.solidresource.refetch.md +0 -13
- package/api/doc/picoflow.solidresource.state.md +0 -13
- package/api/doc/picoflow.solidstate._constructor_.md +0 -49
- package/api/doc/picoflow.solidstate.get.md +0 -13
- package/api/doc/picoflow.solidstate.md +0 -115
- package/api/doc/picoflow.solidstate.set.md +0 -13
- package/api/doc/picoflow.state.md +0 -55
- package/api/doc/picoflow.stream.md +0 -55
- package/api/doc/picoflow.streamasync.md +0 -55
- package/api/picoflow.public.api.md +0 -244
- package/api-extractor.json +0 -61
package/src/basic/derivation.ts
CHANGED
|
@@ -1,103 +1,147 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { FlowObservable } from "./observable";
|
|
2
|
+
import { TrackingContext } from "./trackingContext";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Represents a reactive derivation whose value is computed based on other reactive signals.
|
|
6
|
+
*
|
|
6
7
|
* @remarks
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* FlowDerivation creates a computed value that automatically tracks its dependencies and
|
|
9
|
+
* recomputes when any dependency changes. The computation is lazy: it doesn't execute until
|
|
10
|
+
* the value is first accessed (via `get()` or `pick()`), and subsequent accesses return the
|
|
11
|
+
* cached value until a dependency changes.
|
|
12
|
+
*
|
|
13
|
+
* **Lazy Initialization:**
|
|
14
|
+
* The compute function doesn't run immediately upon creation. It runs only when:
|
|
15
|
+
* - The value is first read via `get()` or `pick()`
|
|
16
|
+
* - The derivation is watched via `watch()`
|
|
17
|
+
*
|
|
18
|
+
* **Dirty Checking and Recomputation:**
|
|
19
|
+
* When a tracked dependency changes, the derivation is marked as "dirty" but doesn't recompute
|
|
20
|
+
* immediately. Recomputation happens lazily on the next value access. This prevents unnecessary
|
|
21
|
+
* computations when multiple dependencies change in quick succession.
|
|
22
|
+
*
|
|
23
|
+
* **Dynamic Dependencies:**
|
|
24
|
+
* Dependencies are tracked dynamically during each computation. If the compute function
|
|
25
|
+
* conditionally tracks different observables, the dependency graph updates automatically.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const $firstName = state('John');
|
|
30
|
+
* const $lastName = state('Doe');
|
|
31
|
+
*
|
|
32
|
+
* const $fullName = derivation((t) => {
|
|
33
|
+
* return `${$firstName.get(t)} ${$lastName.get(t)}`;
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Compute function hasn't run yet (lazy)
|
|
37
|
+
* const name = $fullName.pick(); // Now it computes
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @typeParam T - The type of the computed value.
|
|
11
41
|
* @public
|
|
12
42
|
*/
|
|
13
43
|
export class FlowDerivation<T> extends FlowObservable<T> {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Creates a new FlowDerivation.
|
|
46
|
+
*
|
|
47
|
+
* @param compute - A function that computes the derived value using a tracking context.
|
|
48
|
+
* The function receives a TrackingContext and should use it to access dependencies via
|
|
49
|
+
* `.get(t)`. The function is not executed immediately; it runs lazily on first access.
|
|
50
|
+
*
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
constructor(compute: (t: TrackingContext) => T) {
|
|
54
|
+
super();
|
|
55
|
+
this._compute = compute;
|
|
56
|
+
this._trackedContext = new TrackingContext(this);
|
|
57
|
+
}
|
|
24
58
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this._initLazy();
|
|
36
|
-
this._compute();
|
|
37
|
-
return this._value;
|
|
38
|
-
}
|
|
59
|
+
/**
|
|
60
|
+
* Internal method to get the raw value.
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
protected _getRaw(): T {
|
|
64
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
65
|
+
this._initLazy();
|
|
66
|
+
this._update();
|
|
67
|
+
return this._value;
|
|
68
|
+
}
|
|
39
69
|
|
|
40
|
-
|
|
70
|
+
/* INTERNAL --------------------------------------------------------- */
|
|
41
71
|
|
|
42
|
-
|
|
43
|
-
|
|
72
|
+
private _initialized = false;
|
|
73
|
+
private _dirty = false;
|
|
74
|
+
private _compute: (t: TrackingContext) => T;
|
|
75
|
+
private _trackedContext: TrackingContext;
|
|
44
76
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
private _initLazy(): void {
|
|
78
|
+
if (!this._initialized) {
|
|
79
|
+
this._value = this._compute(this._trackedContext);
|
|
80
|
+
this._initialized = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
51
83
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
compute(this._trackedGet, this._trackedWatch);
|
|
57
|
-
this._untrackedCompute = () =>
|
|
58
|
-
compute(this._untrackedGet, this._untrackedWatch);
|
|
59
|
-
}
|
|
84
|
+
/* @internal */ private _update(): void {
|
|
85
|
+
if (this._dirty) {
|
|
86
|
+
// Store current dependencies
|
|
87
|
+
const dependencies = [...this._dependencies];
|
|
60
88
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this._initialized = true;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
89
|
+
// Clear current dependencies, compute and retrack dependencies
|
|
90
|
+
this._dependencies.clear();
|
|
91
|
+
this._value = this._compute(this._trackedContext);
|
|
67
92
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
93
|
+
// Unsubscribe from dependencies that are no longer needed
|
|
94
|
+
const dependenciesToRemove = dependencies.filter(
|
|
95
|
+
(dependency) => !this._dependencies.has(dependency),
|
|
96
|
+
);
|
|
97
|
+
dependenciesToRemove.forEach((dependency) => {
|
|
98
|
+
dependency._unregisterDependency(this);
|
|
99
|
+
});
|
|
72
100
|
|
|
73
|
-
|
|
74
|
-
|
|
101
|
+
this._dirty = false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
75
104
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
105
|
+
/* @internal */ override _notify(): void {
|
|
106
|
+
this._dirty = true;
|
|
107
|
+
super._notify();
|
|
108
|
+
}
|
|
79
109
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Watches the derivation, registering it as a dependency in the given context.
|
|
112
|
+
*
|
|
113
|
+
* @param context - The tracking context in which to register this derivation.
|
|
114
|
+
*
|
|
115
|
+
* @remarks
|
|
116
|
+
* This method overrides the base `watch()` to handle a special case: when a derivation
|
|
117
|
+
* is watched without having its value read (e.g., `$derivation.watch(t)` instead of
|
|
118
|
+
* `$derivation.get(t)`), the derivation still needs to compute its value to establish
|
|
119
|
+
* its own dependencies. Otherwise, the derivation would be tracked but wouldn't track
|
|
120
|
+
* its own dependencies, breaking the reactive chain.
|
|
121
|
+
*
|
|
122
|
+
* This override ensures that:
|
|
123
|
+
* 1. The derivation is registered as a dependency in the provided context
|
|
124
|
+
* 2. The derivation computes its value (if not already computed)
|
|
125
|
+
* 3. The derivation tracks its own dependencies during computation
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* const $derived = derivation((t) => $state.get(t) * 2);
|
|
130
|
+
*
|
|
131
|
+
* effect((t) => {
|
|
132
|
+
* $derived.watch(t); // Derivation computes even without reading value
|
|
133
|
+
* doSomething(); // Effect re-runs when $derived's dependencies change
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @public
|
|
138
|
+
*/
|
|
139
|
+
public override watch(context: TrackingContext): void {
|
|
140
|
+
super.watch(context); // Register this derivation as a dependency
|
|
87
141
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this._dirty = true;
|
|
94
|
-
super._notify();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/* @internal */ override _watch(): void {
|
|
98
|
-
/* v8 ignore next 1 */
|
|
99
|
-
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
100
|
-
this._initLazy();
|
|
101
|
-
this._compute();
|
|
102
|
-
}
|
|
142
|
+
// Ensure the derivation is initialized and up-to-date
|
|
143
|
+
// This is needed when watching a derivation without reading its value
|
|
144
|
+
this._initLazy();
|
|
145
|
+
this._update();
|
|
146
|
+
}
|
|
103
147
|
}
|
package/src/basic/disposable.ts
CHANGED
|
@@ -1,27 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Represents an object with a disposable lifecycle.
|
|
2
|
+
* Represents an object with a disposable lifecycle that manages resources requiring cleanup.
|
|
3
|
+
*
|
|
3
4
|
* @remarks
|
|
4
|
-
*
|
|
5
|
+
* FlowDisposable is the interface for PicoFlow primitives that hold resources needing
|
|
6
|
+
* explicit cleanup. Implementing this interface ensures that objects can properly release
|
|
7
|
+
* resources such as subscriptions, event listeners, and dependent effects.
|
|
8
|
+
*
|
|
9
|
+
* **Disposal Behavior:**
|
|
10
|
+
* All PicoFlow reactive primitives (signals, states, effects, derivations, etc.) implement
|
|
11
|
+
* this interface. When disposed:
|
|
12
|
+
* - The primitive becomes unusable and throws errors on further access
|
|
13
|
+
* - All subscriptions and dependencies are cleaned up
|
|
14
|
+
* - Memory is freed for garbage collection
|
|
15
|
+
*
|
|
16
|
+
* **Options:**
|
|
17
|
+
* The optional `options` parameter controls disposal behavior:
|
|
18
|
+
* - `{ self: true }`: Dispose only this object, leaving dependent effects/listeners active
|
|
19
|
+
* - `{ self: false }` or omitted: Dispose this object AND all dependent effects/listeners (cascade)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const $state = state(0);
|
|
24
|
+
* const fx = effect((t) => console.log($state.get(t)));
|
|
25
|
+
*
|
|
26
|
+
* // Cascade disposal - disposes $state and all its effects
|
|
27
|
+
* $state.dispose();
|
|
28
|
+
*
|
|
29
|
+
* // Self-only disposal - disposes $state but leaves effects intact
|
|
30
|
+
* $state.dispose({ self: true });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
5
33
|
* @public
|
|
6
34
|
*/
|
|
7
35
|
export interface FlowDisposable {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Disposes resources held by this object.
|
|
38
|
+
*
|
|
39
|
+
* @param options - Optional configuration for disposal behavior.
|
|
40
|
+
* @param options.self - When true, disposes only this object without cascading to dependents.
|
|
41
|
+
* When false or omitted, disposes this object and all its dependent effects and listeners.
|
|
42
|
+
*
|
|
43
|
+
* @throws Error if the object has already been disposed (behavior may vary by implementation).
|
|
44
|
+
*/
|
|
45
|
+
dispose(options?: { self: boolean }): void;
|
|
13
46
|
}
|
|
14
47
|
|
|
15
48
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @
|
|
49
|
+
* Type guard that checks whether an object implements the FlowDisposable interface.
|
|
50
|
+
*
|
|
51
|
+
* @param obj - The object to test for disposability.
|
|
52
|
+
* @returns True if the object has a `dispose` method and is therefore disposable, false otherwise.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* This utility function is useful for safely checking if an object needs disposal before
|
|
56
|
+
* attempting cleanup operations. It performs a runtime check for the presence of a `dispose`
|
|
57
|
+
* method, making it safe to use with unknown types.
|
|
58
|
+
*
|
|
59
|
+
* **Common Use Cases:**
|
|
60
|
+
* - Conditionally disposing items in collections (arrays, maps)
|
|
61
|
+
* - Generic cleanup functions that handle both disposable and non-disposable objects
|
|
62
|
+
* - Defensive programming when working with mixed types
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* function cleanupArray<T>(items: T[]) {
|
|
67
|
+
* items.forEach(item => {
|
|
68
|
+
* if (isDisposable(item)) {
|
|
69
|
+
* item.dispose();
|
|
70
|
+
* }
|
|
71
|
+
* });
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* const mixed = [state(1), "string", signal(), 42];
|
|
75
|
+
* cleanupArray(mixed); // Only disposes the state and signal
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
19
78
|
* @public
|
|
20
79
|
*/
|
|
21
80
|
export function isDisposable(obj: unknown): obj is FlowDisposable {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
81
|
+
return (
|
|
82
|
+
obj !== null &&
|
|
83
|
+
obj !== undefined &&
|
|
84
|
+
typeof (obj as FlowDisposable).dispose === "function"
|
|
85
|
+
);
|
|
27
86
|
}
|
package/src/basic/effect.ts
CHANGED
|
@@ -1,96 +1,104 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
1
|
+
import type { FlowSignal } from "./signal";
|
|
2
|
+
import { TrackingContext } from "./trackingContext";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Represents a reactive effect that executes side-effect functions based
|
|
6
6
|
* on its tracked dependencies.
|
|
7
7
|
*
|
|
8
8
|
* @remarks
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* FlowEffect executes an apply function that performs side effects. The effect always runs
|
|
10
|
+
* with a tracking context, allowing you to explicitly control which observables and signals
|
|
11
|
+
* become dependencies using `.get(t)` for tracked reads or `.pick()` for untracked reads.
|
|
12
|
+
*
|
|
13
|
+
* When any tracked dependency changes, the effect automatically re-executes. The effect
|
|
14
|
+
* runs immediately upon creation and whenever its dependencies trigger updates.
|
|
15
|
+
*
|
|
16
|
+
* Unlike the old API, effects in the new TrackingContext-based system always execute with
|
|
17
|
+
* a tracking context available. You control reactivity by choosing which observables to track
|
|
18
|
+
* within the effect body.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const fx = effect((t) => {
|
|
23
|
+
* const reactive = $stateA.get(t); // Tracked - effect re-runs when $stateA changes
|
|
24
|
+
* const snapshot = $stateB.pick(); // Not tracked - changes don't trigger re-runs
|
|
25
|
+
* console.log(reactive, snapshot);
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
12
28
|
*
|
|
13
29
|
* @public
|
|
14
30
|
*/
|
|
15
31
|
export class FlowEffect {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Creates a new FlowEffect.
|
|
34
|
+
*
|
|
35
|
+
* @param apply - A side-effect function that receives a tracking context to
|
|
36
|
+
* access and register dependencies on reactive observables and signals.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* The provided function is executed immediately upon construction with a tracking context.
|
|
40
|
+
* Use the context parameter to call `.get(t)` on observables you want to track, or `.pick()`
|
|
41
|
+
* on observables you want to read without creating dependencies.
|
|
42
|
+
*
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
constructor(apply: (t: TrackingContext) => void) {
|
|
46
|
+
this._trackedContext = new TrackingContext(this);
|
|
47
|
+
this._apply = apply;
|
|
48
|
+
this._exec();
|
|
49
|
+
}
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Disposes the effect, unregistering all its tracked dependencies.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* Once disposed, the effect must no longer be used. Trying to dispose an effect
|
|
56
|
+
* that is already disposed will throw an error.
|
|
57
|
+
*
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
public dispose(): void {
|
|
61
|
+
if (this._disposed) throw new Error("[PicoFlow] Effect is disposed");
|
|
62
|
+
Array.from(this._dependencies).forEach((dependency) => {
|
|
63
|
+
this._unregisterDependency(dependency);
|
|
64
|
+
});
|
|
65
|
+
this._disposed = true;
|
|
66
|
+
}
|
|
51
67
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Indicates whether this effect has been disposed.
|
|
70
|
+
*
|
|
71
|
+
* @returns A boolean value that is true if the effect is disposed, false otherwise.
|
|
72
|
+
*
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
public get disposed(): boolean {
|
|
76
|
+
return this._disposed;
|
|
77
|
+
}
|
|
62
78
|
|
|
63
|
-
|
|
79
|
+
/* INTERNAL ------------------------------------------------------------ */
|
|
64
80
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
private _disposed = false;
|
|
82
|
+
private _dependencies = new Set<FlowSignal>();
|
|
83
|
+
private _trackedContext: TrackingContext;
|
|
84
|
+
private _apply: (t: TrackingContext) => void;
|
|
68
85
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
private _trackedExec: () => void;
|
|
74
|
-
private _untrackedExec: () => void;
|
|
86
|
+
/** @internal */ _exec(): void {
|
|
87
|
+
if (this._disposed)
|
|
88
|
+
/* v8 ignore next 1 */
|
|
89
|
+
throw new Error("[PicoFlow] Effect is disposed");
|
|
75
90
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
throw new Error("[PicoFlow] Effect is disposed");
|
|
80
|
-
if (this._initialized) this._untrackedExec();
|
|
81
|
-
else {
|
|
82
|
-
this._trackedExec();
|
|
83
|
-
this._initialized = true;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
91
|
+
// Always execute with tracking context
|
|
92
|
+
this._apply(this._trackedContext);
|
|
93
|
+
}
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
/** @internal */ _registerDependency(dependency: FlowSignal): void {
|
|
96
|
+
this._dependencies.add(dependency);
|
|
97
|
+
dependency._registerEffect(this);
|
|
98
|
+
}
|
|
91
99
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
/** @internal */ _unregisterDependency(dependency: FlowSignal): void {
|
|
101
|
+
this._dependencies.delete(dependency);
|
|
102
|
+
dependency._unregisterEffect(this);
|
|
103
|
+
}
|
|
96
104
|
}
|
package/src/basic/index.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
export { FlowSignal } from "./signal";
|
|
2
|
-
export { FlowState } from "./state";
|
|
3
|
-
export { FlowObservable } from "./observable";
|
|
4
|
-
export { FlowDerivation } from "./derivation";
|
|
5
|
-
export { FlowEffect } from "./effect";
|
|
6
1
|
export { FlowConstant } from "./constant";
|
|
7
|
-
export {
|
|
8
|
-
export type { FlowGetter } from "./observable";
|
|
9
|
-
export type { FlowWatcher } from "./signal";
|
|
2
|
+
export { FlowDerivation } from "./derivation";
|
|
10
3
|
export type { FlowDisposable } from "./disposable";
|
|
4
|
+
export { isDisposable } from "./disposable";
|
|
5
|
+
export { FlowEffect } from "./effect";
|
|
6
|
+
export { FlowObservable } from "./observable";
|
|
7
|
+
export { FlowSignal } from "./signal";
|
|
8
|
+
export { FlowState } from "./state";
|
|
9
|
+
export { TrackingContext } from "./trackingContext";
|