@ersbeth/picoflow 0.0.1 → 0.2.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/api/doc/index.md +1 -1
- package/api/doc/picoflow.array.md +55 -0
- package/api/doc/picoflow.constant.md +55 -0
- package/api/doc/picoflow.derivation.md +1 -1
- package/api/doc/picoflow.effect.md +1 -1
- package/api/doc/picoflow.flowarray._constructor_.md +49 -0
- package/api/doc/picoflow.flowarray._lastaction.md +13 -0
- package/api/doc/picoflow.flowarray.clear.md +17 -0
- package/api/doc/picoflow.flowarray.dispose.md +55 -0
- package/api/doc/picoflow.flowarray.get.md +19 -0
- package/api/doc/picoflow.flowarray.length.md +13 -0
- package/api/doc/picoflow.flowarray.md +273 -0
- package/api/doc/picoflow.flowarray.pop.md +17 -0
- package/api/doc/picoflow.flowarray.push.md +53 -0
- package/api/doc/picoflow.flowarray.set.md +53 -0
- package/api/doc/picoflow.flowarray.setitem.md +69 -0
- package/api/doc/picoflow.flowarray.shift.md +17 -0
- package/api/doc/picoflow.flowarray.splice.md +85 -0
- package/api/doc/picoflow.flowarray.unshift.md +53 -0
- package/api/doc/picoflow.flowarrayaction.md +37 -0
- package/api/doc/picoflow.flowconstant._constructor_.md +49 -0
- package/api/doc/picoflow.flowconstant.get.md +25 -0
- package/api/doc/picoflow.flowconstant.md +88 -0
- package/api/doc/picoflow.flowderivation._constructor_.md +2 -2
- package/api/doc/picoflow.flowderivation.get.md +2 -2
- package/api/doc/picoflow.flowderivation.md +2 -2
- package/api/doc/picoflow.flowdisposable.dispose.md +55 -0
- package/api/doc/picoflow.flowdisposable.md +43 -0
- package/api/doc/picoflow.floweffect._constructor_.md +7 -2
- package/api/doc/picoflow.floweffect.dispose.md +3 -3
- package/api/doc/picoflow.floweffect.disposed.md +1 -1
- package/api/doc/picoflow.floweffect.md +4 -4
- package/api/doc/picoflow.flowgetter.md +2 -2
- package/api/doc/picoflow.flowmap._lastdeleted.md +1 -1
- package/api/doc/picoflow.flowmap._lastset.md +1 -1
- package/api/doc/picoflow.flowmap.delete.md +6 -2
- package/api/doc/picoflow.flowmap.md +5 -7
- package/api/doc/picoflow.flowmap.setat.md +6 -2
- package/api/doc/picoflow.flowobservable.get.md +3 -3
- package/api/doc/picoflow.flowobservable.md +18 -4
- package/api/doc/picoflow.flowobservable.subscribe.md +55 -0
- package/api/doc/picoflow.flowresource._constructor_.md +2 -18
- package/api/doc/picoflow.flowresource.fetch.md +1 -1
- package/api/doc/picoflow.flowresource.get.md +4 -4
- package/api/doc/picoflow.flowresource.md +4 -4
- package/api/doc/picoflow.flowresourceasync._constructor_.md +49 -0
- package/api/doc/picoflow.flowresourceasync.fetch.md +27 -0
- package/api/doc/picoflow.flowresourceasync.get.md +23 -0
- package/api/doc/picoflow.flowresourceasync.md +100 -0
- package/api/doc/picoflow.flowsignal.dispose.md +42 -8
- package/api/doc/picoflow.flowsignal.disposed.md +2 -2
- package/api/doc/picoflow.flowsignal.md +8 -7
- package/api/doc/picoflow.flowsignal.trigger.md +3 -7
- package/api/doc/picoflow.flowstate.md +4 -52
- package/api/doc/picoflow.flowstate.set.md +5 -5
- package/api/doc/picoflow.flowstream._constructor_.md +3 -19
- package/api/doc/picoflow.flowstream.dispose.md +1 -1
- package/api/doc/picoflow.flowstream.get.md +4 -4
- package/api/doc/picoflow.flowstream.md +5 -5
- package/api/doc/picoflow.flowstreamasync._constructor_.md +54 -0
- package/api/doc/picoflow.flowstreamasync.dispose.md +21 -0
- package/api/doc/picoflow.flowstreamasync.get.md +23 -0
- package/api/doc/picoflow.flowstreamasync.md +100 -0
- package/api/doc/picoflow.flowstreamdisposer.md +13 -0
- package/api/doc/picoflow.flowstreamsetter.md +13 -0
- package/api/doc/picoflow.flowstreamupdater.md +19 -0
- package/api/doc/picoflow.flowwatcher.md +1 -1
- package/api/doc/picoflow.isdisposable.md +55 -0
- package/api/doc/picoflow.map.md +1 -1
- package/api/doc/picoflow.md +149 -13
- package/api/doc/picoflow.resource.md +2 -18
- package/api/doc/picoflow.resourceasync.md +55 -0
- package/api/doc/picoflow.signal.md +1 -1
- package/api/doc/picoflow.state.md +3 -3
- package/api/doc/picoflow.stream.md +2 -18
- package/api/doc/picoflow.streamasync.md +55 -0
- package/api/picoflow.public.api.md +192 -0
- package/api-extractor.json +2 -1
- package/dist/picoflow.js +513 -305
- package/dist/types/advanced/array.d.ts +116 -0
- package/dist/types/advanced/array.d.ts.map +1 -0
- package/dist/types/advanced/index.d.ts +9 -0
- package/dist/types/advanced/index.d.ts.map +1 -0
- package/dist/types/{map.d.ts → advanced/map.d.ts} +12 -12
- package/dist/types/advanced/map.d.ts.map +1 -0
- package/dist/types/advanced/resource.d.ts +39 -0
- package/dist/types/advanced/resource.d.ts.map +1 -0
- package/dist/types/{resource.d.ts → advanced/resourceAsync.d.ts} +6 -11
- package/dist/types/advanced/resourceAsync.d.ts.map +1 -0
- package/dist/types/advanced/stream.d.ts +59 -0
- package/dist/types/advanced/stream.d.ts.map +1 -0
- package/dist/types/advanced/streamAsync.d.ts +43 -0
- package/dist/types/advanced/streamAsync.d.ts.map +1 -0
- package/dist/types/basic/constant.d.ts +32 -0
- package/dist/types/basic/constant.d.ts.map +1 -0
- package/dist/types/basic/derivation.d.ts +40 -0
- package/dist/types/basic/derivation.d.ts.map +1 -0
- package/dist/types/basic/disposable.d.ts +23 -0
- package/dist/types/basic/disposable.d.ts.map +1 -0
- package/dist/types/basic/effect.d.ts +56 -0
- package/dist/types/basic/effect.d.ts.map +1 -0
- package/dist/types/basic/index.d.ts +11 -0
- package/dist/types/basic/index.d.ts.map +1 -0
- package/dist/types/basic/observable.d.ts +34 -0
- package/dist/types/basic/observable.d.ts.map +1 -0
- package/dist/types/basic/signal.d.ts +40 -0
- package/dist/types/basic/signal.d.ts.map +1 -0
- package/dist/types/basic/state.d.ts +26 -0
- package/dist/types/basic/state.d.ts.map +1 -0
- package/dist/types/creators.d.ts +38 -13
- package/dist/types/creators.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -9
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/advanced/array.ts +224 -0
- package/src/advanced/index.ts +12 -0
- package/src/{map.ts → advanced/map.ts} +14 -14
- package/src/advanced/resource.ts +56 -0
- package/src/{resource.ts → advanced/resourceAsync.ts} +9 -16
- package/src/advanced/stream.ts +87 -0
- package/src/advanced/streamAsync.ts +82 -0
- package/src/basic/constant.ts +64 -0
- package/src/basic/derivation.ts +86 -0
- package/src/basic/disposable.ts +27 -0
- package/src/basic/effect.ts +96 -0
- package/src/basic/index.ts +10 -0
- package/src/basic/observable.ts +51 -0
- package/src/basic/signal.ts +117 -0
- package/src/basic/state.ts +39 -0
- package/src/creators.ts +66 -15
- package/src/index.ts +26 -11
- package/test/array.test.ts +620 -0
- package/test/constant.test.ts +46 -0
- package/test/derivation.test.ts +30 -6
- package/test/effect.test.ts +29 -0
- package/test/map.test.ts +38 -0
- package/test/resource.test.ts +18 -16
- package/test/resourceAsync.test.ts +108 -0
- package/test/signal.test.ts +18 -1
- package/test/state.test.ts +107 -2
- package/test/stream.test.ts +38 -13
- package/test/streamAsync.test.ts +194 -0
- package/tsconfig.json +3 -1
- package/api/doc/picoflow.flowdisposer.md +0 -13
- package/api/doc/picoflow.flowsetter.md +0 -13
- package/api/doc/picoflow.flowstate._constructor_.md +0 -49
- package/api/doc/picoflow.flowstate.get.md +0 -23
- package/api/doc/picoflow.flowupdater.md +0 -19
- package/api/picoflow.api.md +0 -145
- package/dist/types/derivation.d.ts +0 -58
- package/dist/types/derivation.d.ts.map +0 -1
- package/dist/types/effect.d.ts +0 -108
- package/dist/types/effect.d.ts.map +0 -1
- package/dist/types/map.d.ts.map +0 -1
- package/dist/types/observable.d.ts +0 -40
- package/dist/types/observable.d.ts.map +0 -1
- package/dist/types/resource.d.ts.map +0 -1
- package/dist/types/signal.d.ts +0 -111
- package/dist/types/signal.d.ts.map +0 -1
- package/dist/types/state.d.ts +0 -39
- package/dist/types/state.d.ts.map +0 -1
- package/dist/types/stream.d.ts +0 -71
- package/dist/types/stream.d.ts.map +0 -1
- package/src/derivation.ts +0 -96
- package/src/effect.ts +0 -152
- package/src/observable.ts +0 -50
- package/src/signal.ts +0 -166
- package/src/state.ts +0 -52
- package/src/stream.ts +0 -99
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { FlowObservable } from "../basic/";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A function type that sets a new value for the reactive stream.
|
|
5
|
+
* @typeparam T - The type of the value.
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export type FlowStreamSetter<T> = (value: T) => void;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A function type that disposes of a resource.
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export type FlowStreamDisposer = () => void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A function type that updates a stream and returns a disposer for cleanup.
|
|
18
|
+
* @remarks
|
|
19
|
+
* The updater receives a setter function to update the stream's value.
|
|
20
|
+
* It should return a disposer function to release any resources or subscriptions.
|
|
21
|
+
* @typeparam T - The type of the stream value.
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
export type FlowStreamUpdater<T> = (
|
|
25
|
+
set: FlowStreamSetter<T>,
|
|
26
|
+
) => FlowStreamDisposer;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Represents a reactive stream that updates its value based on an updater function.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* FlowStream extends FlowObservable to encapsulate an update mechanism provided by an external updater function.
|
|
33
|
+
* The updater is invoked during construction with a setter, and it returns a disposer to be called upon disposal.
|
|
34
|
+
* Note: The stream's current value may be undefined until explicitly set.
|
|
35
|
+
*
|
|
36
|
+
* @typeparam T - The type of the stream's value.
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export class FlowStream<T> extends FlowObservable<T | undefined> {
|
|
40
|
+
/**
|
|
41
|
+
* Creates a new FlowStream.
|
|
42
|
+
* @param updater - A function that receives a setter to update the stream's value.
|
|
43
|
+
* It should return a disposer function that will be called upon disposal.
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
constructor(updater: FlowStreamUpdater<T>) {
|
|
47
|
+
super();
|
|
48
|
+
this._disposer = updater((value: T) => {
|
|
49
|
+
this._set(value);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Retrieves the current value of the stream.
|
|
55
|
+
* @returns The current value, or undefined if no value has been set yet.
|
|
56
|
+
* @throws Error if the stream is disposed.
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
public get(): T | undefined {
|
|
60
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
61
|
+
return this._value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Disposes the stream, releasing all resources.
|
|
66
|
+
* @remarks
|
|
67
|
+
* In addition to disposing the underlying observable, this method calls the disposer
|
|
68
|
+
* returned by the updater.
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
public override dispose(): void {
|
|
72
|
+
super.dispose();
|
|
73
|
+
this._disposer();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* INTERNAL ------------------------------------------------------ */
|
|
77
|
+
|
|
78
|
+
private _disposer: FlowStreamDisposer;
|
|
79
|
+
|
|
80
|
+
private _set(value: T): void {
|
|
81
|
+
/* v8 ignore next */
|
|
82
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
83
|
+
if (value === this._value) return;
|
|
84
|
+
this._value = value;
|
|
85
|
+
this._notify();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { FlowObservable } from "../basic";
|
|
2
|
+
import type { FlowStreamDisposer, FlowStreamUpdater } from "./stream";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Represents an asynchronous reactive stream that updates its value based on an updater function.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* A FlowStreamAsync extends FlowObservable and encapsulates a mechanism for asynchronously updating its value.
|
|
9
|
+
* The updater is invoked during construction with a setter, and its returned disposer is called upon disposal.
|
|
10
|
+
* Note: The stream's current value may be undefined until it is set.
|
|
11
|
+
*
|
|
12
|
+
* @typeparam T - The type of the stream's value.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export class FlowStreamAsync<T> extends FlowObservable<Promise<T>> {
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new asynchronous FlowStream.
|
|
18
|
+
* @param updater - A function that receives a setter to update the stream's value.
|
|
19
|
+
* It should return a disposer function that will be called upon disposal.
|
|
20
|
+
* @remarks The updater function can invoke the setter asynchronously to update the stream.
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
constructor(updater: FlowStreamUpdater<T>) {
|
|
24
|
+
super();
|
|
25
|
+
this._disposer = updater((value: T) => {
|
|
26
|
+
this._set(value);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this._value = new Promise((resolve) => {
|
|
30
|
+
this._resolve = resolve;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Retrieves the current value of the stream as a Promise.
|
|
36
|
+
* @returns A Promise that resolves to the current value.
|
|
37
|
+
* @throws Error if the stream is disposed.
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
public get(): Promise<T> {
|
|
41
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
42
|
+
return this._value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Disposes the stream, releasing all resources.
|
|
47
|
+
* @remarks In addition to disposing the underlying observable, this method calls the disposer
|
|
48
|
+
* returned by the updater.
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
public override dispose(): void {
|
|
52
|
+
super.dispose();
|
|
53
|
+
this._disposer();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* INTERNAL ------------------------------------------------------ */
|
|
57
|
+
|
|
58
|
+
private _initialized = false;
|
|
59
|
+
private _awaitedValue?: T;
|
|
60
|
+
|
|
61
|
+
private _resolve!: (value: T) => void;
|
|
62
|
+
private _disposer: FlowStreamDisposer;
|
|
63
|
+
|
|
64
|
+
private _set(value: T): void {
|
|
65
|
+
/* v8 ignore next */
|
|
66
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
67
|
+
|
|
68
|
+
if (!this._initialized) {
|
|
69
|
+
this._resolve(value);
|
|
70
|
+
this._initialized = true;
|
|
71
|
+
this._awaitedValue = value;
|
|
72
|
+
this._notify();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (value === this._awaitedValue) return;
|
|
77
|
+
|
|
78
|
+
this._value = Promise.resolve(value);
|
|
79
|
+
this._awaitedValue = value;
|
|
80
|
+
this._notify();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { FlowObservable } from "./observable";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a reactive and immutable constant value computed lazily upon first access.
|
|
5
|
+
*
|
|
6
|
+
* @remarks This class extends FlowObservable and supports initializing the constant using either a direct value
|
|
7
|
+
* or a lazy initializer function. Once computed, the value is cached for all subsequent accesses.
|
|
8
|
+
*
|
|
9
|
+
* @typeparam T - The type of the constant value.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export class FlowConstant<T> extends FlowObservable<T> {
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new FlowConstant instance.
|
|
16
|
+
*
|
|
17
|
+
* @param value - Either a direct value of type T or a function returning a value of type T for lazy initialization.
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
constructor(value: T | (() => T)) {
|
|
21
|
+
super();
|
|
22
|
+
this._initEager(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Retrieves the constant value, computing it lazily if needed.
|
|
27
|
+
*
|
|
28
|
+
* Accessing this method will initialize the value if it has not been computed already.
|
|
29
|
+
* Throws an error if the instance has been disposed or if lazy initialization fails.
|
|
30
|
+
*
|
|
31
|
+
* @returns The cached constant value.
|
|
32
|
+
* @throws Error if the constant is disposed or cannot be initialized.
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
get(): T {
|
|
36
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
37
|
+
this._initLazy();
|
|
38
|
+
return this._value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* INTERNAL --------------------------------------------------------- */
|
|
42
|
+
|
|
43
|
+
/*@internal*/ protected _initialized = false;
|
|
44
|
+
/*@internal*/ protected _init?: () => T;
|
|
45
|
+
|
|
46
|
+
/*@internal*/ protected _initEager(value: T | (() => T)): void {
|
|
47
|
+
if (typeof value === "function") {
|
|
48
|
+
this._init = value as () => T;
|
|
49
|
+
} else {
|
|
50
|
+
this._value = value;
|
|
51
|
+
this._initialized = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/*@internal*/ protected _initLazy(): void {
|
|
56
|
+
if (!this._initialized && this._init) {
|
|
57
|
+
this._value = this._init();
|
|
58
|
+
this._initialized = true;
|
|
59
|
+
}
|
|
60
|
+
/* v8 ignore next 2 */
|
|
61
|
+
if (!this._initialized)
|
|
62
|
+
throw new Error("[PicoFlow] Primitive can't be initialized");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type FlowGetter, FlowObservable } from "./observable";
|
|
2
|
+
import type { FlowWatcher } from "./signal";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Represents a reactive derivation whose value is computed based on other reactive signals.
|
|
6
|
+
* @remarks
|
|
7
|
+
* It tracks dependencies automatically and recomputes its value when any dependency changes.
|
|
8
|
+
* Use FlowDerivation to create derived values in a reactive manner. It lazily initializes the computed value,
|
|
9
|
+
* ensuring that computations only occur when necessary.
|
|
10
|
+
* @typeparam T - The type of the computed value.
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export class FlowDerivation<T> extends FlowObservable<T> {
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new FlowDerivation.
|
|
16
|
+
* @param compute - A function that computes the derived value. It is provided with two parameters:
|
|
17
|
+
* a getter and a watcher that respect dependency tracking.
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
constructor(compute: (get: FlowGetter, watch: FlowWatcher) => T) {
|
|
21
|
+
super();
|
|
22
|
+
this._initEager(compute);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Gets the current derived value.
|
|
27
|
+
* @returns The current computed value.
|
|
28
|
+
* @remarks
|
|
29
|
+
* This method lazily initializes and updates the derivation if it is marked as dirty. It throws an error
|
|
30
|
+
* if the derivation has been disposed.
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
public get(): T {
|
|
34
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
35
|
+
this._initLazy();
|
|
36
|
+
this._compute();
|
|
37
|
+
return this._value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* INTERNAL --------------------------------------------------------- */
|
|
41
|
+
|
|
42
|
+
private _initialized = false;
|
|
43
|
+
private _dirty = false;
|
|
44
|
+
|
|
45
|
+
private _trackedGet: FlowGetter = (observable) => observable._getFrom(this);
|
|
46
|
+
private _trackedWatch: FlowWatcher = (signal) => signal._watchFrom(this);
|
|
47
|
+
private _untrackedGet: FlowGetter = (observable) => observable.get();
|
|
48
|
+
private _untrackedWatch: FlowWatcher = (signal) => signal._watch();
|
|
49
|
+
private _trackedCompute!: () => T;
|
|
50
|
+
private _untrackedCompute!: () => T;
|
|
51
|
+
|
|
52
|
+
private _initEager(
|
|
53
|
+
compute: (get: FlowGetter, watch: FlowWatcher) => T,
|
|
54
|
+
): void {
|
|
55
|
+
this._trackedCompute = () =>
|
|
56
|
+
compute(this._trackedGet, this._trackedWatch);
|
|
57
|
+
this._untrackedCompute = () =>
|
|
58
|
+
compute(this._untrackedGet, this._untrackedWatch);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private _initLazy(): void {
|
|
62
|
+
if (!this._initialized) {
|
|
63
|
+
this._value = this._trackedCompute();
|
|
64
|
+
this._initialized = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* @internal */ _compute(): void {
|
|
69
|
+
if (this._dirty) {
|
|
70
|
+
this._value = this._untrackedCompute();
|
|
71
|
+
this._dirty = false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* @internal */ override _notify(): void {
|
|
76
|
+
this._dirty = true;
|
|
77
|
+
super._notify();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* @internal */ override _watch(): void {
|
|
81
|
+
/* v8 ignore next 1 */
|
|
82
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
83
|
+
this._initLazy();
|
|
84
|
+
this._compute();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents an object with a disposable lifecycle.
|
|
3
|
+
* @remarks
|
|
4
|
+
* Objects implementing this interface require explicit resource disposal.
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export interface FlowDisposable {
|
|
8
|
+
/**
|
|
9
|
+
* Disposes resources held by this object.
|
|
10
|
+
* @param options - Options to specify disposal behavior.
|
|
11
|
+
*/
|
|
12
|
+
dispose(options?: { self: boolean }): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks whether an object implements the FlowDisposable interface.
|
|
17
|
+
* @param obj - The object to test.
|
|
18
|
+
* @returns True if the object has a dispose method, otherwise false.
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export function isDisposable(obj: unknown): obj is FlowDisposable {
|
|
22
|
+
return (
|
|
23
|
+
obj !== null &&
|
|
24
|
+
obj !== undefined &&
|
|
25
|
+
typeof (obj as FlowDisposable).dispose === "function"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { FlowGetter } from "./observable";
|
|
2
|
+
import type { FlowSignal, FlowWatcher } from "./signal";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Represents a reactive effect that executes side-effect functions based
|
|
6
|
+
* on its tracked dependencies.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* The FlowEffect executes an apply function that performs side effects,
|
|
10
|
+
* running initially in a tracked mode to register dependencies and then in
|
|
11
|
+
* an untracked mode to re-execute the effect on updates.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export class FlowEffect {
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new FlowEffect.
|
|
18
|
+
*
|
|
19
|
+
* @param apply - A side-effect function that receives a getter and a watcher to
|
|
20
|
+
* access and register dependencies on reactive observables and signals.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* The provided function is executed immediately in a tracked mode to collect dependencies.
|
|
24
|
+
* On subsequent executions, it runs in an untracked mode.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
constructor(apply: (get: FlowGetter, watch: FlowWatcher) => void) {
|
|
29
|
+
this._trackedExec = () => apply(this._trackedGet, this._trackedWatch);
|
|
30
|
+
this._untrackedExec = () =>
|
|
31
|
+
apply(this._untrackedGet, this._untrackedWatch);
|
|
32
|
+
this._exec();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Disposes the effect, unregistering all its tracked dependencies.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* Once disposed, the effect must no longer be used. Trying to dispose an effect
|
|
40
|
+
* that is already disposed will throw an error.
|
|
41
|
+
*
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
public dispose(): void {
|
|
45
|
+
if (this._disposed) throw new Error("[PicoFlow] Effect is disposed");
|
|
46
|
+
Array.from(this._dependencies).forEach((dependency) => {
|
|
47
|
+
this._unregisterDependency(dependency);
|
|
48
|
+
});
|
|
49
|
+
this._disposed = true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Indicates whether this effect has been disposed.
|
|
54
|
+
*
|
|
55
|
+
* @returns A boolean value that is true if the effect is disposed, false otherwise.
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
public get disposed(): boolean {
|
|
60
|
+
return this._disposed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* INTERNAL ------------------------------------------------------------ */
|
|
64
|
+
|
|
65
|
+
private _disposed = false;
|
|
66
|
+
private _initialized = false;
|
|
67
|
+
private _dependencies = new Set<FlowSignal>();
|
|
68
|
+
|
|
69
|
+
private _trackedGet: FlowGetter = (observable) => observable._getFrom(this);
|
|
70
|
+
private _trackedWatch: FlowWatcher = (signal) => signal._watchFrom(this);
|
|
71
|
+
private _untrackedGet: FlowGetter = (observable) => observable.get();
|
|
72
|
+
private _untrackedWatch: FlowWatcher = (signal) => signal._watch();
|
|
73
|
+
private _trackedExec: () => void;
|
|
74
|
+
private _untrackedExec: () => void;
|
|
75
|
+
|
|
76
|
+
/*@internal*/ _exec(): void {
|
|
77
|
+
if (this._disposed)
|
|
78
|
+
/* v8 ignore next 1 */
|
|
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
|
+
}
|
|
86
|
+
|
|
87
|
+
/*@internal*/ _registerDependency(dependency: FlowSignal): void {
|
|
88
|
+
this._dependencies.add(dependency);
|
|
89
|
+
dependency._registerEffect(this);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/*@internal*/ _unregisterDependency(dependency: FlowSignal): void {
|
|
93
|
+
this._dependencies.delete(dependency);
|
|
94
|
+
dependency._unregisterEffect(this);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export { FlowConstant } from "./constant";
|
|
7
|
+
export { isDisposable } from "./disposable";
|
|
8
|
+
export type { FlowGetter } from "./observable";
|
|
9
|
+
export type { FlowWatcher } from "./signal";
|
|
10
|
+
export type { FlowDisposable } from "./disposable";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { FlowEffect } from "./effect";
|
|
2
|
+
import { FlowSignal } from "./signal";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A function that retrieves the current value from a FlowObservable.
|
|
6
|
+
* @typeparam T - The type of the value held by the observable.
|
|
7
|
+
* @param observable - The FlowObservable instance to retrieve the value from.
|
|
8
|
+
* @returns The current value of the observable.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export type FlowGetter = <T>(observable: FlowObservable<T>) => T;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Represents a reactive observable that holds and tracks a value.
|
|
15
|
+
*
|
|
16
|
+
*
|
|
17
|
+
* @remarks Subclasses must implement the {@link FlowObservable.get} method to return the current value.
|
|
18
|
+
* @typeparam T - The type of the value held by the observable.
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export abstract class FlowObservable<T> extends FlowSignal {
|
|
22
|
+
/**
|
|
23
|
+
* Retrieves the current value stored in the observable.
|
|
24
|
+
* Subclasses must override this method to provide the current value.
|
|
25
|
+
* @returns The current value of type T.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
abstract get(): T;
|
|
29
|
+
|
|
30
|
+
/* INTERNAL -------------------------------------------*/
|
|
31
|
+
|
|
32
|
+
/*@internal*/ protected _value!: T;
|
|
33
|
+
|
|
34
|
+
/*@internal*/ _getFrom(listener: FlowObservable<unknown> | FlowEffect): T {
|
|
35
|
+
listener._registerDependency(this);
|
|
36
|
+
return this.get();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Subscribes a listener function to changes of the observable.
|
|
41
|
+
* The listener is executed immediately with the current value and on subsequent updates.
|
|
42
|
+
* @param listener - A callback function that receives the new value.
|
|
43
|
+
* @returns A disposer function to cancel the subscription.
|
|
44
|
+
*/
|
|
45
|
+
subscribe(listener: (value: T) => void): () => void {
|
|
46
|
+
const effect = new FlowEffect((get) => {
|
|
47
|
+
listener(get(this));
|
|
48
|
+
});
|
|
49
|
+
return () => effect.dispose();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { FlowDisposable } from "./disposable";
|
|
2
|
+
import type { FlowEffect } from "./effect";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A function for watching a FlowSignal.
|
|
6
|
+
* @param signal - The FlowSignal that is being observed.
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export type FlowWatcher = (signal: FlowSignal) => void;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents a reactive signal.
|
|
13
|
+
*
|
|
14
|
+
* @remarks Use FlowSignal to create reactive streams that notify listeners and execute associated effects.
|
|
15
|
+
* Signals can be triggered and disposed. Once disposed, interactions with the signal will throw errors.
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export class FlowSignal implements FlowDisposable {
|
|
19
|
+
/**
|
|
20
|
+
* Triggers the FlowSignal.
|
|
21
|
+
* Notifies all registered listeners and schedules execution of associated effects.
|
|
22
|
+
* @throws If the FlowSignal has already been disposed.
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
public trigger(): void {
|
|
26
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
27
|
+
this._notify();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Disposes the FlowSignal.
|
|
32
|
+
* Cleans up all registered effects, listeners, and dependencies.
|
|
33
|
+
* Once disposed, further usage of the signal will throw an error.
|
|
34
|
+
* @throws If the FlowSignal is already disposed.
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
public dispose(options?: { self: boolean }): void {
|
|
38
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
39
|
+
if (options?.self) {
|
|
40
|
+
Array.from(this._effects).forEach((effect) =>
|
|
41
|
+
effect._unregisterDependency(this),
|
|
42
|
+
);
|
|
43
|
+
Array.from(this._listeners).forEach((listener) =>
|
|
44
|
+
listener._unregisterDependency(this),
|
|
45
|
+
);
|
|
46
|
+
} else {
|
|
47
|
+
Array.from(this._effects).forEach((effect) => effect.dispose());
|
|
48
|
+
Array.from(this._listeners).forEach((listener) =>
|
|
49
|
+
listener.dispose(),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
Array.from(this._dependencies).forEach((dependency) => {
|
|
53
|
+
this._unregisterDependency(dependency);
|
|
54
|
+
});
|
|
55
|
+
this._disposed = true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Indicates whether the FlowSignal has been disposed.
|
|
60
|
+
* @remarks Once disposed, the signal should not be used.
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
public get disposed(): boolean {
|
|
64
|
+
return this._disposed;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* INTERNAL ------------------------------------------------------------- */
|
|
68
|
+
|
|
69
|
+
/*@internal*/ protected _disposed = false;
|
|
70
|
+
|
|
71
|
+
/*@internal*/ protected _dependencies = new Set<FlowSignal>();
|
|
72
|
+
|
|
73
|
+
/*@internal*/ protected _listeners = new Set<FlowSignal>();
|
|
74
|
+
|
|
75
|
+
/*@internal*/ protected _effects = new Set<FlowEffect>();
|
|
76
|
+
|
|
77
|
+
/*@internal*/ _watch(): void {
|
|
78
|
+
/* v8 ignore next 1 */
|
|
79
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/*@internal*/ _notify(): void {
|
|
83
|
+
this._listeners.forEach((listener) => listener._notify());
|
|
84
|
+
this._effects.forEach((effect) => effect._exec());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/*@internal*/ _watchFrom(listener: FlowSignal | FlowEffect): void {
|
|
88
|
+
listener._registerDependency(this);
|
|
89
|
+
this._watch();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/*@internal*/ _registerDependency(dependency: FlowSignal): void {
|
|
93
|
+
this._dependencies.add(dependency);
|
|
94
|
+
dependency._registerListener(this);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/*@internal*/ _unregisterDependency(dependency: FlowSignal): void {
|
|
98
|
+
this._dependencies.delete(dependency);
|
|
99
|
+
dependency._unregisterListener(this);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/*@internal*/ _registerListener(signal: FlowSignal): void {
|
|
103
|
+
this._listeners.add(signal);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/*@internal*/ _unregisterListener(signal: FlowSignal): void {
|
|
107
|
+
this._listeners.delete(signal);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/*@internal*/ _registerEffect(effect: FlowEffect): void {
|
|
111
|
+
this._effects.add(effect);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/*@internal*/ _unregisterEffect(effect: FlowEffect): void {
|
|
115
|
+
this._effects.delete(effect);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FlowConstant } from "./constant";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a reactive state that holds a mutable value.
|
|
5
|
+
*
|
|
6
|
+
* @typeparam T - The type of the state value.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* FlowState extends FlowConstant, which provides the {@link FlowConstant.get} method to read
|
|
10
|
+
* the current state. Use the {@link FlowState.set} method to update the state. When the state is updated,
|
|
11
|
+
* subscribers are notified automatically. This class notifies subscribers only when the value changes.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export class FlowState<T> extends FlowConstant<T> {
|
|
16
|
+
/**
|
|
17
|
+
* Updates the state with a new value.
|
|
18
|
+
* @param value - A new value or a callback function that computes a new value based on the current state.
|
|
19
|
+
* @remarks
|
|
20
|
+
* If the computed new value is strictly equal to the current state value, no change is made and subscribers
|
|
21
|
+
* will not be notified. Otherwise, the state is updated and all subscribers are informed of the change.
|
|
22
|
+
* @throws Error if the state has been disposed.
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
set(value: T | ((current: T) => T)): void {
|
|
26
|
+
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
27
|
+
|
|
28
|
+
// compute new value
|
|
29
|
+
const next =
|
|
30
|
+
typeof value === "function"
|
|
31
|
+
? (value as (current: T) => T)(this._value)
|
|
32
|
+
: value;
|
|
33
|
+
|
|
34
|
+
// apply new value
|
|
35
|
+
if (next === this._value) return;
|
|
36
|
+
this._value = next;
|
|
37
|
+
this._notify();
|
|
38
|
+
}
|
|
39
|
+
}
|