@ersbeth/picoflow 0.0.1 → 0.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.
Files changed (143) hide show
  1. package/api/doc/index.md +1 -1
  2. package/api/doc/picoflow.constant.md +55 -0
  3. package/api/doc/picoflow.derivation.md +1 -1
  4. package/api/doc/picoflow.effect.md +1 -1
  5. package/api/doc/picoflow.flowconstant._constructor_.md +49 -0
  6. package/api/doc/picoflow.flowconstant.get.md +25 -0
  7. package/api/doc/picoflow.flowconstant.md +88 -0
  8. package/api/doc/picoflow.flowderivation._constructor_.md +2 -2
  9. package/api/doc/picoflow.flowderivation.get.md +2 -2
  10. package/api/doc/picoflow.flowderivation.md +2 -2
  11. package/api/doc/picoflow.floweffect._constructor_.md +7 -2
  12. package/api/doc/picoflow.floweffect.dispose.md +3 -3
  13. package/api/doc/picoflow.floweffect.disposed.md +1 -1
  14. package/api/doc/picoflow.floweffect.md +4 -4
  15. package/api/doc/picoflow.flowgetter.md +2 -2
  16. package/api/doc/picoflow.flowmap._lastdeleted.md +1 -1
  17. package/api/doc/picoflow.flowmap._lastset.md +1 -1
  18. package/api/doc/picoflow.flowmap.delete.md +6 -2
  19. package/api/doc/picoflow.flowmap.md +5 -7
  20. package/api/doc/picoflow.flowmap.setat.md +6 -2
  21. package/api/doc/picoflow.flowobservable.get.md +3 -3
  22. package/api/doc/picoflow.flowobservable.md +18 -4
  23. package/api/doc/picoflow.flowobservable.subscribe.md +55 -0
  24. package/api/doc/picoflow.flowresource._constructor_.md +2 -18
  25. package/api/doc/picoflow.flowresource.fetch.md +1 -1
  26. package/api/doc/picoflow.flowresource.get.md +4 -4
  27. package/api/doc/picoflow.flowresource.md +4 -4
  28. package/api/doc/picoflow.flowresourceasync._constructor_.md +49 -0
  29. package/api/doc/picoflow.flowresourceasync.fetch.md +27 -0
  30. package/api/doc/picoflow.flowresourceasync.get.md +23 -0
  31. package/api/doc/picoflow.flowresourceasync.md +100 -0
  32. package/api/doc/picoflow.flowsignal.dispose.md +3 -7
  33. package/api/doc/picoflow.flowsignal.disposed.md +2 -2
  34. package/api/doc/picoflow.flowsignal.md +5 -5
  35. package/api/doc/picoflow.flowsignal.trigger.md +3 -7
  36. package/api/doc/picoflow.flowstate.md +4 -52
  37. package/api/doc/picoflow.flowstate.set.md +5 -5
  38. package/api/doc/picoflow.flowstream._constructor_.md +3 -19
  39. package/api/doc/picoflow.flowstream.dispose.md +1 -1
  40. package/api/doc/picoflow.flowstream.get.md +4 -4
  41. package/api/doc/picoflow.flowstream.md +5 -5
  42. package/api/doc/picoflow.flowstreamasync._constructor_.md +54 -0
  43. package/api/doc/picoflow.flowstreamasync.dispose.md +21 -0
  44. package/api/doc/picoflow.flowstreamasync.get.md +23 -0
  45. package/api/doc/picoflow.flowstreamasync.md +100 -0
  46. package/api/doc/picoflow.flowstreamdisposer.md +13 -0
  47. package/api/doc/picoflow.flowstreamsetter.md +13 -0
  48. package/api/doc/picoflow.flowstreamupdater.md +19 -0
  49. package/api/doc/picoflow.flowwatcher.md +1 -1
  50. package/api/doc/picoflow.map.md +1 -1
  51. package/api/doc/picoflow.md +80 -14
  52. package/api/doc/picoflow.resource.md +2 -18
  53. package/api/doc/picoflow.resourceasync.md +55 -0
  54. package/api/doc/picoflow.signal.md +1 -1
  55. package/api/doc/picoflow.state.md +3 -3
  56. package/api/doc/picoflow.stream.md +2 -18
  57. package/api/doc/picoflow.streamasync.md +55 -0
  58. package/api/picoflow.public.api.md +131 -0
  59. package/api-extractor.json +2 -1
  60. package/dist/picoflow.js +326 -302
  61. package/dist/types/advanced/index.d.ts +7 -0
  62. package/dist/types/advanced/index.d.ts.map +1 -0
  63. package/dist/types/{map.d.ts → advanced/map.d.ts} +12 -12
  64. package/dist/types/advanced/map.d.ts.map +1 -0
  65. package/dist/types/advanced/resource.d.ts +39 -0
  66. package/dist/types/advanced/resource.d.ts.map +1 -0
  67. package/dist/types/{resource.d.ts → advanced/resourceAsync.d.ts} +6 -11
  68. package/dist/types/advanced/resourceAsync.d.ts.map +1 -0
  69. package/dist/types/advanced/stream.d.ts +59 -0
  70. package/dist/types/advanced/stream.d.ts.map +1 -0
  71. package/dist/types/advanced/streamAsync.d.ts +43 -0
  72. package/dist/types/advanced/streamAsync.d.ts.map +1 -0
  73. package/dist/types/basic/constant.d.ts +32 -0
  74. package/dist/types/basic/constant.d.ts.map +1 -0
  75. package/dist/types/basic/derivation.d.ts +40 -0
  76. package/dist/types/basic/derivation.d.ts.map +1 -0
  77. package/dist/types/basic/effect.d.ts +56 -0
  78. package/dist/types/basic/effect.d.ts.map +1 -0
  79. package/dist/types/basic/index.d.ts +9 -0
  80. package/dist/types/basic/index.d.ts.map +1 -0
  81. package/dist/types/basic/observable.d.ts +34 -0
  82. package/dist/types/basic/observable.d.ts.map +1 -0
  83. package/dist/types/basic/signal.d.ts +37 -0
  84. package/dist/types/basic/signal.d.ts.map +1 -0
  85. package/dist/types/basic/state.d.ts +26 -0
  86. package/dist/types/basic/state.d.ts.map +1 -0
  87. package/dist/types/creators.d.ts +29 -13
  88. package/dist/types/creators.d.ts.map +1 -1
  89. package/dist/types/index.d.ts +3 -9
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/package.json +1 -1
  92. package/src/advanced/index.ts +10 -0
  93. package/src/{map.ts → advanced/map.ts} +14 -14
  94. package/src/advanced/resource.ts +56 -0
  95. package/src/{resource.ts → advanced/resourceAsync.ts} +9 -16
  96. package/src/advanced/stream.ts +87 -0
  97. package/src/advanced/streamAsync.ts +82 -0
  98. package/src/basic/constant.ts +64 -0
  99. package/src/basic/derivation.ts +86 -0
  100. package/src/basic/effect.ts +96 -0
  101. package/src/basic/index.ts +8 -0
  102. package/src/basic/observable.ts +51 -0
  103. package/src/basic/signal.ts +105 -0
  104. package/src/basic/state.ts +39 -0
  105. package/src/creators.ts +54 -15
  106. package/src/index.ts +21 -11
  107. package/test/constant.test.ts +46 -0
  108. package/test/derivation.test.ts +30 -6
  109. package/test/effect.test.ts +29 -0
  110. package/test/map.test.ts +38 -0
  111. package/test/resource.test.ts +18 -16
  112. package/test/resourceAsync.test.ts +108 -0
  113. package/test/signal.test.ts +18 -1
  114. package/test/state.test.ts +107 -2
  115. package/test/stream.test.ts +38 -13
  116. package/test/streamAsync.test.ts +194 -0
  117. package/tsconfig.json +3 -1
  118. package/api/doc/picoflow.flowdisposer.md +0 -13
  119. package/api/doc/picoflow.flowsetter.md +0 -13
  120. package/api/doc/picoflow.flowstate._constructor_.md +0 -49
  121. package/api/doc/picoflow.flowstate.get.md +0 -23
  122. package/api/doc/picoflow.flowupdater.md +0 -19
  123. package/api/picoflow.api.md +0 -145
  124. package/dist/types/derivation.d.ts +0 -58
  125. package/dist/types/derivation.d.ts.map +0 -1
  126. package/dist/types/effect.d.ts +0 -108
  127. package/dist/types/effect.d.ts.map +0 -1
  128. package/dist/types/map.d.ts.map +0 -1
  129. package/dist/types/observable.d.ts +0 -40
  130. package/dist/types/observable.d.ts.map +0 -1
  131. package/dist/types/resource.d.ts.map +0 -1
  132. package/dist/types/signal.d.ts +0 -111
  133. package/dist/types/signal.d.ts.map +0 -1
  134. package/dist/types/state.d.ts +0 -39
  135. package/dist/types/state.d.ts.map +0 -1
  136. package/dist/types/stream.d.ts +0 -71
  137. package/dist/types/stream.d.ts.map +0 -1
  138. package/src/derivation.ts +0 -96
  139. package/src/effect.ts +0 -152
  140. package/src/observable.ts +0 -50
  141. package/src/signal.ts +0 -166
  142. package/src/state.ts +0 -52
  143. package/src/stream.ts +0 -99
package/dist/picoflow.js CHANGED
@@ -1,27 +1,23 @@
1
1
  class FlowSignal {
2
- /* API ---------------------------------------------------------------------- */
3
2
  /**
4
- * Triggers the signal.
5
- * @remarks
6
- * This method notifies all registered listeners and causes associated effects to execute.
7
- * @throws Error if the signal is disposed.
3
+ * Triggers the FlowSignal.
4
+ * Notifies all registered listeners and schedules execution of associated effects.
5
+ * @throws If the FlowSignal has already been disposed.
8
6
  * @public
9
7
  */
10
8
  trigger() {
11
- if (this._disposed) throw new Error("Signal is disposed");
9
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
12
10
  this._notify();
13
11
  }
14
12
  /**
15
- * Disposes the signal.
16
- * @remarks
17
- * Disposing a signal will dispose all registered effects and listeners,
18
- * and unregister all dependencies. Once disposed, any further calls to the signal
19
- * will throw an error.
20
- * @throws Error if the signal is already disposed.
13
+ * Disposes the FlowSignal.
14
+ * Cleans up all registered effects, listeners, and dependencies.
15
+ * Once disposed, further usage of the signal will throw an error.
16
+ * @throws If the FlowSignal is already disposed.
21
17
  * @public
22
18
  */
23
19
  dispose() {
24
- if (this._disposed) throw new Error("Signal is disposed");
20
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
25
21
  Array.from(this._effects).forEach((effect) => effect.dispose());
26
22
  Array.from(this._listeners).forEach((listener) => listener.dispose());
27
23
  Array.from(this._dependencies).forEach((dependency) => {
@@ -30,207 +26,74 @@ class FlowSignal {
30
26
  this._disposed = true;
31
27
  }
32
28
  /**
33
- * Indicates whether the signal has been disposed.
34
- * @remarks
35
- * Once disposed, the signal should not be used.
29
+ * Indicates whether the FlowSignal has been disposed.
30
+ * @remarks Once disposed, the signal should not be used.
36
31
  * @public
37
32
  */
38
33
  get disposed() {
39
34
  return this._disposed;
40
35
  }
41
36
  /* INTERNAL ------------------------------------------------------------- */
42
- /**
43
- * @internal
44
- */
37
+ /*@internal*/
45
38
  _disposed = false;
46
- /**
47
- * @internal
48
- */
39
+ /*@internal*/
49
40
  _dependencies = /* @__PURE__ */ new Set();
50
- /**
51
- * @internal
52
- */
41
+ /*@internal*/
53
42
  _listeners = /* @__PURE__ */ new Set();
54
- /**
55
- * @internal
56
- */
43
+ /*@internal*/
57
44
  _effects = /* @__PURE__ */ new Set();
58
- /**
59
- * @internal
60
- * Internal method to watch the signal.
61
- * @throws Error if the signal is disposed.
62
- */
45
+ /*@internal*/
63
46
  _watch() {
64
- if (this._disposed) throw new Error("Signal is disposed");
47
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
65
48
  }
66
- /**
67
- * @internal
68
- * Notifies all listeners and executes all registered effects.
69
- */
49
+ /*@internal*/
70
50
  _notify() {
71
51
  this._listeners.forEach((listener) => listener._notify());
72
52
  this._effects.forEach((effect) => effect._exec());
73
53
  }
74
- /**
75
- * @internal
76
- * Registers a dependency from the given listener and watches the signal.
77
- * @param listener - A FlowSignal or FlowEffect that will depend on this signal.
78
- */
54
+ /*@internal*/
79
55
  _watchFrom(listener) {
80
56
  listener._registerDependency(this);
81
57
  this._watch();
82
58
  }
83
- /**
84
- * @internal
85
- * Registers a dependency with the given FlowSignal.
86
- * @param dependency - The FlowSignal to register as a dependency.
87
- */
59
+ /*@internal*/
88
60
  _registerDependency(dependency) {
89
61
  this._dependencies.add(dependency);
90
62
  dependency._registerListener(this);
91
63
  }
92
- /**
93
- * @internal
94
- * Unregisters the given dependency.
95
- * @param dependency - The FlowSignal to unregister.
96
- */
64
+ /*@internal*/
97
65
  _unregisterDependency(dependency) {
98
66
  this._dependencies.delete(dependency);
99
67
  dependency._unregisterListener(this);
100
68
  }
101
- /**
102
- * @internal
103
- * Registers a listener (another FlowSignal) to this signal.
104
- * @param signal - The FlowSignal to register as a listener.
105
- */
69
+ /*@internal*/
106
70
  _registerListener(signal) {
107
71
  this._listeners.add(signal);
108
72
  }
109
- /**
110
- * @internal
111
- * Unregisters the given listener.
112
- * @param signal - The FlowSignal to unregister.
113
- */
73
+ /*@internal*/
114
74
  _unregisterListener(signal) {
115
75
  this._listeners.delete(signal);
116
76
  }
117
- /**
118
- * @internal
119
- * Registers a FlowEffect to this signal.
120
- * @param effect - The FlowEffect to register.
121
- */
77
+ /*@internal*/
122
78
  _registerEffect(effect) {
123
79
  this._effects.add(effect);
124
80
  }
125
- /**
126
- * @internal
127
- * Unregisters the given FlowEffect.
128
- * @param effect - The FlowEffect to unregister.
129
- */
81
+ /*@internal*/
130
82
  _unregisterEffect(effect) {
131
83
  this._effects.delete(effect);
132
84
  }
133
85
  }
134
86
 
135
- class FlowObservable extends FlowSignal {
136
- /* INTERNAL -------------------------------------------*/
137
- /**
138
- * @internal
139
- * Internal storage for the observable's value.
140
- */
141
- _value;
142
- /**
143
- * @internal
144
- * Retrieves the current value from the observable and registers a dependency
145
- * from the provided listener.
146
- * @param listener - The FlowObservable or FlowEffect that is accessing this observable.
147
- * @returns The current value, as returned by {@link FlowObservable.get}.
148
- */
149
- _getFrom(listener) {
150
- listener._registerDependency(this);
151
- return this.get();
152
- }
153
- }
154
-
155
- class FlowDerivation extends FlowObservable {
156
- /**
157
- * Creates a new FlowDerivation.
158
- * @param compute - A function that computes the derived value. It is provided with a getter
159
- * and a watcher function to access observables and signals dependencies.
160
- * @public
161
- */
162
- constructor(compute) {
163
- super();
164
- this._trackedExec = () => compute(this._trackedGet, this._trackedWatch);
165
- this._untrackedExec = () => compute(this._untrackedGet, this._untrackedWatch);
166
- }
167
- /**
168
- * Gets the current derived value.
169
- * @returns The current computed value.
170
- * @remarks
171
- * This method ensures that the derivation is up-to-date before returning the value.
172
- * @public
173
- */
174
- get() {
175
- this._exec();
176
- return this._value;
177
- }
178
- /* INTERNAL MEMBERS AND METHODS */
179
- /** @internal */
180
- _initialized = false;
181
- /** @internal */
182
- _dirty = true;
183
- /** @internal */
184
- _trackedGet = (observable) => observable._getFrom(this);
185
- /** @internal */
186
- _trackedWatch = (signal) => signal._watchFrom(this);
187
- /** @internal */
188
- _untrackedGet = (observable) => observable.get();
189
- /** @internal */
190
- _untrackedWatch = (signal) => signal._watch();
191
- /** @internal */
192
- _trackedExec;
193
- /** @internal */
194
- _untrackedExec;
195
- /**
196
- * @internal
197
- * Executes the compute function if necessary to update the derived value.
198
- */
199
- _exec() {
200
- if (this._disposed) throw new Error("Effect is disposed");
201
- if (this._dirty) {
202
- if (this._initialized) this._value = this._untrackedExec();
203
- else {
204
- this._value = this._trackedExec();
205
- this._initialized = true;
206
- }
207
- this._dirty = false;
208
- }
209
- }
210
- /**
211
- * @internal
212
- * Marks the derivation as dirty and notifies downstream dependencies.
213
- */
214
- _notify() {
215
- this._dirty = true;
216
- super._notify();
217
- }
218
- /**
219
- * @internal
220
- * Ensures that the derivation is up-to-date when it is watched.
221
- */
222
- _watch() {
223
- this._exec();
224
- }
225
- }
226
-
227
87
  class FlowEffect {
228
- /* API --------------------------------------------------- */
229
88
  /**
230
89
  * Creates a new FlowEffect.
231
90
  *
232
- * @param apply - A function that performs the side effect. It receives a getter and a watcher
233
- * function to access and register dependencies on reactive observables and signals.
91
+ * @param apply - A side-effect function that receives a getter and a watcher to
92
+ * access and register dependencies on reactive observables and signals.
93
+ *
94
+ * @remarks
95
+ * The provided function is executed immediately in a tracked mode to collect dependencies.
96
+ * On subsequent executions, it runs in an untracked mode.
234
97
  *
235
98
  * @public
236
99
  */
@@ -240,16 +103,16 @@ class FlowEffect {
240
103
  this._exec();
241
104
  }
242
105
  /**
243
- * Disposes the effect, unregistering all its dependencies.
106
+ * Disposes the effect, unregistering all its tracked dependencies.
244
107
  *
245
108
  * @remarks
246
- * After disposal, the effect should no longer be used. Calling this method on an already
247
- * disposed effect will throw an error.
109
+ * Once disposed, the effect must no longer be used. Trying to dispose an effect
110
+ * that is already disposed will throw an error.
248
111
  *
249
112
  * @public
250
113
  */
251
114
  dispose() {
252
- if (this._disposed) throw new Error("Effect is disposed");
115
+ if (this._disposed) throw new Error("[PicoFlow] Effect is disposed");
253
116
  Array.from(this._dependencies).forEach((dependency) => {
254
117
  this._unregisterDependency(dependency);
255
118
  });
@@ -258,7 +121,7 @@ class FlowEffect {
258
121
  /**
259
122
  * Indicates whether this effect has been disposed.
260
123
  *
261
- * @returns A boolean value indicating if the effect is disposed.
124
+ * @returns A boolean value that is true if the effect is disposed, false otherwise.
262
125
  *
263
126
  * @public
264
127
  */
@@ -266,165 +129,191 @@ class FlowEffect {
266
129
  return this._disposed;
267
130
  }
268
131
  /* INTERNAL ------------------------------------------------------------ */
269
- /**
270
- * @internal
271
- */
272
132
  _disposed = false;
273
- /**
274
- * @internal
275
- */
276
133
  _initialized = false;
277
- /**
278
- * @internal
279
- */
280
134
  _dependencies = /* @__PURE__ */ new Set();
281
- /**
282
- * @internal
283
- * A tracked getter that registers a dependency when accessing an observable.
284
- */
285
135
  _trackedGet = (observable) => observable._getFrom(this);
286
- /**
287
- * @internal
288
- * A tracked watcher that registers a dependency when watching a signal.
289
- */
290
136
  _trackedWatch = (signal) => signal._watchFrom(this);
291
- /**
292
- * @internal
293
- * An untracked getter that simply retrieves the current value from an observable.
294
- */
295
137
  _untrackedGet = (observable) => observable.get();
296
- /**
297
- * @internal
298
- * An untracked watcher that calls the default watch on a signal.
299
- */
300
138
  _untrackedWatch = (signal) => signal._watch();
301
- /**
302
- * @internal
303
- * Execution function used during initialization (tracked mode).
304
- */
305
139
  _trackedExec;
306
- /**
307
- * @internal
308
- * Execution function used after initialization (untracked mode).
309
- */
310
140
  _untrackedExec;
311
- /**
312
- * @internal
313
- * Executes the effect. If the effect has not been initialized, it runs in tracked mode;
314
- * otherwise, it runs in untracked mode.
315
- *
316
- * @throws Error if the effect has been disposed.
317
- */
141
+ /*@internal*/
318
142
  _exec() {
319
- if (this._disposed) throw new Error("Effect is disposed");
143
+ if (this._disposed)
144
+ throw new Error("[PicoFlow] Effect is disposed");
320
145
  if (this._initialized) this._untrackedExec();
321
146
  else {
322
147
  this._trackedExec();
323
148
  this._initialized = true;
324
149
  }
325
150
  }
326
- /**
327
- * @internal
328
- * Registers a dependency on the given signal.
329
- *
330
- * @param dependency - The FlowSignal to register as a dependency.
331
- */
151
+ /*@internal*/
332
152
  _registerDependency(dependency) {
333
153
  this._dependencies.add(dependency);
334
154
  dependency._registerEffect(this);
335
155
  }
336
- /**
337
- * @internal
338
- * Unregisters the given dependency.
339
- *
340
- * @param dependency - The FlowSignal to unregister.
341
- */
156
+ /*@internal*/
342
157
  _unregisterDependency(dependency) {
343
158
  this._dependencies.delete(dependency);
344
159
  dependency._unregisterEffect(this);
345
160
  }
346
161
  }
347
162
 
348
- class FlowResource extends FlowObservable {
163
+ class FlowObservable extends FlowSignal {
164
+ /* INTERNAL -------------------------------------------*/
165
+ /*@internal*/
166
+ _value;
167
+ /*@internal*/
168
+ _getFrom(listener) {
169
+ listener._registerDependency(this);
170
+ return this.get();
171
+ }
349
172
  /**
350
- * Creates a new FlowResource.
351
- * @param fetch - An asynchronous function that retrieves the resource's value.
352
- * @param initial - The initial value of the resource.
173
+ * Subscribes a listener function to changes of the observable.
174
+ * The listener is executed immediately with the current value and on subsequent updates.
175
+ * @param listener - A callback function that receives the new value.
176
+ * @returns A disposer function to cancel the subscription.
177
+ */
178
+ subscribe(listener) {
179
+ const effect = new FlowEffect((get) => {
180
+ listener(get(this));
181
+ });
182
+ return () => effect.dispose();
183
+ }
184
+ }
185
+
186
+ class FlowConstant extends FlowObservable {
187
+ /**
188
+ * Creates a new FlowConstant instance.
189
+ *
190
+ * @param value - Either a direct value of type T or a function returning a value of type T for lazy initialization.
353
191
  * @public
354
192
  */
355
- constructor(fetch, initial) {
193
+ constructor(value) {
356
194
  super();
357
- this._value = initial;
358
- this._fetch = fetch;
195
+ this._initEager(value);
359
196
  }
360
197
  /**
361
- * Retrieves the current resource value.
362
- * @returns The current value.
363
- * @throws Error if the resource is disposed.
198
+ * Retrieves the constant value, computing it lazily if needed.
199
+ *
200
+ * Accessing this method will initialize the value if it has not been computed already.
201
+ * Throws an error if the instance has been disposed or if lazy initialization fails.
202
+ *
203
+ * @returns The cached constant value.
204
+ * @throws Error if the constant is disposed or cannot be initialized.
364
205
  * @public
365
206
  */
366
207
  get() {
367
- if (this._disposed) throw new Error("Resource is disposed");
208
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
209
+ this._initLazy();
368
210
  return this._value;
369
211
  }
212
+ /* INTERNAL --------------------------------------------------------- */
213
+ /*@internal*/
214
+ _initialized = false;
215
+ /*@internal*/
216
+ _init;
217
+ /*@internal*/
218
+ _initEager(value) {
219
+ if (typeof value === "function") {
220
+ this._init = value;
221
+ } else {
222
+ this._value = value;
223
+ this._initialized = true;
224
+ }
225
+ }
226
+ /*@internal*/
227
+ _initLazy() {
228
+ if (!this._initialized && this._init) {
229
+ this._value = this._init();
230
+ this._initialized = true;
231
+ }
232
+ if (!this._initialized)
233
+ throw new Error("[PicoFlow] Primitive can't be initialized");
234
+ }
235
+ }
236
+
237
+ class FlowState extends FlowConstant {
370
238
  /**
371
- * Asynchronously fetches a new value for the resource.
239
+ * Updates the state with a new value.
240
+ * @param value - A new value or a callback function that computes a new value based on the current state.
372
241
  * @remarks
373
- * Executes the internal fetch function. If the fetched value differs from the current one,
374
- * updates the resource's value and notifies subscribers.
375
- * @returns A Promise that resolves when the fetch operation is complete.
376
- * @throws Error if the resource is disposed.
242
+ * If the computed new value is strictly equal to the current state value, no change is made and subscribers
243
+ * will not be notified. Otherwise, the state is updated and all subscribers are informed of the change.
244
+ * @throws Error if the state has been disposed.
377
245
  * @public
378
246
  */
379
- async fetch() {
380
- if (this._disposed) throw new Error("Resource is disposed");
381
- const value = await this._fetch();
382
- if (value === this._value) return;
383
- this._value = value;
247
+ set(value) {
248
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
249
+ const next = typeof value === "function" ? value(this._value) : value;
250
+ if (next === this._value) return;
251
+ this._value = next;
384
252
  this._notify();
385
253
  }
386
- /* INTERNAL ------------------------------------------------ */
387
- /**
388
- * @internal
389
- * The asynchronous function used to fetch the resource value.
390
- */
391
- _fetch;
392
254
  }
393
255
 
394
- class FlowState extends FlowObservable {
256
+ class FlowDerivation extends FlowObservable {
395
257
  /**
396
- * Creates a new FlowState with the given initial value.
397
- * @param value - The initial value for the state.
258
+ * Creates a new FlowDerivation.
259
+ * @param compute - A function that computes the derived value. It is provided with two parameters:
260
+ * a getter and a watcher that respect dependency tracking.
398
261
  * @public
399
262
  */
400
- constructor(value) {
263
+ constructor(compute) {
401
264
  super();
402
- this._value = value;
265
+ this._initEager(compute);
403
266
  }
404
267
  /**
405
- * Retrieves the current state value.
406
- * @returns The current value of the state.
407
- * @throws Error if the state has been disposed.
268
+ * Gets the current derived value.
269
+ * @returns The current computed value.
270
+ * @remarks
271
+ * This method lazily initializes and updates the derivation if it is marked as dirty. It throws an error
272
+ * if the derivation has been disposed.
408
273
  * @public
409
274
  */
410
275
  get() {
411
- if (this._disposed) throw new Error("State is disposed");
276
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
277
+ this._initLazy();
278
+ this._compute();
412
279
  return this._value;
413
280
  }
414
- /**
415
- * Updates the state with a new value.
416
- * @param value - The new value to set.
417
- * @remarks
418
- * If the new value is identical to the current value, no update or notification occurs.
419
- * Otherwise, the state is updated and all subscribers are notified.
420
- * @throws Error if the state has been disposed.
421
- * @public
422
- */
423
- set(value) {
424
- if (this._disposed) throw new Error("State is disposed");
425
- if (value === this._value) return;
426
- this._value = value;
427
- this._notify();
281
+ /* INTERNAL --------------------------------------------------------- */
282
+ _initialized = false;
283
+ _dirty = false;
284
+ _trackedGet = (observable) => observable._getFrom(this);
285
+ _trackedWatch = (signal) => signal._watchFrom(this);
286
+ _untrackedGet = (observable) => observable.get();
287
+ _untrackedWatch = (signal) => signal._watch();
288
+ _trackedCompute;
289
+ _untrackedCompute;
290
+ _initEager(compute) {
291
+ this._trackedCompute = () => compute(this._trackedGet, this._trackedWatch);
292
+ this._untrackedCompute = () => compute(this._untrackedGet, this._untrackedWatch);
293
+ }
294
+ _initLazy() {
295
+ if (!this._initialized) {
296
+ this._value = this._trackedCompute();
297
+ this._initialized = true;
298
+ }
299
+ }
300
+ /* @internal */
301
+ _compute() {
302
+ if (this._dirty) {
303
+ this._value = this._untrackedCompute();
304
+ this._dirty = false;
305
+ }
306
+ }
307
+ /* @internal */
308
+ _notify() {
309
+ this._dirty = true;
310
+ super._notify();
311
+ }
312
+ /* @internal */
313
+ _watch() {
314
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
315
+ this._initLazy();
316
+ this._compute();
428
317
  }
429
318
  }
430
319
 
@@ -454,15 +343,16 @@ class FlowMap extends FlowState {
454
343
  *
455
344
  * @param key - The key at which to set the value.
456
345
  * @param value - The value to set.
346
+ * @throws If the FlowMap instance is disposed.
457
347
  *
458
348
  * @remarks
459
- * This method updates the internal map with the given key and value, emits the new
460
- * key-value pair via {@link FlowMap.$lastSet}, and notifies subscribers of the change.
349
+ * Updates the internal map, emits the key-value pair via {@link FlowMap.$lastSet},
350
+ * and notifies all subscribers of the change.
461
351
  *
462
352
  * @public
463
353
  */
464
354
  setAt(key, value) {
465
- if (this._disposed) throw new Error("StateMap is disposed");
355
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
466
356
  this._value.set(key, value);
467
357
  this.$lastSet.set({ key, value });
468
358
  this._notify();
@@ -471,16 +361,16 @@ class FlowMap extends FlowState {
471
361
  * Deletes the value at the specified key from the underlying map.
472
362
  *
473
363
  * @param key - The key to delete.
364
+ * @throws If the FlowMap instance is disposed.
474
365
  *
475
366
  * @remarks
476
- * This method removes the key from the internal map, emits the deleted key and its
477
- * corresponding value via {@link FlowMap.$lastDeleted}, and notifies subscribers
478
- * of the change.
367
+ * Removes the key from the internal map, emits the deleted key and its value via {@link FlowMap.$lastDeleted},
368
+ * and notifies all subscribers of the change.
479
369
  *
480
370
  * @public
481
371
  */
482
372
  delete(key) {
483
- if (this._disposed) throw new Error("StateMap is disposed");
373
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
484
374
  const value = this._value.get(key);
485
375
  this._value.delete(key);
486
376
  this.$lastDeleted.set({ key, value });
@@ -489,29 +379,26 @@ class FlowMap extends FlowState {
489
379
  }
490
380
 
491
381
  class FlowStream extends FlowObservable {
492
- /* API -------------------------------------------------------- */
493
382
  /**
494
383
  * Creates a new FlowStream.
495
384
  * @param updater - A function that receives a setter to update the stream's value.
496
385
  * It should return a disposer function that will be called upon disposal.
497
- * @param initial - The initial value of the stream.
498
386
  * @public
499
387
  */
500
- constructor(updater, initial) {
388
+ constructor(updater) {
501
389
  super();
502
- this._value = initial;
503
390
  this._disposer = updater((value) => {
504
391
  this._set(value);
505
392
  });
506
393
  }
507
394
  /**
508
395
  * Retrieves the current value of the stream.
509
- * @returns The current value.
396
+ * @returns The current value, or undefined if no value has been set yet.
510
397
  * @throws Error if the stream is disposed.
511
398
  * @public
512
399
  */
513
400
  get() {
514
- if (this._disposed) throw new Error("Stream is disposed");
401
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
515
402
  return this._value;
516
403
  }
517
404
  /**
@@ -526,36 +413,173 @@ class FlowStream extends FlowObservable {
526
413
  this._disposer();
527
414
  }
528
415
  /* INTERNAL ------------------------------------------------------ */
416
+ _disposer;
417
+ _set(value) {
418
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
419
+ if (value === this._value) return;
420
+ this._value = value;
421
+ this._notify();
422
+ }
423
+ }
424
+
425
+ class FlowStreamAsync extends FlowObservable {
529
426
  /**
530
- * @internal
531
- * The disposer function returned by the updater.
427
+ * Creates a new asynchronous FlowStream.
428
+ * @param updater - A function that receives a setter to update the stream's value.
429
+ * It should return a disposer function that will be called upon disposal.
430
+ * @remarks The updater function can invoke the setter asynchronously to update the stream.
431
+ * @public
532
432
  */
533
- _disposer;
433
+ constructor(updater) {
434
+ super();
435
+ this._disposer = updater((value) => {
436
+ this._set(value);
437
+ });
438
+ this._value = new Promise((resolve) => {
439
+ this._resolve = resolve;
440
+ });
441
+ }
534
442
  /**
535
- * @internal
536
- * Updates the stream's value and notifies subscribers if the value changes.
537
- * @param value - The new value to set.
538
- * @internal
443
+ * Retrieves the current value of the stream as a Promise.
444
+ * @returns A Promise that resolves to the current value.
445
+ * @throws Error if the stream is disposed.
446
+ * @public
539
447
  */
448
+ get() {
449
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
450
+ return this._value;
451
+ }
452
+ /**
453
+ * Disposes the stream, releasing all resources.
454
+ * @remarks In addition to disposing the underlying observable, this method calls the disposer
455
+ * returned by the updater.
456
+ * @public
457
+ */
458
+ dispose() {
459
+ super.dispose();
460
+ this._disposer();
461
+ }
462
+ /* INTERNAL ------------------------------------------------------ */
463
+ _initialized = false;
464
+ _awaitedValue;
465
+ _resolve;
466
+ _disposer;
540
467
  _set(value) {
541
- if (this._disposed) throw new Error("Stream is disposed");
468
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
469
+ if (!this._initialized) {
470
+ this._resolve(value);
471
+ this._initialized = true;
472
+ this._awaitedValue = value;
473
+ this._notify();
474
+ return;
475
+ }
476
+ if (value === this._awaitedValue) return;
477
+ this._value = Promise.resolve(value);
478
+ this._awaitedValue = value;
479
+ this._notify();
480
+ }
481
+ }
482
+
483
+ class FlowResource extends FlowObservable {
484
+ /**
485
+ * Creates a new FlowResource.
486
+ * @param fetch - An asynchronous function that retrieves the resource's value.
487
+ *
488
+ * @public
489
+ */
490
+ constructor(fetch) {
491
+ super();
492
+ this._fetch = fetch;
493
+ }
494
+ /**
495
+ * Retrieves the current resource value.
496
+ * @returns The current value, or undefined if the resource has not been fetched yet.
497
+ * @throws Error if the resource is disposed.
498
+ * @public
499
+ */
500
+ get() {
501
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
502
+ return this._value;
503
+ }
504
+ /**
505
+ * Asynchronously fetches a new value for the resource.
506
+ * @remarks
507
+ * Executes the internal fetch function. If the fetched value differs from the current one,
508
+ * updates the resource's value and notifies subscribers.
509
+ * @returns A Promise that resolves when the fetch operation is complete.
510
+ * @throws Error if the resource is disposed.
511
+ * @public
512
+ */
513
+ async fetch() {
514
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
515
+ const value = await this._fetch();
542
516
  if (value === this._value) return;
543
517
  this._value = value;
544
518
  this._notify();
545
519
  }
520
+ /* INTERNAL ------------------------------------------------ */
521
+ _fetch;
522
+ }
523
+
524
+ class FlowResourceAsync extends FlowObservable {
525
+ /**
526
+ * Creates a new FlowResource.
527
+ * @param fetch - An asynchronous function that retrieves the resource's value.
528
+ * @public
529
+ */
530
+ constructor(fetch) {
531
+ super();
532
+ this._fetch = fetch;
533
+ }
534
+ /**
535
+ * Retrieves the current resource value.
536
+ * @returns The current value, or undefined if the resource has not been fetched yet.
537
+ * @throws Error if the resource is disposed.
538
+ * @public
539
+ */
540
+ get() {
541
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
542
+ if (!this._value) this._value = this._fetch();
543
+ return this._value;
544
+ }
545
+ /**
546
+ * Asynchronously fetches a new value for the resource.
547
+ * @remarks
548
+ * Executes the internal fetch function. If the fetched value differs from the current one,
549
+ * updates the resource's value and notifies subscribers.
550
+ * @returns A Promise that resolves when the fetch operation is complete.
551
+ * @throws Error if the resource is disposed.
552
+ * @public
553
+ */
554
+ async fetch() {
555
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
556
+ this._value = this._fetch();
557
+ this._notify();
558
+ }
559
+ /* INTERNAL ------------------------------------------------ */
560
+ _fetch;
546
561
  }
547
562
 
548
563
  function signal() {
549
564
  return new FlowSignal();
550
565
  }
566
+ function constant(value) {
567
+ return new FlowConstant(value);
568
+ }
551
569
  function state(value) {
552
570
  return new FlowState(value);
553
571
  }
554
- function resource(fn, initial) {
555
- return new FlowResource(fn, initial);
572
+ function resource(fn) {
573
+ return new FlowResource(fn);
574
+ }
575
+ function resourceAsync(fn) {
576
+ return new FlowResourceAsync(fn);
577
+ }
578
+ function stream(updater) {
579
+ return new FlowStream(updater);
556
580
  }
557
- function stream(updater, initial) {
558
- return new FlowStream(updater, initial);
581
+ function streamAsync(updater) {
582
+ return new FlowStreamAsync(updater);
559
583
  }
560
584
  function derivation(fn) {
561
585
  return new FlowDerivation(fn);
@@ -569,4 +593,4 @@ function map(initial) {
569
593
  );
570
594
  }
571
595
 
572
- export { derivation, effect, map, resource, signal, state, stream };
596
+ export { constant, derivation, effect, map, resource, resourceAsync, signal, state, stream, streamAsync };