@ersbeth/picoflow 1.0.0 → 1.1.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/.cursor/plans/unifier-flowresource-avec-flowderivation-c9506e24.plan.md +372 -0
- package/README.md +25 -171
- package/biome.json +4 -1
- package/dist/picoflow.js +1129 -661
- package/dist/types/flow/base/flowDisposable.d.ts +67 -0
- package/dist/types/flow/base/flowDisposable.d.ts.map +1 -0
- package/dist/types/flow/base/flowEffect.d.ts +127 -0
- package/dist/types/flow/base/flowEffect.d.ts.map +1 -0
- package/dist/types/flow/base/flowGraph.d.ts +97 -0
- package/dist/types/flow/base/flowGraph.d.ts.map +1 -0
- package/dist/types/flow/base/flowSignal.d.ts +134 -0
- package/dist/types/flow/base/flowSignal.d.ts.map +1 -0
- package/dist/types/flow/base/flowTracker.d.ts +15 -0
- package/dist/types/flow/base/flowTracker.d.ts.map +1 -0
- package/dist/types/flow/base/index.d.ts +7 -0
- package/dist/types/flow/base/index.d.ts.map +1 -0
- package/dist/types/flow/base/utils.d.ts +20 -0
- package/dist/types/flow/base/utils.d.ts.map +1 -0
- package/dist/types/{advanced/array.d.ts → flow/collections/flowArray.d.ts} +50 -12
- package/dist/types/flow/collections/flowArray.d.ts.map +1 -0
- package/dist/types/flow/collections/flowMap.d.ts +224 -0
- package/dist/types/flow/collections/flowMap.d.ts.map +1 -0
- package/dist/types/flow/collections/index.d.ts +3 -0
- package/dist/types/flow/collections/index.d.ts.map +1 -0
- package/dist/types/flow/index.d.ts +4 -0
- package/dist/types/flow/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts +137 -0
- package/dist/types/flow/nodes/async/flowConstantAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts +137 -0
- package/dist/types/flow/nodes/async/flowDerivationAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts +343 -0
- package/dist/types/flow/nodes/async/flowNodeAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts +81 -0
- package/dist/types/flow/nodes/async/flowReadonlyAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts +111 -0
- package/dist/types/flow/nodes/async/flowStateAsync.d.ts.map +1 -0
- package/dist/types/flow/nodes/async/index.d.ts +6 -0
- package/dist/types/flow/nodes/async/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/index.d.ts +3 -0
- package/dist/types/flow/nodes/index.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowConstant.d.ts +108 -0
- package/dist/types/flow/nodes/sync/flowConstant.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts +100 -0
- package/dist/types/flow/nodes/sync/flowDerivation.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowNode.d.ts +314 -0
- package/dist/types/flow/nodes/sync/flowNode.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts +57 -0
- package/dist/types/flow/nodes/sync/flowReadonly.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/flowState.d.ts +96 -0
- package/dist/types/flow/nodes/sync/flowState.d.ts.map +1 -0
- package/dist/types/flow/nodes/sync/index.d.ts +6 -0
- package/dist/types/flow/nodes/sync/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/solid/converters.d.ts +34 -44
- package/dist/types/solid/converters.d.ts.map +1 -1
- package/dist/types/solid/primitives.d.ts +1 -0
- package/dist/types/solid/primitives.d.ts.map +1 -1
- package/docs/.vitepress/config.mts +1 -1
- package/docs/api/typedoc-sidebar.json +81 -1
- package/package.json +60 -58
- package/src/flow/base/flowDisposable.ts +71 -0
- package/src/flow/base/flowEffect.ts +171 -0
- package/src/flow/base/flowGraph.ts +288 -0
- package/src/flow/base/flowSignal.ts +207 -0
- package/src/flow/base/flowTracker.ts +17 -0
- package/src/flow/base/index.ts +6 -0
- package/src/flow/base/utils.ts +19 -0
- package/src/flow/collections/flowArray.ts +409 -0
- package/src/flow/collections/flowMap.ts +398 -0
- package/src/flow/collections/index.ts +2 -0
- package/src/flow/index.ts +3 -0
- package/src/flow/nodes/async/flowConstantAsync.ts +142 -0
- package/src/flow/nodes/async/flowDerivationAsync.ts +143 -0
- package/src/flow/nodes/async/flowNodeAsync.ts +474 -0
- package/src/flow/nodes/async/flowReadonlyAsync.ts +81 -0
- package/src/flow/nodes/async/flowStateAsync.ts +116 -0
- package/src/flow/nodes/async/index.ts +5 -0
- package/src/flow/nodes/await/advanced/index.ts +5 -0
- package/src/{advanced → flow/nodes/await/advanced}/resource.ts +37 -3
- package/src/{advanced → flow/nodes/await/advanced}/resourceAsync.ts +35 -3
- package/src/{advanced → flow/nodes/await/advanced}/stream.ts +40 -2
- package/src/{advanced → flow/nodes/await/advanced}/streamAsync.ts +38 -3
- package/src/flow/nodes/await/flowConstantAwait.ts +154 -0
- package/src/flow/nodes/await/flowDerivationAwait.ts +154 -0
- package/src/flow/nodes/await/flowNodeAwait.ts +508 -0
- package/src/flow/nodes/await/flowReadonlyAwait.ts +89 -0
- package/src/flow/nodes/await/flowStateAwait.ts +130 -0
- package/src/flow/nodes/await/index.ts +5 -0
- package/src/flow/nodes/index.ts +3 -0
- package/src/flow/nodes/sync/flowConstant.ts +111 -0
- package/src/flow/nodes/sync/flowDerivation.ts +105 -0
- package/src/flow/nodes/sync/flowNode.ts +439 -0
- package/src/flow/nodes/sync/flowReadonly.ts +57 -0
- package/src/flow/nodes/sync/flowState.ts +101 -0
- package/src/flow/nodes/sync/index.ts +5 -0
- package/src/index.ts +1 -47
- package/src/solid/converters.ts +59 -198
- package/src/solid/primitives.ts +4 -0
- package/test/base/flowEffect.test.ts +108 -0
- package/test/base/flowGraph.test.ts +485 -0
- package/test/base/flowSignal.test.ts +372 -0
- package/test/collections/flowArray.asyncStates.test.ts +1553 -0
- package/test/collections/flowArray.scalars.test.ts +1129 -0
- package/test/collections/flowArray.states.test.ts +1365 -0
- package/test/collections/flowMap.asyncStates.test.ts +1105 -0
- package/test/collections/flowMap.scalars.test.ts +877 -0
- package/test/collections/flowMap.states.test.ts +1097 -0
- package/test/nodes/async/flowConstantAsync.test.ts +860 -0
- package/test/nodes/async/flowDerivationAsync.test.ts +1517 -0
- package/test/nodes/async/flowStateAsync.test.ts +1387 -0
- package/test/{resource.test.ts → nodes/await/advanced/resource.test.ts} +21 -19
- package/test/{resourceAsync.test.ts → nodes/await/advanced/resourceAsync.test.ts} +3 -1
- package/test/{stream.test.ts → nodes/await/advanced/stream.test.ts} +30 -28
- package/test/{streamAsync.test.ts → nodes/await/advanced/streamAsync.test.ts} +16 -14
- package/test/nodes/await/flowConstantAwait.test.ts +643 -0
- package/test/nodes/await/flowDerivationAwait.test.ts +1583 -0
- package/test/nodes/await/flowStateAwait.test.ts +999 -0
- package/test/nodes/mixed/derivation.test.ts +1527 -0
- package/test/nodes/sync/flowConstant.test.ts +620 -0
- package/test/nodes/sync/flowDerivation.test.ts +1373 -0
- package/test/nodes/sync/flowState.test.ts +945 -0
- package/test/solid/converters.test.ts +721 -0
- package/test/solid/primitives.test.ts +1031 -0
- package/tsconfig.json +2 -1
- package/vitest.config.ts +7 -1
- package/IMPLEMENTATION_GUIDE.md +0 -1578
- package/dist/types/advanced/array.d.ts.map +0 -1
- package/dist/types/advanced/index.d.ts +0 -9
- package/dist/types/advanced/index.d.ts.map +0 -1
- package/dist/types/advanced/map.d.ts +0 -166
- package/dist/types/advanced/map.d.ts.map +0 -1
- package/dist/types/advanced/resource.d.ts +0 -78
- package/dist/types/advanced/resource.d.ts.map +0 -1
- package/dist/types/advanced/resourceAsync.d.ts +0 -56
- package/dist/types/advanced/resourceAsync.d.ts.map +0 -1
- package/dist/types/advanced/stream.d.ts +0 -117
- package/dist/types/advanced/stream.d.ts.map +0 -1
- package/dist/types/advanced/streamAsync.d.ts +0 -97
- package/dist/types/advanced/streamAsync.d.ts.map +0 -1
- package/dist/types/basic/constant.d.ts +0 -60
- package/dist/types/basic/constant.d.ts.map +0 -1
- package/dist/types/basic/derivation.d.ts +0 -89
- package/dist/types/basic/derivation.d.ts.map +0 -1
- package/dist/types/basic/disposable.d.ts +0 -82
- package/dist/types/basic/disposable.d.ts.map +0 -1
- package/dist/types/basic/effect.d.ts +0 -67
- package/dist/types/basic/effect.d.ts.map +0 -1
- package/dist/types/basic/index.d.ts +0 -10
- package/dist/types/basic/index.d.ts.map +0 -1
- package/dist/types/basic/observable.d.ts +0 -83
- package/dist/types/basic/observable.d.ts.map +0 -1
- package/dist/types/basic/signal.d.ts +0 -69
- package/dist/types/basic/signal.d.ts.map +0 -1
- package/dist/types/basic/state.d.ts +0 -47
- package/dist/types/basic/state.d.ts.map +0 -1
- package/dist/types/basic/trackingContext.d.ts +0 -33
- package/dist/types/basic/trackingContext.d.ts.map +0 -1
- package/dist/types/creators.d.ts +0 -340
- package/dist/types/creators.d.ts.map +0 -1
- package/src/advanced/array.ts +0 -222
- package/src/advanced/index.ts +0 -12
- package/src/advanced/map.ts +0 -193
- package/src/basic/constant.ts +0 -97
- package/src/basic/derivation.ts +0 -147
- package/src/basic/disposable.ts +0 -86
- package/src/basic/effect.ts +0 -104
- package/src/basic/index.ts +0 -9
- package/src/basic/observable.ts +0 -109
- package/src/basic/signal.ts +0 -145
- package/src/basic/state.ts +0 -60
- package/src/basic/trackingContext.ts +0 -45
- package/src/creators.ts +0 -395
- package/test/array.test.ts +0 -600
- package/test/constant.test.ts +0 -44
- package/test/derivation.test.ts +0 -539
- package/test/effect.test.ts +0 -29
- package/test/map.test.ts +0 -240
- package/test/signal.test.ts +0 -72
- package/test/state.test.ts +0 -212
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FlowObservableAsync } from "../async";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Represents a reactive resource that asynchronously fetches its value and returns `T | undefined`.
|
|
@@ -53,7 +53,7 @@ import { FlowObservable } from "../basic";
|
|
|
53
53
|
*
|
|
54
54
|
* @public
|
|
55
55
|
*/
|
|
56
|
-
export class FlowResource<T> extends
|
|
56
|
+
export class FlowResource<T> extends FlowObservableAsync<T | undefined> {
|
|
57
57
|
/**
|
|
58
58
|
* Creates a new FlowResource.
|
|
59
59
|
*
|
|
@@ -91,10 +91,44 @@ export class FlowResource<T> extends FlowObservable<T | undefined> {
|
|
|
91
91
|
const value = await this._fetch();
|
|
92
92
|
if (value === this._value) return;
|
|
93
93
|
this._value = value;
|
|
94
|
-
this.
|
|
94
|
+
this.trigger();
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/* INTERNAL ------------------------------------------------ */
|
|
98
98
|
|
|
99
99
|
private _fetch: () => Promise<T>;
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Creates a new reactive resource that asynchronously fetches its value, returning `T | undefined`.
|
|
104
|
+
*
|
|
105
|
+
* @typeParam T - The type of the resource value.
|
|
106
|
+
* @param fn - An asynchronous function that fetches the resource value.
|
|
107
|
+
* @returns A new instance of {@link FlowResource}.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* A resource manages async data fetching with reactive updates. Unlike {@link resourceAsync},
|
|
111
|
+
* this returns the resolved value directly (or `undefined` if not fetched yet), making it
|
|
112
|
+
* easier to work with in synchronous contexts. Call `fetch()` to trigger the async operation.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* const $user = resource(() => fetch('/api/user').then(r => r.json()));
|
|
117
|
+
*
|
|
118
|
+
* // Trigger fetch
|
|
119
|
+
* await $user.fetch();
|
|
120
|
+
*
|
|
121
|
+
* // Use in effect
|
|
122
|
+
* effect((t) => {
|
|
123
|
+
* const user = $user.get(t);
|
|
124
|
+
* if (user) {
|
|
125
|
+
* console.log(user.name);
|
|
126
|
+
* }
|
|
127
|
+
* });
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @public
|
|
131
|
+
*/
|
|
132
|
+
export function resource<T>(fn: () => Promise<T>): FlowResource<T> {
|
|
133
|
+
return new FlowResource(fn);
|
|
134
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FlowObservableAsync } from "../async";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Represents a reactive resource that asynchronously fetches its value and always returns a Promise.
|
|
@@ -35,7 +35,7 @@ import { FlowObservable } from "../basic/";
|
|
|
35
35
|
*
|
|
36
36
|
* @public
|
|
37
37
|
*/
|
|
38
|
-
export class FlowResourceAsync<T> extends
|
|
38
|
+
export class FlowResourceAsync<T> extends FlowObservableAsync<Promise<T>> {
|
|
39
39
|
/**
|
|
40
40
|
* Creates a new FlowResource.
|
|
41
41
|
* @param fetch - An asynchronous function that retrieves the resource's value.
|
|
@@ -68,10 +68,42 @@ export class FlowResourceAsync<T> extends FlowObservable<Promise<T>> {
|
|
|
68
68
|
public async fetch(): Promise<void> {
|
|
69
69
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
70
70
|
this._value = this._fetch();
|
|
71
|
-
this.
|
|
71
|
+
this.trigger();
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/* INTERNAL ------------------------------------------------ */
|
|
75
75
|
|
|
76
76
|
private _fetch: () => Promise<T>;
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates a new reactive asynchronous resource that always returns a Promise.
|
|
81
|
+
*
|
|
82
|
+
* @typeParam T - The type of the resource value.
|
|
83
|
+
* @param fn - An asynchronous function that fetches the resource value.
|
|
84
|
+
* @returns A new instance of {@link FlowResourceAsync}.
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* An async resource manages async data fetching and always returns a Promise, making it
|
|
88
|
+
* ideal for async/await patterns. Unlike {@link resource}, the Promise is created lazily
|
|
89
|
+
* on first access and cached. Call `fetch()` to create a new Promise and trigger a refetch.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* const $data = resourceAsync(() => fetch('/api/data').then(r => r.json()));
|
|
94
|
+
*
|
|
95
|
+
* // Use with async/await
|
|
96
|
+
* effect(async (t) => {
|
|
97
|
+
* const data = await $data.get(t);
|
|
98
|
+
* console.log(data);
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* // Refetch
|
|
102
|
+
* await $data.fetch();
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* @public
|
|
106
|
+
*/
|
|
107
|
+
export function resourceAsync<T>(fn: () => Promise<T>): FlowResourceAsync<T> {
|
|
108
|
+
return new FlowResourceAsync(fn);
|
|
109
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FlowObservable } from "../
|
|
1
|
+
import { FlowObservable } from "../sync";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A function type that sets a new value for the reactive stream.
|
|
@@ -145,6 +145,44 @@ export class FlowStream<T> extends FlowObservable<T | undefined> {
|
|
|
145
145
|
if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
|
|
146
146
|
if (value === this._value) return;
|
|
147
147
|
this._value = value;
|
|
148
|
-
this.
|
|
148
|
+
this.trigger();
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Creates a new reactive stream that bridges external event sources with PicoFlow's reactive system.
|
|
154
|
+
*
|
|
155
|
+
* @typeParam T - The type of the stream value.
|
|
156
|
+
* @param updater - A function that receives a setter to update the stream's value.
|
|
157
|
+
* It should return a disposer function to clean up resources.
|
|
158
|
+
* @returns A new instance of {@link FlowStream}.
|
|
159
|
+
*
|
|
160
|
+
* @remarks
|
|
161
|
+
* Streams are ideal for integrating push-based data sources like WebSockets, DOM events,
|
|
162
|
+
* timers, or any event emitter. The updater sets up subscriptions and calls the setter
|
|
163
|
+
* when new data arrives. The returned disposer is called on cleanup.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* // WebSocket stream
|
|
168
|
+
* const $messages = stream<string>((set) => {
|
|
169
|
+
* const ws = new WebSocket('ws://example.com');
|
|
170
|
+
* ws.onmessage = (e) => set(e.data);
|
|
171
|
+
* return () => ws.close();
|
|
172
|
+
* });
|
|
173
|
+
*
|
|
174
|
+
* // Timer stream
|
|
175
|
+
* const $tick = stream<number>((set) => {
|
|
176
|
+
* let count = 0;
|
|
177
|
+
* const id = setInterval(() => set(count++), 1000);
|
|
178
|
+
* return () => clearInterval(id);
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @public
|
|
183
|
+
*/
|
|
184
|
+
export function stream<T>(
|
|
185
|
+
updater: (set: (value: T) => void) => () => void,
|
|
186
|
+
): FlowStream<T> {
|
|
187
|
+
return new FlowStream(updater);
|
|
188
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FlowObservable } from "../
|
|
1
|
+
import { FlowObservable } from "../sync";
|
|
2
2
|
import type { FlowStreamDisposer, FlowStreamUpdater } from "./stream";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -128,7 +128,7 @@ export class FlowStreamAsync<T> extends FlowObservable<Promise<T>> {
|
|
|
128
128
|
this._resolve(value);
|
|
129
129
|
this._initialized = true;
|
|
130
130
|
this._awaitedValue = value;
|
|
131
|
-
this.
|
|
131
|
+
this.trigger();
|
|
132
132
|
return;
|
|
133
133
|
}
|
|
134
134
|
|
|
@@ -136,6 +136,41 @@ export class FlowStreamAsync<T> extends FlowObservable<Promise<T>> {
|
|
|
136
136
|
|
|
137
137
|
this._value = Promise.resolve(value);
|
|
138
138
|
this._awaitedValue = value;
|
|
139
|
-
this.
|
|
139
|
+
this.trigger();
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Creates a new reactive asynchronous stream that always returns a Promise.
|
|
145
|
+
*
|
|
146
|
+
* @typeParam T - The type of the stream value.
|
|
147
|
+
* @param updater - A function that receives a setter to update the stream's value.
|
|
148
|
+
* It should return a disposer function to clean up resources.
|
|
149
|
+
* @returns A new instance of {@link FlowStreamAsync}.
|
|
150
|
+
*
|
|
151
|
+
* @remarks
|
|
152
|
+
* Async streams are ideal for push-based async data sources where you want to use async/await.
|
|
153
|
+
* Unlike {@link stream}, this always returns a Promise that resolves to the value. The initial
|
|
154
|
+
* Promise is created on construction and resolves when the setter is first called.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* const $asyncMessages = streamAsync<string>((set) => {
|
|
159
|
+
* const ws = new WebSocket('ws://example.com');
|
|
160
|
+
* ws.onmessage = (e) => set(e.data);
|
|
161
|
+
* return () => ws.close();
|
|
162
|
+
* });
|
|
163
|
+
*
|
|
164
|
+
* effect(async (t) => {
|
|
165
|
+
* const message = await $asyncMessages.get(t);
|
|
166
|
+
* console.log('Received:', message);
|
|
167
|
+
* });
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* @public
|
|
171
|
+
*/
|
|
172
|
+
export function streamAsync<T>(
|
|
173
|
+
updater: (set: (value: T) => void) => () => void,
|
|
174
|
+
): FlowStreamAsync<T> {
|
|
175
|
+
return new FlowStreamAsync(updater);
|
|
176
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { FlowNodeAwait } from "./flowNodeAwait";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a constant value that can be computed lazily upon first access, with asynchronous values.
|
|
5
|
+
*
|
|
6
|
+
* @typeParam T - The type of the constant value (not the Promise itself).
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* `FlowConstantAwait` is a type alias based on {@link FlowNodeAwait} with the `set()` and `refresh()`
|
|
10
|
+
* methods removed, making it immutable. It provides all the reactive capabilities of `FlowNodeAwait`
|
|
11
|
+
* (lazy computation, caching, dependency tracking) but prevents value mutation after initialization.
|
|
12
|
+
*
|
|
13
|
+
* Unlike {@link FlowStateAwait}, which is mutable via the `set()` method, a constant's value never
|
|
14
|
+
* changes after it's computed. This makes it ideal for values that should remain stable throughout
|
|
15
|
+
* the application lifecycle.
|
|
16
|
+
*
|
|
17
|
+
* **Key Difference from FlowConstantAsync:**
|
|
18
|
+
* Unlike {@link FlowConstantAsync}, which returns Promises from `get()`, FlowConstantAwait returns
|
|
19
|
+
* the resolved value directly. This makes the API more convenient for reactive code that doesn't want
|
|
20
|
+
* to deal with Promises in every access. The Promise is awaited internally and the resolved value is cached.
|
|
21
|
+
*
|
|
22
|
+
* **Asynchronous Nature:**
|
|
23
|
+
* FlowConstantAwait works with Promises internally, but provides a synchronous-looking API:
|
|
24
|
+
* - `get(t)` returns the resolved value synchronously (`T | undefined`). The value may be `undefined`
|
|
25
|
+
* if the Promise hasn't resolved yet on first access.
|
|
26
|
+
* - `pick()` returns a `Promise<T>` that must be awaited. Use this when you need to ensure the value
|
|
27
|
+
* is fully resolved before proceeding.
|
|
28
|
+
* - The Promise is awaited internally and the resolved value is cached permanently.
|
|
29
|
+
*
|
|
30
|
+
* **Lazy Computation:**
|
|
31
|
+
* Constants are created using the `constantAwait()` factory function, which accepts either a
|
|
32
|
+
* `Promise<T>` or a function returning `Promise<T>`. The computation doesn't run immediately
|
|
33
|
+
* upon creation when using a function. It executes only when:
|
|
34
|
+
* - The value is first read via `get()` or `pick()`
|
|
35
|
+
* - The constant is watched via `watch()`
|
|
36
|
+
* This allows you to defer expensive async computations until they're actually needed.
|
|
37
|
+
*
|
|
38
|
+
* **Initialization Patterns:**
|
|
39
|
+
* - **Constant Promise**: Pass a `Promise<T>` directly; it's stored immediately and resolved internally.
|
|
40
|
+
* The resolved value is cached and can be accessed synchronously via `get()`.
|
|
41
|
+
* - **Lazy initialization**: Pass a function `() => Promise<T>`; it's called only when the value
|
|
42
|
+
* is first accessed via `get()` or `pick()`. The Promise is awaited internally and the resolved
|
|
43
|
+
* value is cached. This is useful for expensive async operations that may not be needed immediately.
|
|
44
|
+
*
|
|
45
|
+
* **Caching:**
|
|
46
|
+
* Once computed (either immediately or lazily), the resolved value is cached permanently. All subsequent
|
|
47
|
+
* accesses return the cached value without re-computation, ensuring the value remains constant
|
|
48
|
+
* and the computation runs only once.
|
|
49
|
+
*
|
|
50
|
+
* **Use Cases:**
|
|
51
|
+
* - Configuration values loaded asynchronously that don't change
|
|
52
|
+
* - Expensive async computations that should only run once
|
|
53
|
+
* - Initialization values for other reactive primitives
|
|
54
|
+
* - Values derived from external async sources that shouldn't be modified
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // Constant Promise - initialized immediately
|
|
59
|
+
* const $config = constantAwait(Promise.resolve({ apiUrl: 'https://api.example.com' }));
|
|
60
|
+
* const config = await $config.pick(); // Async - returns Promise
|
|
61
|
+
* const configValue = $config.get(t); // Synchronous - returns resolved value
|
|
62
|
+
*
|
|
63
|
+
* // Lazy initialization - computed on first access
|
|
64
|
+
* const $expensiveValue = constantAwait(async () => {
|
|
65
|
+
* console.log('Computing...');
|
|
66
|
+
* return await performExpensiveAsyncCalculation();
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* // First access triggers computation
|
|
70
|
+
* const value1 = await $expensiveValue.pick(); // Logs: "Computing..."
|
|
71
|
+
*
|
|
72
|
+
* // Subsequent accesses return cached value
|
|
73
|
+
* const value2 = $expensiveValue.get(t); // Synchronous - returns cached value, no log
|
|
74
|
+
*
|
|
75
|
+
* // Value cannot be changed (set() method is not available)
|
|
76
|
+
* // $expensiveValue.set(Promise.resolve(100)); // TypeScript error: Property 'set' does not exist
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
81
|
+
export type FlowConstantAwait<T> = Omit<FlowNodeAwait<T>, "set" | "refresh">;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Creates a new reactive constant with lazy initialization support.
|
|
85
|
+
*
|
|
86
|
+
* @typeParam T - The type of the constant value (not the Promise itself).
|
|
87
|
+
*
|
|
88
|
+
* @param value - Either a `Promise<T>` for immediate initialization, or a function `() => Promise<T>`
|
|
89
|
+
* for lazy initialization. The function will be called only when the value is first accessed.
|
|
90
|
+
*
|
|
91
|
+
* @returns A new instance of {@link FlowConstantAwait} that provides reactive access to the computed value.
|
|
92
|
+
*
|
|
93
|
+
* @remarks
|
|
94
|
+
* Constants are immutable reactive values that are computed once and cached permanently. Unlike
|
|
95
|
+
* states (created with `stateAwait()`), constants cannot be modified after initialization - they don't
|
|
96
|
+
* have a `set()` method.
|
|
97
|
+
*
|
|
98
|
+
* **Asynchronous Nature:**
|
|
99
|
+
* FlowConstantAwait works with Promises internally, but provides a synchronous-looking API:
|
|
100
|
+
* - `get(t)` returns the resolved value synchronously (`T | undefined`). The value may be `undefined`
|
|
101
|
+
* if the Promise hasn't resolved yet on first access.
|
|
102
|
+
* - `pick()` returns a `Promise<T>` that must be awaited. Use this when you need to ensure the value
|
|
103
|
+
* is fully resolved before proceeding.
|
|
104
|
+
* - The Promise is awaited internally and the resolved value is cached permanently.
|
|
105
|
+
*
|
|
106
|
+
* **Lazy Computation:**
|
|
107
|
+
* When you provide a function, the computation is not executed immediately upon creation. It runs only when:
|
|
108
|
+
* - The value is first read via `get(t)` or `pick()`
|
|
109
|
+
* - The constant is watched via `watch()`
|
|
110
|
+
*
|
|
111
|
+
* This lazy computation is useful for:
|
|
112
|
+
* - Expensive async computations that may not be needed immediately
|
|
113
|
+
* - Values that depend on resources not available at construction time
|
|
114
|
+
* - Deferring async operations until the value is actually needed
|
|
115
|
+
*
|
|
116
|
+
* **When to Use Constants:**
|
|
117
|
+
* - Use `constantAwait()` for values that should never change after initialization
|
|
118
|
+
* - Use `stateAwait()` for mutable values that can be updated
|
|
119
|
+
* - Use `derivationAwait()` for values that recompute when dependencies change
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* // Eager initialization with Promise
|
|
124
|
+
* const $config = constantAwait(Promise.resolve({
|
|
125
|
+
* apiUrl: 'https://api.example.com'
|
|
126
|
+
* }));
|
|
127
|
+
* const config = await $config.pick(); // Async - returns Promise
|
|
128
|
+
*
|
|
129
|
+
* // Lazy initialization with async function
|
|
130
|
+
* const $expensiveValue = constantAwait(async () => {
|
|
131
|
+
* return await loadConfigurationFromFile();
|
|
132
|
+
* });
|
|
133
|
+
*
|
|
134
|
+
* // Computation hasn't run yet
|
|
135
|
+
* // First access triggers it
|
|
136
|
+
* const value = await $expensiveValue.pick(); // Async - ensures value is resolved
|
|
137
|
+
*
|
|
138
|
+
* // Subsequent accesses return cached value
|
|
139
|
+
* const value2 = $expensiveValue.get(t); // Synchronous - returns cached value
|
|
140
|
+
*
|
|
141
|
+
* // Use in reactive context
|
|
142
|
+
* effect((t) => {
|
|
143
|
+
* const cfg = $config.get(t); // Synchronous - returns resolved value
|
|
144
|
+
* console.log(`API URL: ${cfg.apiUrl}`);
|
|
145
|
+
* });
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* @public
|
|
149
|
+
*/
|
|
150
|
+
export function constantAwait<T>(
|
|
151
|
+
value: Promise<T> | (() => Promise<T>),
|
|
152
|
+
): FlowConstantAwait<T> {
|
|
153
|
+
return new FlowNodeAwait(value);
|
|
154
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { FlowTracker } from "../../base";
|
|
2
|
+
import { FlowNodeAwait } from "./flowNodeAwait";
|
|
3
|
+
/**
|
|
4
|
+
* Represents a reactive derivation whose value is computed based on other reactive signals, with asynchronous values.
|
|
5
|
+
*
|
|
6
|
+
* @typeParam T - The type of the computed value (not the Promise itself).
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* `FlowDerivationAwait` is a type alias based on {@link FlowNodeAwait} with the `set()` method removed,
|
|
10
|
+
* making it immutable. It provides all the reactive capabilities of `FlowNodeAwait` (dependency tracking,
|
|
11
|
+
* lazy computation, caching) but prevents value mutation after initialization.
|
|
12
|
+
*
|
|
13
|
+
* Unlike {@link FlowConstantAwait}, which computes its value once and never changes, derivations
|
|
14
|
+
* automatically recompute when any tracked dependency changes. This makes them ideal for
|
|
15
|
+
* creating derived state that stays in sync with its sources.
|
|
16
|
+
*
|
|
17
|
+
* **Key Difference from FlowDerivationAsync:**
|
|
18
|
+
* Unlike {@link FlowDerivationAsync}, which returns Promises from `get()`, FlowDerivationAwait returns
|
|
19
|
+
* the resolved value directly. This makes the API more convenient for reactive code that doesn't want
|
|
20
|
+
* to deal with Promises in every access. The Promise is awaited internally and the resolved value is cached.
|
|
21
|
+
*
|
|
22
|
+
* **Asynchronous Nature:**
|
|
23
|
+
* FlowDerivationAwait works with Promises internally, but provides a synchronous-looking API:
|
|
24
|
+
* - `get(t)` returns the resolved value synchronously (`T | undefined`). The value may be `undefined`
|
|
25
|
+
* if the Promise hasn't resolved yet on first access.
|
|
26
|
+
* - `pick()` returns a `Promise<T>` that must be awaited. Use this when you need to ensure the value
|
|
27
|
+
* is fully resolved before proceeding.
|
|
28
|
+
* - The Promise is awaited internally and the resolved value is cached until dependencies change.
|
|
29
|
+
*
|
|
30
|
+
* **Lazy Computation:**
|
|
31
|
+
* The compute function doesn't run immediately upon creation. It executes only when:
|
|
32
|
+
* - The value is first read via `get()` or `pick()`
|
|
33
|
+
* - The derivation is watched via `watch()`
|
|
34
|
+
* This allows you to defer expensive async computations until they're actually needed.
|
|
35
|
+
*
|
|
36
|
+
* **Automatic Recomputation:**
|
|
37
|
+
* When a tracked dependency changes, the derivation is marked as "dirty" but doesn't recompute
|
|
38
|
+
* immediately. Recomputation happens lazily on the next value access. This prevents unnecessary
|
|
39
|
+
* computations when multiple dependencies change in quick succession. The resolved value is cached
|
|
40
|
+
* until dependencies change, ensuring efficient access patterns. You can also force recomputation using
|
|
41
|
+
* the `refresh()` method.
|
|
42
|
+
*
|
|
43
|
+
* **Dynamic Dependencies:**
|
|
44
|
+
* Dependencies are tracked dynamically during each computation. If the compute function
|
|
45
|
+
* conditionally tracks different observables, the dependency graph updates automatically.
|
|
46
|
+
* When accessing await node dependencies via `.get(t)`, the value is returned synchronously (no await needed).
|
|
47
|
+
* The compute function must return a `Promise<T>` which is awaited internally.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const $firstName = stateAwait(Promise.resolve('John'));
|
|
52
|
+
* const $lastName = stateAwait(Promise.resolve('Doe'));
|
|
53
|
+
*
|
|
54
|
+
* const $fullName = derivationAwait(async (t) => {
|
|
55
|
+
* const first = $firstName.get(t); // Synchronous - returns resolved value
|
|
56
|
+
* const last = $lastName.get(t); // Synchronous - returns resolved value
|
|
57
|
+
* return `${first} ${last}`;
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* // Compute function hasn't run yet (lazy)
|
|
61
|
+
* const name = await $fullName.pick(); // Now it computes: "John Doe"
|
|
62
|
+
*
|
|
63
|
+
* // When dependencies change, derivation recomputes automatically
|
|
64
|
+
* await $firstName.set(Promise.resolve('Jane'));
|
|
65
|
+
* const newName = await $fullName.pick(); // "Jane Doe" (recomputed)
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
export type FlowDerivationAwait<T> = Omit<FlowNodeAwait<T>, "set">;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Creates a new reactive derivation whose value is computed based on other reactive signals.
|
|
75
|
+
*
|
|
76
|
+
* @typeParam T - The type of the derived value (not the Promise itself).
|
|
77
|
+
*
|
|
78
|
+
* @param fn - A function that computes the derived value using a tracking context. The function
|
|
79
|
+
* receives a tracking context (`t`) that should be used to access dependencies via `.get(t)`.
|
|
80
|
+
* The function must return a `Promise<T>`. The function is not executed immediately; it runs
|
|
81
|
+
* lazily on first access.
|
|
82
|
+
*
|
|
83
|
+
* @returns A new instance of {@link FlowDerivationAwait} that provides reactive access to the computed value.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* A derivation is a computed reactive value that automatically tracks its dependencies and
|
|
87
|
+
* recomputes when they change. The computation is lazy - it runs only when the value is
|
|
88
|
+
* accessed, not on construction. Use derivations to create derived state without manual
|
|
89
|
+
* dependency management.
|
|
90
|
+
*
|
|
91
|
+
* **Asynchronous Nature:**
|
|
92
|
+
* FlowDerivationAwait works with Promises internally, but provides a synchronous-looking API:
|
|
93
|
+
* - `get(t)` returns the resolved value synchronously (`T | undefined`). The value may be `undefined`
|
|
94
|
+
* if the Promise hasn't resolved yet on first access.
|
|
95
|
+
* - `pick()` returns a `Promise<T>` that must be awaited. Use this when you need to ensure the value
|
|
96
|
+
* is fully resolved before proceeding.
|
|
97
|
+
* - Within the compute function, when accessing await node dependencies via `.get(t)`, the value
|
|
98
|
+
* is returned synchronously (no await needed). This makes the code cleaner and easier to read.
|
|
99
|
+
* - The compute function must return a `Promise<T>` which is awaited internally.
|
|
100
|
+
*
|
|
101
|
+
* **Lazy Computation:**
|
|
102
|
+
* The compute function is not executed immediately upon creation. It runs only when:
|
|
103
|
+
* - The value is first read via `get(t)` or `pick()`
|
|
104
|
+
* - The derivation is watched via `watch()`
|
|
105
|
+
*
|
|
106
|
+
* This lazy computation is useful for:
|
|
107
|
+
* - Expensive async computations that may not be needed immediately
|
|
108
|
+
* - Values that depend on async resources
|
|
109
|
+
* - Deferring async operations until the value is actually needed
|
|
110
|
+
*
|
|
111
|
+
* **Automatic Recomputation:**
|
|
112
|
+
* When any tracked dependency changes, the derivation automatically recomputes on the next access.
|
|
113
|
+
* This ensures the derived value always stays in sync with its sources.
|
|
114
|
+
*
|
|
115
|
+
* **When to Use Derivations:**
|
|
116
|
+
* - Use `derivationAwait()` for values that should recompute when dependencies change
|
|
117
|
+
* - Use `constantAwait()` for values that compute once and never change
|
|
118
|
+
* - Use `stateAwait()` for mutable values that can be updated directly
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const $firstName = stateAwait(Promise.resolve('John'));
|
|
123
|
+
* const $lastName = stateAwait(Promise.resolve('Doe'));
|
|
124
|
+
*
|
|
125
|
+
* const $fullName = derivationAwait(async (t) => {
|
|
126
|
+
* const first = $firstName.get(t); // Synchronous - returns resolved value
|
|
127
|
+
* const last = $lastName.get(t); // Synchronous - returns resolved value
|
|
128
|
+
* return `${first} ${last}`;
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* // Use in reactive context
|
|
132
|
+
* effect((t) => {
|
|
133
|
+
* const name = $fullName.get(t); // Synchronous - returns resolved value
|
|
134
|
+
* console.log(name); // Logs: "John Doe"
|
|
135
|
+
* });
|
|
136
|
+
*
|
|
137
|
+
* // When dependencies change, derivation recomputes automatically
|
|
138
|
+
* await $firstName.set(Promise.resolve('Jane')); // Logs: "Jane Doe"
|
|
139
|
+
*
|
|
140
|
+
* // Async data fetching example
|
|
141
|
+
* const $userId = stateAwait(Promise.resolve(1));
|
|
142
|
+
* const $user = derivationAwait(async (t) => {
|
|
143
|
+
* const id = $userId.get(t); // Synchronous - returns resolved value
|
|
144
|
+
* return await fetchUser(id); // Still need await for external async operations
|
|
145
|
+
* });
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* @public
|
|
149
|
+
*/
|
|
150
|
+
export function derivationAwait<T>(
|
|
151
|
+
fn: (t: FlowTracker) => Promise<T>,
|
|
152
|
+
): FlowDerivationAwait<T> {
|
|
153
|
+
return new FlowNodeAwait(fn);
|
|
154
|
+
}
|