@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.
Files changed (169) hide show
  1. package/api/doc/index.md +1 -1
  2. package/api/doc/picoflow.array.md +55 -0
  3. package/api/doc/picoflow.constant.md +55 -0
  4. package/api/doc/picoflow.derivation.md +1 -1
  5. package/api/doc/picoflow.effect.md +1 -1
  6. package/api/doc/picoflow.flowarray._constructor_.md +49 -0
  7. package/api/doc/picoflow.flowarray._lastaction.md +13 -0
  8. package/api/doc/picoflow.flowarray.clear.md +17 -0
  9. package/api/doc/picoflow.flowarray.dispose.md +55 -0
  10. package/api/doc/picoflow.flowarray.get.md +19 -0
  11. package/api/doc/picoflow.flowarray.length.md +13 -0
  12. package/api/doc/picoflow.flowarray.md +273 -0
  13. package/api/doc/picoflow.flowarray.pop.md +17 -0
  14. package/api/doc/picoflow.flowarray.push.md +53 -0
  15. package/api/doc/picoflow.flowarray.set.md +53 -0
  16. package/api/doc/picoflow.flowarray.setitem.md +69 -0
  17. package/api/doc/picoflow.flowarray.shift.md +17 -0
  18. package/api/doc/picoflow.flowarray.splice.md +85 -0
  19. package/api/doc/picoflow.flowarray.unshift.md +53 -0
  20. package/api/doc/picoflow.flowarrayaction.md +37 -0
  21. package/api/doc/picoflow.flowconstant._constructor_.md +49 -0
  22. package/api/doc/picoflow.flowconstant.get.md +25 -0
  23. package/api/doc/picoflow.flowconstant.md +88 -0
  24. package/api/doc/picoflow.flowderivation._constructor_.md +2 -2
  25. package/api/doc/picoflow.flowderivation.get.md +2 -2
  26. package/api/doc/picoflow.flowderivation.md +2 -2
  27. package/api/doc/picoflow.flowdisposable.dispose.md +55 -0
  28. package/api/doc/picoflow.flowdisposable.md +43 -0
  29. package/api/doc/picoflow.floweffect._constructor_.md +7 -2
  30. package/api/doc/picoflow.floweffect.dispose.md +3 -3
  31. package/api/doc/picoflow.floweffect.disposed.md +1 -1
  32. package/api/doc/picoflow.floweffect.md +4 -4
  33. package/api/doc/picoflow.flowgetter.md +2 -2
  34. package/api/doc/picoflow.flowmap._lastdeleted.md +1 -1
  35. package/api/doc/picoflow.flowmap._lastset.md +1 -1
  36. package/api/doc/picoflow.flowmap.delete.md +6 -2
  37. package/api/doc/picoflow.flowmap.md +5 -7
  38. package/api/doc/picoflow.flowmap.setat.md +6 -2
  39. package/api/doc/picoflow.flowobservable.get.md +3 -3
  40. package/api/doc/picoflow.flowobservable.md +18 -4
  41. package/api/doc/picoflow.flowobservable.subscribe.md +55 -0
  42. package/api/doc/picoflow.flowresource._constructor_.md +2 -18
  43. package/api/doc/picoflow.flowresource.fetch.md +1 -1
  44. package/api/doc/picoflow.flowresource.get.md +4 -4
  45. package/api/doc/picoflow.flowresource.md +4 -4
  46. package/api/doc/picoflow.flowresourceasync._constructor_.md +49 -0
  47. package/api/doc/picoflow.flowresourceasync.fetch.md +27 -0
  48. package/api/doc/picoflow.flowresourceasync.get.md +23 -0
  49. package/api/doc/picoflow.flowresourceasync.md +100 -0
  50. package/api/doc/picoflow.flowsignal.dispose.md +42 -8
  51. package/api/doc/picoflow.flowsignal.disposed.md +2 -2
  52. package/api/doc/picoflow.flowsignal.md +8 -7
  53. package/api/doc/picoflow.flowsignal.trigger.md +3 -7
  54. package/api/doc/picoflow.flowstate.md +4 -52
  55. package/api/doc/picoflow.flowstate.set.md +5 -5
  56. package/api/doc/picoflow.flowstream._constructor_.md +3 -19
  57. package/api/doc/picoflow.flowstream.dispose.md +1 -1
  58. package/api/doc/picoflow.flowstream.get.md +4 -4
  59. package/api/doc/picoflow.flowstream.md +5 -5
  60. package/api/doc/picoflow.flowstreamasync._constructor_.md +54 -0
  61. package/api/doc/picoflow.flowstreamasync.dispose.md +21 -0
  62. package/api/doc/picoflow.flowstreamasync.get.md +23 -0
  63. package/api/doc/picoflow.flowstreamasync.md +100 -0
  64. package/api/doc/picoflow.flowstreamdisposer.md +13 -0
  65. package/api/doc/picoflow.flowstreamsetter.md +13 -0
  66. package/api/doc/picoflow.flowstreamupdater.md +19 -0
  67. package/api/doc/picoflow.flowwatcher.md +1 -1
  68. package/api/doc/picoflow.isdisposable.md +55 -0
  69. package/api/doc/picoflow.map.md +1 -1
  70. package/api/doc/picoflow.md +149 -13
  71. package/api/doc/picoflow.resource.md +2 -18
  72. package/api/doc/picoflow.resourceasync.md +55 -0
  73. package/api/doc/picoflow.signal.md +1 -1
  74. package/api/doc/picoflow.state.md +3 -3
  75. package/api/doc/picoflow.stream.md +2 -18
  76. package/api/doc/picoflow.streamasync.md +55 -0
  77. package/api/picoflow.public.api.md +192 -0
  78. package/api-extractor.json +2 -1
  79. package/dist/picoflow.js +513 -305
  80. package/dist/types/advanced/array.d.ts +116 -0
  81. package/dist/types/advanced/array.d.ts.map +1 -0
  82. package/dist/types/advanced/index.d.ts +9 -0
  83. package/dist/types/advanced/index.d.ts.map +1 -0
  84. package/dist/types/{map.d.ts → advanced/map.d.ts} +12 -12
  85. package/dist/types/advanced/map.d.ts.map +1 -0
  86. package/dist/types/advanced/resource.d.ts +39 -0
  87. package/dist/types/advanced/resource.d.ts.map +1 -0
  88. package/dist/types/{resource.d.ts → advanced/resourceAsync.d.ts} +6 -11
  89. package/dist/types/advanced/resourceAsync.d.ts.map +1 -0
  90. package/dist/types/advanced/stream.d.ts +59 -0
  91. package/dist/types/advanced/stream.d.ts.map +1 -0
  92. package/dist/types/advanced/streamAsync.d.ts +43 -0
  93. package/dist/types/advanced/streamAsync.d.ts.map +1 -0
  94. package/dist/types/basic/constant.d.ts +32 -0
  95. package/dist/types/basic/constant.d.ts.map +1 -0
  96. package/dist/types/basic/derivation.d.ts +40 -0
  97. package/dist/types/basic/derivation.d.ts.map +1 -0
  98. package/dist/types/basic/disposable.d.ts +23 -0
  99. package/dist/types/basic/disposable.d.ts.map +1 -0
  100. package/dist/types/basic/effect.d.ts +56 -0
  101. package/dist/types/basic/effect.d.ts.map +1 -0
  102. package/dist/types/basic/index.d.ts +11 -0
  103. package/dist/types/basic/index.d.ts.map +1 -0
  104. package/dist/types/basic/observable.d.ts +34 -0
  105. package/dist/types/basic/observable.d.ts.map +1 -0
  106. package/dist/types/basic/signal.d.ts +40 -0
  107. package/dist/types/basic/signal.d.ts.map +1 -0
  108. package/dist/types/basic/state.d.ts +26 -0
  109. package/dist/types/basic/state.d.ts.map +1 -0
  110. package/dist/types/creators.d.ts +38 -13
  111. package/dist/types/creators.d.ts.map +1 -1
  112. package/dist/types/index.d.ts +4 -9
  113. package/dist/types/index.d.ts.map +1 -1
  114. package/package.json +1 -1
  115. package/src/advanced/array.ts +224 -0
  116. package/src/advanced/index.ts +12 -0
  117. package/src/{map.ts → advanced/map.ts} +14 -14
  118. package/src/advanced/resource.ts +56 -0
  119. package/src/{resource.ts → advanced/resourceAsync.ts} +9 -16
  120. package/src/advanced/stream.ts +87 -0
  121. package/src/advanced/streamAsync.ts +82 -0
  122. package/src/basic/constant.ts +64 -0
  123. package/src/basic/derivation.ts +86 -0
  124. package/src/basic/disposable.ts +27 -0
  125. package/src/basic/effect.ts +96 -0
  126. package/src/basic/index.ts +10 -0
  127. package/src/basic/observable.ts +51 -0
  128. package/src/basic/signal.ts +117 -0
  129. package/src/basic/state.ts +39 -0
  130. package/src/creators.ts +66 -15
  131. package/src/index.ts +26 -11
  132. package/test/array.test.ts +620 -0
  133. package/test/constant.test.ts +46 -0
  134. package/test/derivation.test.ts +30 -6
  135. package/test/effect.test.ts +29 -0
  136. package/test/map.test.ts +38 -0
  137. package/test/resource.test.ts +18 -16
  138. package/test/resourceAsync.test.ts +108 -0
  139. package/test/signal.test.ts +18 -1
  140. package/test/state.test.ts +107 -2
  141. package/test/stream.test.ts +38 -13
  142. package/test/streamAsync.test.ts +194 -0
  143. package/tsconfig.json +3 -1
  144. package/api/doc/picoflow.flowdisposer.md +0 -13
  145. package/api/doc/picoflow.flowsetter.md +0 -13
  146. package/api/doc/picoflow.flowstate._constructor_.md +0 -49
  147. package/api/doc/picoflow.flowstate.get.md +0 -23
  148. package/api/doc/picoflow.flowupdater.md +0 -19
  149. package/api/picoflow.api.md +0 -145
  150. package/dist/types/derivation.d.ts +0 -58
  151. package/dist/types/derivation.d.ts.map +0 -1
  152. package/dist/types/effect.d.ts +0 -108
  153. package/dist/types/effect.d.ts.map +0 -1
  154. package/dist/types/map.d.ts.map +0 -1
  155. package/dist/types/observable.d.ts +0 -40
  156. package/dist/types/observable.d.ts.map +0 -1
  157. package/dist/types/resource.d.ts.map +0 -1
  158. package/dist/types/signal.d.ts +0 -111
  159. package/dist/types/signal.d.ts.map +0 -1
  160. package/dist/types/state.d.ts +0 -39
  161. package/dist/types/state.d.ts.map +0 -1
  162. package/dist/types/stream.d.ts +0 -71
  163. package/dist/types/stream.d.ts.map +0 -1
  164. package/src/derivation.ts +0 -96
  165. package/src/effect.ts +0 -152
  166. package/src/observable.ts +0 -50
  167. package/src/signal.ts +0 -166
  168. package/src/state.ts +0 -52
  169. package/src/stream.ts +0 -99
package/dist/picoflow.js CHANGED
@@ -1,236 +1,110 @@
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
- dispose() {
24
- if (this._disposed) throw new Error("Signal is disposed");
25
- Array.from(this._effects).forEach((effect) => effect.dispose());
26
- Array.from(this._listeners).forEach((listener) => listener.dispose());
19
+ dispose(options) {
20
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
21
+ if (options?.self) {
22
+ Array.from(this._effects).forEach(
23
+ (effect) => effect._unregisterDependency(this)
24
+ );
25
+ Array.from(this._listeners).forEach(
26
+ (listener) => listener._unregisterDependency(this)
27
+ );
28
+ } else {
29
+ Array.from(this._effects).forEach((effect) => effect.dispose());
30
+ Array.from(this._listeners).forEach(
31
+ (listener) => listener.dispose()
32
+ );
33
+ }
27
34
  Array.from(this._dependencies).forEach((dependency) => {
28
35
  this._unregisterDependency(dependency);
29
36
  });
30
37
  this._disposed = true;
31
38
  }
32
39
  /**
33
- * Indicates whether the signal has been disposed.
34
- * @remarks
35
- * Once disposed, the signal should not be used.
40
+ * Indicates whether the FlowSignal has been disposed.
41
+ * @remarks Once disposed, the signal should not be used.
36
42
  * @public
37
43
  */
38
44
  get disposed() {
39
45
  return this._disposed;
40
46
  }
41
47
  /* INTERNAL ------------------------------------------------------------- */
42
- /**
43
- * @internal
44
- */
48
+ /*@internal*/
45
49
  _disposed = false;
46
- /**
47
- * @internal
48
- */
50
+ /*@internal*/
49
51
  _dependencies = /* @__PURE__ */ new Set();
50
- /**
51
- * @internal
52
- */
52
+ /*@internal*/
53
53
  _listeners = /* @__PURE__ */ new Set();
54
- /**
55
- * @internal
56
- */
54
+ /*@internal*/
57
55
  _effects = /* @__PURE__ */ new Set();
58
- /**
59
- * @internal
60
- * Internal method to watch the signal.
61
- * @throws Error if the signal is disposed.
62
- */
56
+ /*@internal*/
63
57
  _watch() {
64
- if (this._disposed) throw new Error("Signal is disposed");
58
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
65
59
  }
66
- /**
67
- * @internal
68
- * Notifies all listeners and executes all registered effects.
69
- */
60
+ /*@internal*/
70
61
  _notify() {
71
62
  this._listeners.forEach((listener) => listener._notify());
72
63
  this._effects.forEach((effect) => effect._exec());
73
64
  }
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
- */
65
+ /*@internal*/
79
66
  _watchFrom(listener) {
80
67
  listener._registerDependency(this);
81
68
  this._watch();
82
69
  }
83
- /**
84
- * @internal
85
- * Registers a dependency with the given FlowSignal.
86
- * @param dependency - The FlowSignal to register as a dependency.
87
- */
70
+ /*@internal*/
88
71
  _registerDependency(dependency) {
89
72
  this._dependencies.add(dependency);
90
73
  dependency._registerListener(this);
91
74
  }
92
- /**
93
- * @internal
94
- * Unregisters the given dependency.
95
- * @param dependency - The FlowSignal to unregister.
96
- */
75
+ /*@internal*/
97
76
  _unregisterDependency(dependency) {
98
77
  this._dependencies.delete(dependency);
99
78
  dependency._unregisterListener(this);
100
79
  }
101
- /**
102
- * @internal
103
- * Registers a listener (another FlowSignal) to this signal.
104
- * @param signal - The FlowSignal to register as a listener.
105
- */
80
+ /*@internal*/
106
81
  _registerListener(signal) {
107
82
  this._listeners.add(signal);
108
83
  }
109
- /**
110
- * @internal
111
- * Unregisters the given listener.
112
- * @param signal - The FlowSignal to unregister.
113
- */
84
+ /*@internal*/
114
85
  _unregisterListener(signal) {
115
86
  this._listeners.delete(signal);
116
87
  }
117
- /**
118
- * @internal
119
- * Registers a FlowEffect to this signal.
120
- * @param effect - The FlowEffect to register.
121
- */
88
+ /*@internal*/
122
89
  _registerEffect(effect) {
123
90
  this._effects.add(effect);
124
91
  }
125
- /**
126
- * @internal
127
- * Unregisters the given FlowEffect.
128
- * @param effect - The FlowEffect to unregister.
129
- */
92
+ /*@internal*/
130
93
  _unregisterEffect(effect) {
131
94
  this._effects.delete(effect);
132
95
  }
133
96
  }
134
97
 
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
98
  class FlowEffect {
228
- /* API --------------------------------------------------- */
229
99
  /**
230
100
  * Creates a new FlowEffect.
231
101
  *
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.
102
+ * @param apply - A side-effect function that receives a getter and a watcher to
103
+ * access and register dependencies on reactive observables and signals.
104
+ *
105
+ * @remarks
106
+ * The provided function is executed immediately in a tracked mode to collect dependencies.
107
+ * On subsequent executions, it runs in an untracked mode.
234
108
  *
235
109
  * @public
236
110
  */
@@ -240,16 +114,16 @@ class FlowEffect {
240
114
  this._exec();
241
115
  }
242
116
  /**
243
- * Disposes the effect, unregistering all its dependencies.
117
+ * Disposes the effect, unregistering all its tracked dependencies.
244
118
  *
245
119
  * @remarks
246
- * After disposal, the effect should no longer be used. Calling this method on an already
247
- * disposed effect will throw an error.
120
+ * Once disposed, the effect must no longer be used. Trying to dispose an effect
121
+ * that is already disposed will throw an error.
248
122
  *
249
123
  * @public
250
124
  */
251
125
  dispose() {
252
- if (this._disposed) throw new Error("Effect is disposed");
126
+ if (this._disposed) throw new Error("[PicoFlow] Effect is disposed");
253
127
  Array.from(this._dependencies).forEach((dependency) => {
254
128
  this._unregisterDependency(dependency);
255
129
  });
@@ -258,7 +132,7 @@ class FlowEffect {
258
132
  /**
259
133
  * Indicates whether this effect has been disposed.
260
134
  *
261
- * @returns A boolean value indicating if the effect is disposed.
135
+ * @returns A boolean value that is true if the effect is disposed, false otherwise.
262
136
  *
263
137
  * @public
264
138
  */
@@ -266,168 +140,198 @@ class FlowEffect {
266
140
  return this._disposed;
267
141
  }
268
142
  /* INTERNAL ------------------------------------------------------------ */
269
- /**
270
- * @internal
271
- */
272
143
  _disposed = false;
273
- /**
274
- * @internal
275
- */
276
144
  _initialized = false;
277
- /**
278
- * @internal
279
- */
280
145
  _dependencies = /* @__PURE__ */ new Set();
281
- /**
282
- * @internal
283
- * A tracked getter that registers a dependency when accessing an observable.
284
- */
285
146
  _trackedGet = (observable) => observable._getFrom(this);
286
- /**
287
- * @internal
288
- * A tracked watcher that registers a dependency when watching a signal.
289
- */
290
147
  _trackedWatch = (signal) => signal._watchFrom(this);
291
- /**
292
- * @internal
293
- * An untracked getter that simply retrieves the current value from an observable.
294
- */
295
148
  _untrackedGet = (observable) => observable.get();
296
- /**
297
- * @internal
298
- * An untracked watcher that calls the default watch on a signal.
299
- */
300
149
  _untrackedWatch = (signal) => signal._watch();
301
- /**
302
- * @internal
303
- * Execution function used during initialization (tracked mode).
304
- */
305
150
  _trackedExec;
306
- /**
307
- * @internal
308
- * Execution function used after initialization (untracked mode).
309
- */
310
151
  _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
- */
152
+ /*@internal*/
318
153
  _exec() {
319
- if (this._disposed) throw new Error("Effect is disposed");
154
+ if (this._disposed)
155
+ throw new Error("[PicoFlow] Effect is disposed");
320
156
  if (this._initialized) this._untrackedExec();
321
157
  else {
322
158
  this._trackedExec();
323
159
  this._initialized = true;
324
160
  }
325
161
  }
326
- /**
327
- * @internal
328
- * Registers a dependency on the given signal.
329
- *
330
- * @param dependency - The FlowSignal to register as a dependency.
331
- */
162
+ /*@internal*/
332
163
  _registerDependency(dependency) {
333
164
  this._dependencies.add(dependency);
334
165
  dependency._registerEffect(this);
335
166
  }
336
- /**
337
- * @internal
338
- * Unregisters the given dependency.
339
- *
340
- * @param dependency - The FlowSignal to unregister.
341
- */
167
+ /*@internal*/
342
168
  _unregisterDependency(dependency) {
343
169
  this._dependencies.delete(dependency);
344
170
  dependency._unregisterEffect(this);
345
171
  }
346
172
  }
347
173
 
348
- class FlowResource extends FlowObservable {
174
+ class FlowObservable extends FlowSignal {
175
+ /* INTERNAL -------------------------------------------*/
176
+ /*@internal*/
177
+ _value;
178
+ /*@internal*/
179
+ _getFrom(listener) {
180
+ listener._registerDependency(this);
181
+ return this.get();
182
+ }
349
183
  /**
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.
184
+ * Subscribes a listener function to changes of the observable.
185
+ * The listener is executed immediately with the current value and on subsequent updates.
186
+ * @param listener - A callback function that receives the new value.
187
+ * @returns A disposer function to cancel the subscription.
188
+ */
189
+ subscribe(listener) {
190
+ const effect = new FlowEffect((get) => {
191
+ listener(get(this));
192
+ });
193
+ return () => effect.dispose();
194
+ }
195
+ }
196
+
197
+ class FlowConstant extends FlowObservable {
198
+ /**
199
+ * Creates a new FlowConstant instance.
200
+ *
201
+ * @param value - Either a direct value of type T or a function returning a value of type T for lazy initialization.
353
202
  * @public
354
203
  */
355
- constructor(fetch, initial) {
204
+ constructor(value) {
356
205
  super();
357
- this._value = initial;
358
- this._fetch = fetch;
206
+ this._initEager(value);
359
207
  }
360
208
  /**
361
- * Retrieves the current resource value.
362
- * @returns The current value.
363
- * @throws Error if the resource is disposed.
209
+ * Retrieves the constant value, computing it lazily if needed.
210
+ *
211
+ * Accessing this method will initialize the value if it has not been computed already.
212
+ * Throws an error if the instance has been disposed or if lazy initialization fails.
213
+ *
214
+ * @returns The cached constant value.
215
+ * @throws Error if the constant is disposed or cannot be initialized.
364
216
  * @public
365
217
  */
366
218
  get() {
367
- if (this._disposed) throw new Error("Resource is disposed");
219
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
220
+ this._initLazy();
368
221
  return this._value;
369
222
  }
223
+ /* INTERNAL --------------------------------------------------------- */
224
+ /*@internal*/
225
+ _initialized = false;
226
+ /*@internal*/
227
+ _init;
228
+ /*@internal*/
229
+ _initEager(value) {
230
+ if (typeof value === "function") {
231
+ this._init = value;
232
+ } else {
233
+ this._value = value;
234
+ this._initialized = true;
235
+ }
236
+ }
237
+ /*@internal*/
238
+ _initLazy() {
239
+ if (!this._initialized && this._init) {
240
+ this._value = this._init();
241
+ this._initialized = true;
242
+ }
243
+ if (!this._initialized)
244
+ throw new Error("[PicoFlow] Primitive can't be initialized");
245
+ }
246
+ }
247
+
248
+ class FlowState extends FlowConstant {
370
249
  /**
371
- * Asynchronously fetches a new value for the resource.
250
+ * Updates the state with a new value.
251
+ * @param value - A new value or a callback function that computes a new value based on the current state.
372
252
  * @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.
253
+ * If the computed new value is strictly equal to the current state value, no change is made and subscribers
254
+ * will not be notified. Otherwise, the state is updated and all subscribers are informed of the change.
255
+ * @throws Error if the state has been disposed.
377
256
  * @public
378
257
  */
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;
258
+ set(value) {
259
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
260
+ const next = typeof value === "function" ? value(this._value) : value;
261
+ if (next === this._value) return;
262
+ this._value = next;
384
263
  this._notify();
385
264
  }
386
- /* INTERNAL ------------------------------------------------ */
387
- /**
388
- * @internal
389
- * The asynchronous function used to fetch the resource value.
390
- */
391
- _fetch;
392
265
  }
393
266
 
394
- class FlowState extends FlowObservable {
267
+ class FlowDerivation extends FlowObservable {
395
268
  /**
396
- * Creates a new FlowState with the given initial value.
397
- * @param value - The initial value for the state.
269
+ * Creates a new FlowDerivation.
270
+ * @param compute - A function that computes the derived value. It is provided with two parameters:
271
+ * a getter and a watcher that respect dependency tracking.
398
272
  * @public
399
273
  */
400
- constructor(value) {
274
+ constructor(compute) {
401
275
  super();
402
- this._value = value;
276
+ this._initEager(compute);
403
277
  }
404
278
  /**
405
- * Retrieves the current state value.
406
- * @returns The current value of the state.
407
- * @throws Error if the state has been disposed.
279
+ * Gets the current derived value.
280
+ * @returns The current computed value.
281
+ * @remarks
282
+ * This method lazily initializes and updates the derivation if it is marked as dirty. It throws an error
283
+ * if the derivation has been disposed.
408
284
  * @public
409
285
  */
410
286
  get() {
411
- if (this._disposed) throw new Error("State is disposed");
287
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
288
+ this._initLazy();
289
+ this._compute();
412
290
  return this._value;
413
291
  }
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();
292
+ /* INTERNAL --------------------------------------------------------- */
293
+ _initialized = false;
294
+ _dirty = false;
295
+ _trackedGet = (observable) => observable._getFrom(this);
296
+ _trackedWatch = (signal) => signal._watchFrom(this);
297
+ _untrackedGet = (observable) => observable.get();
298
+ _untrackedWatch = (signal) => signal._watch();
299
+ _trackedCompute;
300
+ _untrackedCompute;
301
+ _initEager(compute) {
302
+ this._trackedCompute = () => compute(this._trackedGet, this._trackedWatch);
303
+ this._untrackedCompute = () => compute(this._untrackedGet, this._untrackedWatch);
304
+ }
305
+ _initLazy() {
306
+ if (!this._initialized) {
307
+ this._value = this._trackedCompute();
308
+ this._initialized = true;
309
+ }
310
+ }
311
+ /* @internal */
312
+ _compute() {
313
+ if (this._dirty) {
314
+ this._value = this._untrackedCompute();
315
+ this._dirty = false;
316
+ }
317
+ }
318
+ /* @internal */
319
+ _notify() {
320
+ this._dirty = true;
321
+ super._notify();
322
+ }
323
+ /* @internal */
324
+ _watch() {
325
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
326
+ this._initLazy();
327
+ this._compute();
428
328
  }
429
329
  }
430
330
 
331
+ function isDisposable(obj) {
332
+ return obj !== null && obj !== void 0 && typeof obj.dispose === "function";
333
+ }
334
+
431
335
  class FlowMap extends FlowState {
432
336
  /**
433
337
  * A reactive state that holds the most recent key and value that were set.
@@ -454,15 +358,16 @@ class FlowMap extends FlowState {
454
358
  *
455
359
  * @param key - The key at which to set the value.
456
360
  * @param value - The value to set.
361
+ * @throws If the FlowMap instance is disposed.
457
362
  *
458
363
  * @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.
364
+ * Updates the internal map, emits the key-value pair via {@link FlowMap.$lastSet},
365
+ * and notifies all subscribers of the change.
461
366
  *
462
367
  * @public
463
368
  */
464
369
  setAt(key, value) {
465
- if (this._disposed) throw new Error("StateMap is disposed");
370
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
466
371
  this._value.set(key, value);
467
372
  this.$lastSet.set({ key, value });
468
373
  this._notify();
@@ -471,16 +376,16 @@ class FlowMap extends FlowState {
471
376
  * Deletes the value at the specified key from the underlying map.
472
377
  *
473
378
  * @param key - The key to delete.
379
+ * @throws If the FlowMap instance is disposed.
474
380
  *
475
381
  * @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.
382
+ * Removes the key from the internal map, emits the deleted key and its value via {@link FlowMap.$lastDeleted},
383
+ * and notifies all subscribers of the change.
479
384
  *
480
385
  * @public
481
386
  */
482
387
  delete(key) {
483
- if (this._disposed) throw new Error("StateMap is disposed");
388
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
484
389
  const value = this._value.get(key);
485
390
  this._value.delete(key);
486
391
  this.$lastDeleted.set({ key, value });
@@ -489,29 +394,26 @@ class FlowMap extends FlowState {
489
394
  }
490
395
 
491
396
  class FlowStream extends FlowObservable {
492
- /* API -------------------------------------------------------- */
493
397
  /**
494
398
  * Creates a new FlowStream.
495
399
  * @param updater - A function that receives a setter to update the stream's value.
496
400
  * It should return a disposer function that will be called upon disposal.
497
- * @param initial - The initial value of the stream.
498
401
  * @public
499
402
  */
500
- constructor(updater, initial) {
403
+ constructor(updater) {
501
404
  super();
502
- this._value = initial;
503
405
  this._disposer = updater((value) => {
504
406
  this._set(value);
505
407
  });
506
408
  }
507
409
  /**
508
410
  * Retrieves the current value of the stream.
509
- * @returns The current value.
411
+ * @returns The current value, or undefined if no value has been set yet.
510
412
  * @throws Error if the stream is disposed.
511
413
  * @public
512
414
  */
513
415
  get() {
514
- if (this._disposed) throw new Error("Stream is disposed");
416
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
515
417
  return this._value;
516
418
  }
517
419
  /**
@@ -526,36 +428,339 @@ class FlowStream extends FlowObservable {
526
428
  this._disposer();
527
429
  }
528
430
  /* INTERNAL ------------------------------------------------------ */
431
+ _disposer;
432
+ _set(value) {
433
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
434
+ if (value === this._value) return;
435
+ this._value = value;
436
+ this._notify();
437
+ }
438
+ }
439
+
440
+ class FlowStreamAsync extends FlowObservable {
529
441
  /**
530
- * @internal
531
- * The disposer function returned by the updater.
442
+ * Creates a new asynchronous FlowStream.
443
+ * @param updater - A function that receives a setter to update the stream's value.
444
+ * It should return a disposer function that will be called upon disposal.
445
+ * @remarks The updater function can invoke the setter asynchronously to update the stream.
446
+ * @public
532
447
  */
533
- _disposer;
448
+ constructor(updater) {
449
+ super();
450
+ this._disposer = updater((value) => {
451
+ this._set(value);
452
+ });
453
+ this._value = new Promise((resolve) => {
454
+ this._resolve = resolve;
455
+ });
456
+ }
457
+ /**
458
+ * Retrieves the current value of the stream as a Promise.
459
+ * @returns A Promise that resolves to the current value.
460
+ * @throws Error if the stream is disposed.
461
+ * @public
462
+ */
463
+ get() {
464
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
465
+ return this._value;
466
+ }
534
467
  /**
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
468
+ * Disposes the stream, releasing all resources.
469
+ * @remarks In addition to disposing the underlying observable, this method calls the disposer
470
+ * returned by the updater.
471
+ * @public
539
472
  */
473
+ dispose() {
474
+ super.dispose();
475
+ this._disposer();
476
+ }
477
+ /* INTERNAL ------------------------------------------------------ */
478
+ _initialized = false;
479
+ _awaitedValue;
480
+ _resolve;
481
+ _disposer;
540
482
  _set(value) {
541
- if (this._disposed) throw new Error("Stream is disposed");
483
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
484
+ if (!this._initialized) {
485
+ this._resolve(value);
486
+ this._initialized = true;
487
+ this._awaitedValue = value;
488
+ this._notify();
489
+ return;
490
+ }
491
+ if (value === this._awaitedValue) return;
492
+ this._value = Promise.resolve(value);
493
+ this._awaitedValue = value;
494
+ this._notify();
495
+ }
496
+ }
497
+
498
+ class FlowResource extends FlowObservable {
499
+ /**
500
+ * Creates a new FlowResource.
501
+ * @param fetch - An asynchronous function that retrieves the resource's value.
502
+ *
503
+ * @public
504
+ */
505
+ constructor(fetch) {
506
+ super();
507
+ this._fetch = fetch;
508
+ }
509
+ /**
510
+ * Retrieves the current resource value.
511
+ * @returns The current value, or undefined if the resource has not been fetched yet.
512
+ * @throws Error if the resource is disposed.
513
+ * @public
514
+ */
515
+ get() {
516
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
517
+ return this._value;
518
+ }
519
+ /**
520
+ * Asynchronously fetches a new value for the resource.
521
+ * @remarks
522
+ * Executes the internal fetch function. If the fetched value differs from the current one,
523
+ * updates the resource's value and notifies subscribers.
524
+ * @returns A Promise that resolves when the fetch operation is complete.
525
+ * @throws Error if the resource is disposed.
526
+ * @public
527
+ */
528
+ async fetch() {
529
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
530
+ const value = await this._fetch();
542
531
  if (value === this._value) return;
543
532
  this._value = value;
544
533
  this._notify();
545
534
  }
535
+ /* INTERNAL ------------------------------------------------ */
536
+ _fetch;
537
+ }
538
+
539
+ class FlowResourceAsync extends FlowObservable {
540
+ /**
541
+ * Creates a new FlowResource.
542
+ * @param fetch - An asynchronous function that retrieves the resource's value.
543
+ * @public
544
+ */
545
+ constructor(fetch) {
546
+ super();
547
+ this._fetch = fetch;
548
+ }
549
+ /**
550
+ * Retrieves the current resource value.
551
+ * @returns The current value, or undefined if the resource has not been fetched yet.
552
+ * @throws Error if the resource is disposed.
553
+ * @public
554
+ */
555
+ get() {
556
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
557
+ if (!this._value) this._value = this._fetch();
558
+ return this._value;
559
+ }
560
+ /**
561
+ * Asynchronously fetches a new value for the resource.
562
+ * @remarks
563
+ * Executes the internal fetch function. If the fetched value differs from the current one,
564
+ * updates the resource's value and notifies subscribers.
565
+ * @returns A Promise that resolves when the fetch operation is complete.
566
+ * @throws Error if the resource is disposed.
567
+ * @public
568
+ */
569
+ async fetch() {
570
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
571
+ this._value = this._fetch();
572
+ this._notify();
573
+ }
574
+ /* INTERNAL ------------------------------------------------ */
575
+ _fetch;
576
+ }
577
+
578
+ class FlowArray extends FlowObservable {
579
+ /**
580
+ * Last action performed on the FlowArray.
581
+ * @public
582
+ */
583
+ $lastAction;
584
+ /**
585
+ * Creates an instance of FlowArray.
586
+ * @param value - Initial array value.
587
+ * @public
588
+ */
589
+ constructor(value = []) {
590
+ super();
591
+ this._value = value;
592
+ this.$lastAction = state({
593
+ type: "set",
594
+ items: value
595
+ });
596
+ }
597
+ /**
598
+ * Gets the current length of the array.
599
+ * @returns The length of the array.
600
+ * @public
601
+ */
602
+ get length() {
603
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
604
+ return this._value.length;
605
+ }
606
+ /**
607
+ * Returns a copy of the internal array.
608
+ * @returns A copy of the array.
609
+ * @public
610
+ */
611
+ get() {
612
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
613
+ return [...this._value];
614
+ }
615
+ /**
616
+ * Replaces the entire array with new items.
617
+ * @param items - The new array of items.
618
+ * @public
619
+ */
620
+ set(items) {
621
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
622
+ this._value.forEach((item) => {
623
+ if (isDisposable(item)) item.dispose({ self: true });
624
+ });
625
+ this._value = items;
626
+ this._notify();
627
+ this.$lastAction.set({ type: "set", items });
628
+ }
629
+ /**
630
+ * Replaces an item at a specific index.
631
+ * @param index - The index of the item to replace.
632
+ * @param item - The new item.
633
+ * @public
634
+ */
635
+ setItem(index, item) {
636
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
637
+ if (index < 0 || index >= this._value.length) {
638
+ throw new Error("[PicoFlow] Index out of bounds");
639
+ }
640
+ this._value[index] = item;
641
+ this._notify();
642
+ this.$lastAction.set({ type: "setItem", index, item });
643
+ }
644
+ /**
645
+ * Appends an item to the end of the array.
646
+ * @param item - The item to append.
647
+ * @public
648
+ */
649
+ push(item) {
650
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
651
+ this._value.push(item);
652
+ this._notify();
653
+ this.$lastAction.set({ type: "push", item });
654
+ }
655
+ /**
656
+ * Removes the last item from the array.
657
+ * @public
658
+ */
659
+ pop() {
660
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
661
+ const item = this._value.pop();
662
+ if (isDisposable(item)) {
663
+ item.dispose({ self: true });
664
+ }
665
+ this._notify();
666
+ this.$lastAction.set({ type: "pop" });
667
+ }
668
+ /**
669
+ * Inserts an item at the beginning of the array.
670
+ * @param item - The item to insert.
671
+ * @public
672
+ */
673
+ unshift(item) {
674
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
675
+ this._value.unshift(item);
676
+ this._notify();
677
+ this.$lastAction.set({ type: "unshift", item });
678
+ }
679
+ /**
680
+ * Removes the first item from the array.
681
+ * @public
682
+ */
683
+ shift() {
684
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
685
+ const item = this._value.shift();
686
+ if (isDisposable(item)) {
687
+ item.dispose({ self: true });
688
+ }
689
+ this._notify();
690
+ this.$lastAction.set({ type: "shift" });
691
+ }
692
+ /**
693
+ * Changes the content of the array.
694
+ * @param start - The starting index.
695
+ * @param deleteCount - Number of items to remove.
696
+ * @param newItems - New items to add.
697
+ * @public
698
+ */
699
+ splice(start, deleteCount, ...newItems) {
700
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
701
+ const items = this._value.splice(start, deleteCount, ...newItems);
702
+ items.forEach((item) => {
703
+ if (isDisposable(item)) item.dispose({ self: true });
704
+ });
705
+ this._notify();
706
+ this.$lastAction.set({
707
+ type: "splice",
708
+ start,
709
+ deleteCount,
710
+ items: newItems
711
+ });
712
+ }
713
+ /**
714
+ * Clears all items from the array.
715
+ * @public
716
+ */
717
+ clear() {
718
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
719
+ const items = [...this._value];
720
+ items.forEach((item) => {
721
+ if (isDisposable(item)) item.dispose({ self: true });
722
+ });
723
+ this._value = [];
724
+ this._notify();
725
+ this.$lastAction.set({ type: "clear" });
726
+ }
727
+ /**
728
+ * Disposes the FlowArray and its items.
729
+ * @param options - Disposal options.
730
+ * @public
731
+ */
732
+ dispose(options) {
733
+ super.dispose(options);
734
+ this._value.forEach((item) => {
735
+ if (isDisposable(item)) item.dispose(options);
736
+ });
737
+ this._value = [];
738
+ }
739
+ /* INTERNAL */
740
+ /*@internal*/
741
+ _value = [];
546
742
  }
547
743
 
548
744
  function signal() {
549
745
  return new FlowSignal();
550
746
  }
747
+ function constant(value) {
748
+ return new FlowConstant(value);
749
+ }
551
750
  function state(value) {
552
751
  return new FlowState(value);
553
752
  }
554
- function resource(fn, initial) {
555
- return new FlowResource(fn, initial);
753
+ function resource(fn) {
754
+ return new FlowResource(fn);
556
755
  }
557
- function stream(updater, initial) {
558
- return new FlowStream(updater, initial);
756
+ function resourceAsync(fn) {
757
+ return new FlowResourceAsync(fn);
758
+ }
759
+ function stream(updater) {
760
+ return new FlowStream(updater);
761
+ }
762
+ function streamAsync(updater) {
763
+ return new FlowStreamAsync(updater);
559
764
  }
560
765
  function derivation(fn) {
561
766
  return new FlowDerivation(fn);
@@ -568,5 +773,8 @@ function map(initial) {
568
773
  new Map(initial ? Object.entries(initial) : [])
569
774
  );
570
775
  }
776
+ function array(initial) {
777
+ return new FlowArray(initial);
778
+ }
571
779
 
572
- export { derivation, effect, map, resource, signal, state, stream };
780
+ export { array, constant, derivation, effect, isDisposable, map, resource, resourceAsync, signal, state, stream, streamAsync };