@ibodr/state 0.0.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/dist/index.d.ts +1169 -0
- package/dist/index.mjs +1311 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +37 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An ArraySet operates as an array until it reaches a certain size, after which a Set is used
|
|
3
|
+
* instead. In either case, the same methods are used to get, set, remove, and visit the items.
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
declare class ArraySet<T> {
|
|
7
|
+
private arraySize;
|
|
8
|
+
private array;
|
|
9
|
+
private set;
|
|
10
|
+
/**
|
|
11
|
+
* Get whether this ArraySet has any elements.
|
|
12
|
+
*
|
|
13
|
+
* @returns True if this ArraySet has any elements, false otherwise.
|
|
14
|
+
*/
|
|
15
|
+
get isEmpty(): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Add an element to the ArraySet if it is not already present.
|
|
18
|
+
*
|
|
19
|
+
* @param elem - The element to add to the set
|
|
20
|
+
* @returns `true` if the element was added, `false` if it was already present
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const arraySet = new ArraySet<string>()
|
|
24
|
+
*
|
|
25
|
+
* console.log(arraySet.add('hello')) // true
|
|
26
|
+
* console.log(arraySet.add('hello')) // false (already exists)
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
add(elem: T): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Remove an element from the ArraySet if it is present.
|
|
32
|
+
*
|
|
33
|
+
* @param elem - The element to remove from the set
|
|
34
|
+
* @returns `true` if the element was removed, `false` if it was not present
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const arraySet = new ArraySet<string>()
|
|
38
|
+
* arraySet.add('hello')
|
|
39
|
+
*
|
|
40
|
+
* console.log(arraySet.remove('hello')) // true
|
|
41
|
+
* console.log(arraySet.remove('hello')) // false (not present)
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
remove(elem: T): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Execute a callback function for each element in the ArraySet.
|
|
47
|
+
*
|
|
48
|
+
* @param visitor - A function to call for each element in the set
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const arraySet = new ArraySet<string>()
|
|
52
|
+
* arraySet.add('hello')
|
|
53
|
+
* arraySet.add('world')
|
|
54
|
+
*
|
|
55
|
+
* arraySet.visit((item) => {
|
|
56
|
+
* console.log(item) // 'hello', 'world'
|
|
57
|
+
* })
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
visit(visitor: (item: T) => void): void;
|
|
61
|
+
/**
|
|
62
|
+
* Make the ArraySet iterable, allowing it to be used in for...of loops and with spread syntax.
|
|
63
|
+
*
|
|
64
|
+
* @returns An iterator that yields each element in the set
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* const arraySet = new ArraySet<number>()
|
|
68
|
+
* arraySet.add(1)
|
|
69
|
+
* arraySet.add(2)
|
|
70
|
+
*
|
|
71
|
+
* for (const item of arraySet) {
|
|
72
|
+
* console.log(item) // 1, 2
|
|
73
|
+
* }
|
|
74
|
+
*
|
|
75
|
+
* const items = [...arraySet] // [1, 2]
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
[Symbol.iterator](): Generator<T, void, unknown>;
|
|
79
|
+
/**
|
|
80
|
+
* Check whether an element is present in the ArraySet.
|
|
81
|
+
*
|
|
82
|
+
* @param elem - The element to check for
|
|
83
|
+
* @returns `true` if the element is present, `false` otherwise
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const arraySet = new ArraySet<string>()
|
|
87
|
+
* arraySet.add('hello')
|
|
88
|
+
*
|
|
89
|
+
* console.log(arraySet.has('hello')) // true
|
|
90
|
+
* console.log(arraySet.has('world')) // false
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
has(elem: T): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Remove all elements from the ArraySet.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const arraySet = new ArraySet<string>()
|
|
100
|
+
* arraySet.add('hello')
|
|
101
|
+
* arraySet.add('world')
|
|
102
|
+
*
|
|
103
|
+
* arraySet.clear()
|
|
104
|
+
* console.log(arraySet.size()) // 0
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
clear(): void;
|
|
108
|
+
/**
|
|
109
|
+
* Get the number of elements in the ArraySet.
|
|
110
|
+
*
|
|
111
|
+
* @returns The number of elements in the set
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const arraySet = new ArraySet<string>()
|
|
115
|
+
* console.log(arraySet.size()) // 0
|
|
116
|
+
*
|
|
117
|
+
* arraySet.add('hello')
|
|
118
|
+
* console.log(arraySet.size()) // 1
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
size(): number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A unique symbol used to indicate that a signal's value should be reset or that
|
|
126
|
+
* there is insufficient history to compute diffs between epochs.
|
|
127
|
+
*
|
|
128
|
+
* This value is returned by {@link Signal.getDiffSince} when the requested epoch
|
|
129
|
+
* is too far in the past and the diff sequence cannot be reconstructed.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* import { atom, getGlobalEpoch, RESET_VALUE } from '@ibodr/state'
|
|
134
|
+
*
|
|
135
|
+
* const count = atom('count', 0, { historyLength: 3 })
|
|
136
|
+
* const oldEpoch = getGlobalEpoch()
|
|
137
|
+
*
|
|
138
|
+
* // Make many changes that exceed history length
|
|
139
|
+
* count.set(1)
|
|
140
|
+
* count.set(2)
|
|
141
|
+
* count.set(3)
|
|
142
|
+
* count.set(4)
|
|
143
|
+
*
|
|
144
|
+
* const diffs = count.getDiffSince(oldEpoch)
|
|
145
|
+
* if (diffs === RESET_VALUE) {
|
|
146
|
+
* console.log('Too many changes, need to reset state')
|
|
147
|
+
* }
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* @public
|
|
151
|
+
*/
|
|
152
|
+
declare const RESET_VALUE: unique symbol;
|
|
153
|
+
/**
|
|
154
|
+
* Type representing the the unique symbol RESET_VALUE symbol, used in type annotations
|
|
155
|
+
* to indicate when a signal value should be reset or when diff computation
|
|
156
|
+
* cannot proceed due to insufficient history.
|
|
157
|
+
*
|
|
158
|
+
* @public
|
|
159
|
+
*/
|
|
160
|
+
type RESET_VALUE = typeof RESET_VALUE;
|
|
161
|
+
/**
|
|
162
|
+
* A reactive value container that can change over time and track diffs between sequential values.
|
|
163
|
+
*
|
|
164
|
+
* Signals are the foundation of the \@ibodr/state reactive system. They automatically manage
|
|
165
|
+
* dependencies and trigger updates when their values change. Any computed signal or effect
|
|
166
|
+
* that reads from this signal will be automatically recomputed when the signal's value changes.
|
|
167
|
+
*
|
|
168
|
+
* There are two types of signal:
|
|
169
|
+
* - **Atomic signals** - Created using `atom()`. These are mutable containers that can be
|
|
170
|
+
* directly updated using `set()` or `update()` methods.
|
|
171
|
+
* - **Computed signals** - Created using `computed()`. These derive their values from other
|
|
172
|
+
* signals and are automatically recomputed when dependencies change.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* import { atom, computed } from '@ibodr/state'
|
|
177
|
+
*
|
|
178
|
+
* // Create an atomic signal
|
|
179
|
+
* const count = atom('count', 0)
|
|
180
|
+
*
|
|
181
|
+
* // Create a computed signal that derives from the atom
|
|
182
|
+
* const doubled = computed('doubled', () => count.get() * 2)
|
|
183
|
+
*
|
|
184
|
+
* console.log(doubled.get()) // 0
|
|
185
|
+
* count.set(5)
|
|
186
|
+
* console.log(doubled.get()) // 10
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* @public
|
|
190
|
+
*/
|
|
191
|
+
interface Signal<Value, Diff = unknown> {
|
|
192
|
+
/**
|
|
193
|
+
* A human-readable identifier for this signal, used primarily for debugging and performance profiling.
|
|
194
|
+
*
|
|
195
|
+
* The name is displayed in debug output from {@link whyAmIRunning} and other diagnostic tools.
|
|
196
|
+
* It does not need to be globally unique within your application.
|
|
197
|
+
*/
|
|
198
|
+
name: string;
|
|
199
|
+
/**
|
|
200
|
+
* Gets the current value of the signal and establishes a dependency relationship.
|
|
201
|
+
*
|
|
202
|
+
* When called from within a computed signal or effect, this signal will be automatically
|
|
203
|
+
* tracked as a dependency. If this signal's value changes, any dependent computations
|
|
204
|
+
* or effects will be marked for re-execution.
|
|
205
|
+
*
|
|
206
|
+
* @returns The current value stored in the signal
|
|
207
|
+
*/
|
|
208
|
+
get(): Value;
|
|
209
|
+
/**
|
|
210
|
+
* The global epoch number when this signal's value last changed.
|
|
211
|
+
*
|
|
212
|
+
* Note that this represents when the value actually changed, not when it was last computed.
|
|
213
|
+
* A computed signal may recalculate and produce the same value without changing its epoch.
|
|
214
|
+
* This is used internally for dependency tracking and history management.
|
|
215
|
+
*/
|
|
216
|
+
lastChangedEpoch: number;
|
|
217
|
+
/**
|
|
218
|
+
* Gets the sequence of diffs that occurred between a specific epoch and the current state.
|
|
219
|
+
*
|
|
220
|
+
* This method enables incremental synchronization by providing a list of changes that
|
|
221
|
+
* have occurred since a specific point in time. If the requested epoch is too far in
|
|
222
|
+
* the past or the signal doesn't have enough history, it returns the unique symbol RESET_VALUE
|
|
223
|
+
* to indicate that a full state reset is required.
|
|
224
|
+
*
|
|
225
|
+
* @param epoch - The epoch timestamp to get diffs since
|
|
226
|
+
* @returns An array of diff objects representing changes since the epoch, or the unique symbol RESET_VALUE if insufficient history is available
|
|
227
|
+
*/
|
|
228
|
+
getDiffSince(epoch: number): RESET_VALUE | Diff[];
|
|
229
|
+
/**
|
|
230
|
+
* Gets the current value of the signal without establishing a dependency relationship.
|
|
231
|
+
*
|
|
232
|
+
* This method bypasses the automatic dependency tracking system, making it useful for
|
|
233
|
+
* performance-critical code paths where the overhead of dependency capture would be
|
|
234
|
+
* problematic. Use with caution as it breaks the reactive guarantees of the system.
|
|
235
|
+
*
|
|
236
|
+
* **Warning**: This method should only be used when you're certain that you don't need
|
|
237
|
+
* the calling context to react to changes in this signal.
|
|
238
|
+
*
|
|
239
|
+
* @param ignoreErrors - Whether to suppress errors during value retrieval (optional)
|
|
240
|
+
* @returns The current value without establishing dependencies
|
|
241
|
+
*/
|
|
242
|
+
__unsafe__getWithoutCapture(ignoreErrors?: boolean): Value;
|
|
243
|
+
/** @internal */
|
|
244
|
+
children: ArraySet<Child>;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Internal interface representing a child node in the signal dependency graph.
|
|
248
|
+
*
|
|
249
|
+
* This interface is used internally by the reactive system to manage dependencies
|
|
250
|
+
* between signals, computed values, and effects. Each child tracks its parent
|
|
251
|
+
* signals and maintains state needed for efficient dependency graph traversal
|
|
252
|
+
* and change propagation.
|
|
253
|
+
*
|
|
254
|
+
* @internal
|
|
255
|
+
*/
|
|
256
|
+
interface Child {
|
|
257
|
+
/**
|
|
258
|
+
* The epoch when this child was last traversed during dependency graph updates.
|
|
259
|
+
* Used to prevent redundant traversals during change propagation.
|
|
260
|
+
*/
|
|
261
|
+
lastTraversedEpoch: number;
|
|
262
|
+
/**
|
|
263
|
+
* Set of parent signals that this child depends on.
|
|
264
|
+
* Used for efficient lookup and cleanup operations.
|
|
265
|
+
*/
|
|
266
|
+
readonly parentSet: ArraySet<Signal<any, any>>;
|
|
267
|
+
/**
|
|
268
|
+
* Array of parent signals that this child depends on.
|
|
269
|
+
* Maintained in parallel with parentSet for ordered access.
|
|
270
|
+
*/
|
|
271
|
+
readonly parents: Signal<any, any>[];
|
|
272
|
+
/**
|
|
273
|
+
* Array of epochs corresponding to each parent signal.
|
|
274
|
+
* Used to detect which parents have changed since last computation.
|
|
275
|
+
*/
|
|
276
|
+
readonly parentEpochs: number[];
|
|
277
|
+
/**
|
|
278
|
+
* Human-readable name for this child, used in debugging output.
|
|
279
|
+
*/
|
|
280
|
+
readonly name: string;
|
|
281
|
+
/**
|
|
282
|
+
* Whether this child is currently subscribed to change notifications.
|
|
283
|
+
* Used to optimize resource usage by unsubscribing inactive dependencies.
|
|
284
|
+
*/
|
|
285
|
+
isActivelyListening: boolean;
|
|
286
|
+
/**
|
|
287
|
+
* Debug information tracking ancestor epochs in the dependency graph.
|
|
288
|
+
* Only populated in debug builds for diagnostic purposes.
|
|
289
|
+
*/
|
|
290
|
+
__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* A function type that computes the difference between two values of a signal.
|
|
294
|
+
*
|
|
295
|
+
* This function is used to generate incremental diffs that can be applied to
|
|
296
|
+
* reconstruct state changes over time. It's particularly useful for features
|
|
297
|
+
* like undo/redo, synchronization, and change tracking.
|
|
298
|
+
*
|
|
299
|
+
* The function should analyze the previous and current values and return a
|
|
300
|
+
* diff object that represents the change. If the diff cannot be computed
|
|
301
|
+
* (e.g., the values are too different or incompatible), it should return
|
|
302
|
+
* the unique symbol RESET_VALUE to indicate that a full state reset is required.
|
|
303
|
+
*
|
|
304
|
+
* @param previousValue - The previous value of the signal
|
|
305
|
+
* @param currentValue - The current value of the signal
|
|
306
|
+
* @param lastComputedEpoch - The epoch when the previous value was set
|
|
307
|
+
* @param currentEpoch - The epoch when the current value was set
|
|
308
|
+
* @returns A diff object representing the change, or the unique symbol RESET_VALUE if no diff can be computed
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```ts
|
|
312
|
+
* import { atom, RESET_VALUE } from '@ibodr/state'
|
|
313
|
+
*
|
|
314
|
+
* // Simple numeric diff
|
|
315
|
+
* const numberDiff: ComputeDiff<number, number> = (prev, curr) => curr - prev
|
|
316
|
+
*
|
|
317
|
+
* // Array diff with reset fallback
|
|
318
|
+
* const arrayDiff: ComputeDiff<string[], { added: string[], removed: string[] }> = (prev, curr) => {
|
|
319
|
+
* if (prev.length > 1000 || curr.length > 1000) {
|
|
320
|
+
* return RESET_VALUE // Too complex, force reset
|
|
321
|
+
* }
|
|
322
|
+
* return {
|
|
323
|
+
* added: curr.filter(item => !prev.includes(item)),
|
|
324
|
+
* removed: prev.filter(item => !curr.includes(item))
|
|
325
|
+
* }
|
|
326
|
+
* }
|
|
327
|
+
*
|
|
328
|
+
* const count = atom('count', 0, { computeDiff: numberDiff })
|
|
329
|
+
* ```
|
|
330
|
+
*
|
|
331
|
+
* @public
|
|
332
|
+
*/
|
|
333
|
+
type ComputeDiff<Value, Diff> = (previousValue: Value, currentValue: Value, lastComputedEpoch: number, currentEpoch: number) => Diff | RESET_VALUE;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* The options to configure an atom, passed into the {@link atom} function.
|
|
337
|
+
* @public
|
|
338
|
+
*/
|
|
339
|
+
interface AtomOptions<Value, Diff> {
|
|
340
|
+
/**
|
|
341
|
+
* The maximum number of diffs to keep in the history buffer.
|
|
342
|
+
*
|
|
343
|
+
* If you don't need to compute diffs, or if you will supply diffs manually via {@link Atom.set}, you can leave this as `undefined` and no history buffer will be created.
|
|
344
|
+
*
|
|
345
|
+
* If you expect the value to be part of an active effect subscription all the time, and to not change multiple times inside of a single transaction, you can set this to a relatively low number (e.g. 10).
|
|
346
|
+
*
|
|
347
|
+
* Otherwise, set this to a higher number based on your usage pattern and memory constraints.
|
|
348
|
+
*
|
|
349
|
+
*/
|
|
350
|
+
historyLength?: number;
|
|
351
|
+
/**
|
|
352
|
+
* A method used to compute a diff between the atom's old and new values. If provided, it will not be used unless you also specify {@link AtomOptions.historyLength}.
|
|
353
|
+
*/
|
|
354
|
+
computeDiff?: ComputeDiff<Value, Diff>;
|
|
355
|
+
/**
|
|
356
|
+
* If provided, this will be used to compare the old and new values of the atom to determine if the value has changed.
|
|
357
|
+
* By default, values are compared using first using strict equality (`===`), then `Object.is`, and finally any `.equals` method present in the object's prototype chain.
|
|
358
|
+
* @param a - The old value
|
|
359
|
+
* @param b - The new value
|
|
360
|
+
* @returns True if the values are equal, false otherwise.
|
|
361
|
+
*/
|
|
362
|
+
isEqual?(a: any, b: any): boolean;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.
|
|
366
|
+
*
|
|
367
|
+
* Atoms are created using the {@link atom} function.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```ts
|
|
371
|
+
* const name = atom('name', 'John')
|
|
372
|
+
*
|
|
373
|
+
* print(name.get()) // 'John'
|
|
374
|
+
* ```
|
|
375
|
+
*
|
|
376
|
+
* @public
|
|
377
|
+
*/
|
|
378
|
+
interface Atom<Value, Diff = unknown> extends Signal<Value, Diff> {
|
|
379
|
+
/**
|
|
380
|
+
* Sets the value of this atom to the given value. If the value is the same as the current value, this is a no-op.
|
|
381
|
+
*
|
|
382
|
+
* @param value - The new value to set.
|
|
383
|
+
* @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}.
|
|
384
|
+
*/
|
|
385
|
+
set(value: Value, diff?: Diff): Value;
|
|
386
|
+
/**
|
|
387
|
+
* Updates the value of this atom using the given updater function. If the returned value is the same as the current value, this is a no-op.
|
|
388
|
+
*
|
|
389
|
+
* @param updater - A function that takes the current value and returns the new value.
|
|
390
|
+
*/
|
|
391
|
+
update(updater: (value: Value) => Value): Value;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Creates a new {@link Atom}.
|
|
395
|
+
*
|
|
396
|
+
* An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.
|
|
397
|
+
*
|
|
398
|
+
* @example
|
|
399
|
+
* ```ts
|
|
400
|
+
* const name = atom('name', 'John')
|
|
401
|
+
*
|
|
402
|
+
* name.get() // 'John'
|
|
403
|
+
*
|
|
404
|
+
* name.set('Jane')
|
|
405
|
+
*
|
|
406
|
+
* name.get() // 'Jane'
|
|
407
|
+
* ```
|
|
408
|
+
*
|
|
409
|
+
* @public
|
|
410
|
+
*/
|
|
411
|
+
declare function atom<Value, Diff = unknown>(
|
|
412
|
+
/**
|
|
413
|
+
* A name for the signal. This is used for debugging and profiling purposes, it does not need to be unique.
|
|
414
|
+
*/
|
|
415
|
+
name: string,
|
|
416
|
+
/**
|
|
417
|
+
* The initial value of the signal.
|
|
418
|
+
*/
|
|
419
|
+
initialValue: Value,
|
|
420
|
+
/**
|
|
421
|
+
* The options to configure the atom. See {@link AtomOptions}.
|
|
422
|
+
*/
|
|
423
|
+
options?: AtomOptions<Value, Diff>): Atom<Value, Diff>;
|
|
424
|
+
/**
|
|
425
|
+
* Returns true if the given value is an {@link Atom}.
|
|
426
|
+
*
|
|
427
|
+
* @param value - The value to check
|
|
428
|
+
* @returns True if the value is an Atom, false otherwise
|
|
429
|
+
* @example
|
|
430
|
+
* ```ts
|
|
431
|
+
* const myAtom = atom('test', 42)
|
|
432
|
+
* const notAtom = 'hello'
|
|
433
|
+
*
|
|
434
|
+
* console.log(isAtom(myAtom)) // true
|
|
435
|
+
* console.log(isAtom(notAtom)) // false
|
|
436
|
+
* ```
|
|
437
|
+
* @public
|
|
438
|
+
*/
|
|
439
|
+
declare function isAtom(value: unknown): value is Atom<unknown>;
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Executes the given function without capturing any parents in the current capture context.
|
|
443
|
+
*
|
|
444
|
+
* This is mainly useful if you want to run an effect only when certain signals change while also
|
|
445
|
+
* dereferencing other signals which should not cause the effect to rerun on their own.
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```ts
|
|
449
|
+
* const name = atom('name', 'Sam')
|
|
450
|
+
* const time = atom('time', () => new Date().getTime())
|
|
451
|
+
*
|
|
452
|
+
* setInterval(() => {
|
|
453
|
+
* time.set(new Date().getTime())
|
|
454
|
+
* })
|
|
455
|
+
*
|
|
456
|
+
* react('log name changes', () => {
|
|
457
|
+
* print(name.get(), 'was changed at', unsafe__withoutCapture(() => time.get()))
|
|
458
|
+
* })
|
|
459
|
+
*
|
|
460
|
+
* ```
|
|
461
|
+
*
|
|
462
|
+
* @public
|
|
463
|
+
*/
|
|
464
|
+
declare function unsafe__withoutCapture<T>(fn: () => T): T;
|
|
465
|
+
/**
|
|
466
|
+
* A debugging tool that tells you why a computed signal or effect is running.
|
|
467
|
+
* Call in the body of a computed signal or effect function.
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```ts
|
|
471
|
+
* const name = atom('name', 'Bob')
|
|
472
|
+
* react('greeting', () => {
|
|
473
|
+
* whyAmIRunning()
|
|
474
|
+
* print('Hello', name.get())
|
|
475
|
+
* })
|
|
476
|
+
*
|
|
477
|
+
* name.set('Alice')
|
|
478
|
+
*
|
|
479
|
+
* // 'greeting' is running because:
|
|
480
|
+
* // 'name' changed => 'Alice'
|
|
481
|
+
* ```
|
|
482
|
+
*
|
|
483
|
+
* @public
|
|
484
|
+
*/
|
|
485
|
+
declare function whyAmIRunning(): void;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* A special symbol used to indicate that a computed signal has not been initialized yet.
|
|
489
|
+
* This is passed as the `previousValue` parameter to a computed signal function on its first run.
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* ```ts
|
|
493
|
+
* const count = atom('count', 0)
|
|
494
|
+
* const double = computed('double', (prevValue) => {
|
|
495
|
+
* if (isUninitialized(prevValue)) {
|
|
496
|
+
* console.log('First computation!')
|
|
497
|
+
* }
|
|
498
|
+
* return count.get() * 2
|
|
499
|
+
* })
|
|
500
|
+
* ```
|
|
501
|
+
*
|
|
502
|
+
* @public
|
|
503
|
+
*/
|
|
504
|
+
declare const UNINITIALIZED: unique symbol;
|
|
505
|
+
/**
|
|
506
|
+
* The type of the first value passed to a computed signal function as the 'prevValue' parameter.
|
|
507
|
+
* This type represents the uninitialized state of a computed signal before its first calculation.
|
|
508
|
+
*
|
|
509
|
+
* @see {@link isUninitialized}
|
|
510
|
+
* @public
|
|
511
|
+
*/
|
|
512
|
+
type UNINITIALIZED = typeof UNINITIALIZED;
|
|
513
|
+
/**
|
|
514
|
+
* Call this inside a computed signal function to determine whether it is the first time the function is being called.
|
|
515
|
+
*
|
|
516
|
+
* Mainly useful for incremental signal computation.
|
|
517
|
+
*
|
|
518
|
+
* @example
|
|
519
|
+
* ```ts
|
|
520
|
+
* const count = atom('count', 0)
|
|
521
|
+
* const double = computed('double', (prevValue) => {
|
|
522
|
+
* if (isUninitialized(prevValue)) {
|
|
523
|
+
* print('First time!')
|
|
524
|
+
* }
|
|
525
|
+
* return count.get() * 2
|
|
526
|
+
* })
|
|
527
|
+
* ```
|
|
528
|
+
*
|
|
529
|
+
* @param value - The value to check.
|
|
530
|
+
* @public
|
|
531
|
+
*/
|
|
532
|
+
declare function isUninitialized(value: any): value is UNINITIALIZED;
|
|
533
|
+
/**
|
|
534
|
+
* A singleton class used to wrap computed signal values along with their diffs.
|
|
535
|
+
* This class is used internally by the {@link withDiff} function to provide both
|
|
536
|
+
* the computed value and its diff to the signal system.
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* ```ts
|
|
540
|
+
* const count = atom('count', 0)
|
|
541
|
+
* const double = computed('double', (prevValue) => {
|
|
542
|
+
* const nextValue = count.get() * 2
|
|
543
|
+
* if (isUninitialized(prevValue)) {
|
|
544
|
+
* return nextValue
|
|
545
|
+
* }
|
|
546
|
+
* return withDiff(nextValue, nextValue - prevValue)
|
|
547
|
+
* })
|
|
548
|
+
* ```
|
|
549
|
+
*
|
|
550
|
+
* @public
|
|
551
|
+
*/
|
|
552
|
+
declare const WithDiff: {
|
|
553
|
+
new <Value, Diff>(value: Value, diff: Diff): {
|
|
554
|
+
value: Value;
|
|
555
|
+
diff: Diff;
|
|
556
|
+
};
|
|
557
|
+
};
|
|
558
|
+
/**
|
|
559
|
+
* Interface representing a value wrapped with its corresponding diff.
|
|
560
|
+
* Used in incremental computation to provide both the new value and the diff from the previous value.
|
|
561
|
+
*
|
|
562
|
+
* @public
|
|
563
|
+
*/
|
|
564
|
+
interface WithDiff<Value, Diff> {
|
|
565
|
+
/**
|
|
566
|
+
* The computed value.
|
|
567
|
+
*/
|
|
568
|
+
value: Value;
|
|
569
|
+
/**
|
|
570
|
+
* The diff between the previous and current value.
|
|
571
|
+
*/
|
|
572
|
+
diff: Diff;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* When writing incrementally-computed signals it is convenient (and usually more performant) to incrementally compute the diff too.
|
|
576
|
+
*
|
|
577
|
+
* You can use this function to wrap the return value of a computed signal function to indicate that the diff should be used instead of calculating a new one with {@link AtomOptions.computeDiff}.
|
|
578
|
+
*
|
|
579
|
+
* @example
|
|
580
|
+
* ```ts
|
|
581
|
+
* const count = atom('count', 0)
|
|
582
|
+
* const double = computed('double', (prevValue) => {
|
|
583
|
+
* const nextValue = count.get() * 2
|
|
584
|
+
* if (isUninitialized(prevValue)) {
|
|
585
|
+
* return nextValue
|
|
586
|
+
* }
|
|
587
|
+
* return withDiff(nextValue, nextValue - prevValue)
|
|
588
|
+
* }, { historyLength: 10 })
|
|
589
|
+
* ```
|
|
590
|
+
*
|
|
591
|
+
*
|
|
592
|
+
* @param value - The value.
|
|
593
|
+
* @param diff - The diff.
|
|
594
|
+
* @public
|
|
595
|
+
*/
|
|
596
|
+
declare function withDiff<Value, Diff>(value: Value, diff: Diff): WithDiff<Value, Diff>;
|
|
597
|
+
/**
|
|
598
|
+
* Options for configuring computed signals. Used when calling `computed` or using the `@computed` decorator.
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```ts
|
|
602
|
+
* const greeting = computed('greeting', () => `Hello ${name.get()}!`, {
|
|
603
|
+
* historyLength: 10,
|
|
604
|
+
* isEqual: (a, b) => a === b,
|
|
605
|
+
* computeDiff: (oldVal, newVal) => ({ type: 'change', from: oldVal, to: newVal })
|
|
606
|
+
* })
|
|
607
|
+
* ```
|
|
608
|
+
*
|
|
609
|
+
* @public
|
|
610
|
+
*/
|
|
611
|
+
interface ComputedOptions<Value, Diff> {
|
|
612
|
+
/**
|
|
613
|
+
* The maximum number of diffs to keep in the history buffer.
|
|
614
|
+
*
|
|
615
|
+
* If you don't need to compute diffs, or if you will supply diffs manually via {@link Atom.set}, you can leave this as `undefined` and no history buffer will be created.
|
|
616
|
+
*
|
|
617
|
+
* If you expect the value to be part of an active effect subscription all the time, and to not change multiple times inside of a single transaction, you can set this to a relatively low number (e.g. 10).
|
|
618
|
+
*
|
|
619
|
+
* Otherwise, set this to a higher number based on your usage pattern and memory constraints.
|
|
620
|
+
*
|
|
621
|
+
*/
|
|
622
|
+
historyLength?: number;
|
|
623
|
+
/**
|
|
624
|
+
* A method used to compute a diff between the computed's old and new values. If provided, it will not be used unless you also specify {@link ComputedOptions.historyLength}.
|
|
625
|
+
*/
|
|
626
|
+
computeDiff?: ComputeDiff<Value, Diff>;
|
|
627
|
+
/**
|
|
628
|
+
* If provided, this will be used to compare the old and new values of the computed to determine if the value has changed.
|
|
629
|
+
* By default, values are compared using first using strict equality (`===`), then `Object.is`, and finally any `.equals` method present in the object's prototype chain.
|
|
630
|
+
* @param a - The old value
|
|
631
|
+
* @param b - The new value
|
|
632
|
+
* @returns True if the values are equal, false otherwise.
|
|
633
|
+
*/
|
|
634
|
+
isEqual?(a: any, b: any): boolean;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* A computed signal created via the `computed` function or `@computed` decorator.
|
|
638
|
+
* Computed signals derive their values from other signals and automatically update when their dependencies change.
|
|
639
|
+
* They use lazy evaluation, only recalculating when accessed and dependencies have changed.
|
|
640
|
+
*
|
|
641
|
+
* @example
|
|
642
|
+
* ```ts
|
|
643
|
+
* const firstName = atom('firstName', 'John')
|
|
644
|
+
* const lastName = atom('lastName', 'Doe')
|
|
645
|
+
* const fullName = computed('fullName', () => `${firstName.get()} ${lastName.get()}`)
|
|
646
|
+
*
|
|
647
|
+
* console.log(fullName.get()) // "John Doe"
|
|
648
|
+
* firstName.set('Jane')
|
|
649
|
+
* console.log(fullName.get()) // "Jane Doe"
|
|
650
|
+
* ```
|
|
651
|
+
*
|
|
652
|
+
* @public
|
|
653
|
+
*/
|
|
654
|
+
interface Computed<Value, Diff = unknown> extends Signal<Value, Diff> {
|
|
655
|
+
/**
|
|
656
|
+
* Whether this computed signal is involved in an actively-running effect graph.
|
|
657
|
+
* Returns true if there are any reactions or other computed signals depending on this one.
|
|
658
|
+
* @public
|
|
659
|
+
*/
|
|
660
|
+
readonly isActivelyListening: boolean;
|
|
661
|
+
/** @internal */
|
|
662
|
+
readonly parentSet: ArraySet<Signal<any, any>>;
|
|
663
|
+
/** @internal */
|
|
664
|
+
readonly parents: Signal<any, any>[];
|
|
665
|
+
/** @internal */
|
|
666
|
+
readonly parentEpochs: number[];
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Retrieves the underlying computed instance for a given property created with the `computed`
|
|
670
|
+
* decorator.
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* ```ts
|
|
674
|
+
* class Counter {
|
|
675
|
+
* max = 100
|
|
676
|
+
* count = atom(0)
|
|
677
|
+
*
|
|
678
|
+
* @computed getRemaining() {
|
|
679
|
+
* return this.max - this.count.get()
|
|
680
|
+
* }
|
|
681
|
+
* }
|
|
682
|
+
*
|
|
683
|
+
* const c = new Counter()
|
|
684
|
+
* const remaining = getComputedInstance(c, 'getRemaining')
|
|
685
|
+
* remaining.get() === 100 // true
|
|
686
|
+
* c.count.set(13)
|
|
687
|
+
* remaining.get() === 87 // true
|
|
688
|
+
* ```
|
|
689
|
+
*
|
|
690
|
+
* @param obj - The object
|
|
691
|
+
* @param propertyName - The property name
|
|
692
|
+
* @public
|
|
693
|
+
*/
|
|
694
|
+
declare function getComputedInstance<Obj extends object, Prop extends keyof Obj>(obj: Obj, propertyName: Prop): Computed<Obj[Prop]>;
|
|
695
|
+
/**
|
|
696
|
+
* Creates a computed signal that derives its value from other signals.
|
|
697
|
+
* Computed signals automatically update when their dependencies change and use lazy evaluation
|
|
698
|
+
* for optimal performance.
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* const name = atom('name', 'John')
|
|
703
|
+
* const greeting = computed('greeting', () => `Hello ${name.get()}!`)
|
|
704
|
+
* console.log(greeting.get()) // 'Hello John!'
|
|
705
|
+
* ```
|
|
706
|
+
*
|
|
707
|
+
* `computed` may also be used as a decorator for creating computed getter methods.
|
|
708
|
+
*
|
|
709
|
+
* @example
|
|
710
|
+
* ```ts
|
|
711
|
+
* class Counter {
|
|
712
|
+
* max = 100
|
|
713
|
+
* count = atom<number>(0)
|
|
714
|
+
*
|
|
715
|
+
* @computed getRemaining() {
|
|
716
|
+
* return this.max - this.count.get()
|
|
717
|
+
* }
|
|
718
|
+
* }
|
|
719
|
+
* ```
|
|
720
|
+
*
|
|
721
|
+
* You may optionally pass in a {@link ComputedOptions} when used as a decorator:
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* ```ts
|
|
725
|
+
* class Counter {
|
|
726
|
+
* max = 100
|
|
727
|
+
* count = atom<number>(0)
|
|
728
|
+
*
|
|
729
|
+
* @computed({isEqual: (a, b) => a === b})
|
|
730
|
+
* getRemaining() {
|
|
731
|
+
* return this.max - this.count.get()
|
|
732
|
+
* }
|
|
733
|
+
* }
|
|
734
|
+
* ```
|
|
735
|
+
*
|
|
736
|
+
* @param name - The name of the signal for debugging purposes
|
|
737
|
+
* @param compute - The function that computes the value of the signal. Receives the previous value and last computed epoch
|
|
738
|
+
* @param options - Optional configuration for the computed signal
|
|
739
|
+
* @returns A new computed signal
|
|
740
|
+
* @public
|
|
741
|
+
*/
|
|
742
|
+
declare function computed<Value, Diff = unknown>(name: string, compute: (previousValue: Value | typeof UNINITIALIZED, lastComputedEpoch: number) => Value | WithDiff<Value, Diff>, options?: ComputedOptions<Value, Diff>): Computed<Value, Diff>;
|
|
743
|
+
/**
|
|
744
|
+
* TC39 decorator for creating computed methods in classes.
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```ts
|
|
748
|
+
* class MyClass {
|
|
749
|
+
* value = atom('value', 10)
|
|
750
|
+
*
|
|
751
|
+
* @computed
|
|
752
|
+
* doubled() {
|
|
753
|
+
* return this.value.get() * 2
|
|
754
|
+
* }
|
|
755
|
+
* }
|
|
756
|
+
* ```
|
|
757
|
+
*
|
|
758
|
+
* @param compute - The method to be decorated
|
|
759
|
+
* @param context - The decorator context provided by TypeScript
|
|
760
|
+
* @returns The decorated method
|
|
761
|
+
* @public
|
|
762
|
+
*/
|
|
763
|
+
declare function computed<This extends object, Value>(compute: () => Value, context: ClassMethodDecoratorContext<This, () => Value>): () => Value;
|
|
764
|
+
/**
|
|
765
|
+
* Legacy TypeScript decorator for creating computed methods in classes.
|
|
766
|
+
*
|
|
767
|
+
* @example
|
|
768
|
+
* ```ts
|
|
769
|
+
* class MyClass {
|
|
770
|
+
* value = atom('value', 10)
|
|
771
|
+
*
|
|
772
|
+
* @computed
|
|
773
|
+
* doubled() {
|
|
774
|
+
* return this.value.get() * 2
|
|
775
|
+
* }
|
|
776
|
+
* }
|
|
777
|
+
* ```
|
|
778
|
+
*
|
|
779
|
+
* @param target - The class prototype
|
|
780
|
+
* @param key - The property key
|
|
781
|
+
* @param descriptor - The property descriptor
|
|
782
|
+
* @returns The modified property descriptor
|
|
783
|
+
* @public
|
|
784
|
+
*/
|
|
785
|
+
declare function computed(target: any, key: string, descriptor: PropertyDescriptor): PropertyDescriptor;
|
|
786
|
+
/**
|
|
787
|
+
* Decorator factory for creating computed methods with options.
|
|
788
|
+
*
|
|
789
|
+
* @example
|
|
790
|
+
* ```ts
|
|
791
|
+
* class MyClass {
|
|
792
|
+
* items = atom('items', [1, 2, 3])
|
|
793
|
+
*
|
|
794
|
+
* @computed({ historyLength: 10 })
|
|
795
|
+
* sum() {
|
|
796
|
+
* return this.items.get().reduce((a, b) => a + b, 0)
|
|
797
|
+
* }
|
|
798
|
+
* }
|
|
799
|
+
* ```
|
|
800
|
+
*
|
|
801
|
+
* @param options - Configuration options for the computed signal
|
|
802
|
+
* @returns A decorator function that can be applied to methods
|
|
803
|
+
* @public
|
|
804
|
+
*/
|
|
805
|
+
declare function computed<Value, Diff = unknown>(options?: ComputedOptions<Value, Diff>): ((target: any, key: string, descriptor: PropertyDescriptor) => PropertyDescriptor) & (<This>(compute: () => Value, context: ClassMethodDecoratorContext<This, () => Value>) => () => Value);
|
|
806
|
+
|
|
807
|
+
/** @public */
|
|
808
|
+
interface EffectSchedulerOptions {
|
|
809
|
+
/**
|
|
810
|
+
* scheduleEffect is a function that will be called when the effect is scheduled.
|
|
811
|
+
*
|
|
812
|
+
* It can be used to defer running effects until a later time, for example to batch them together with requestAnimationFrame.
|
|
813
|
+
*
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* ```ts
|
|
817
|
+
* let isRafScheduled = false
|
|
818
|
+
* const scheduledEffects: Array<() => void> = []
|
|
819
|
+
* const scheduleEffect = (runEffect: () => void) => {
|
|
820
|
+
* scheduledEffects.push(runEffect)
|
|
821
|
+
* if (!isRafScheduled) {
|
|
822
|
+
* isRafScheduled = true
|
|
823
|
+
* requestAnimationFrame(() => {
|
|
824
|
+
* isRafScheduled = false
|
|
825
|
+
* scheduledEffects.forEach((runEffect) => runEffect())
|
|
826
|
+
* scheduledEffects.length = 0
|
|
827
|
+
* })
|
|
828
|
+
* }
|
|
829
|
+
* }
|
|
830
|
+
* const stop = react('set page title', () => {
|
|
831
|
+
* document.title = doc.title,
|
|
832
|
+
* }, scheduleEffect)
|
|
833
|
+
* ```
|
|
834
|
+
*
|
|
835
|
+
* @param execute - A function that will execute the effect.
|
|
836
|
+
* @returns void
|
|
837
|
+
*/
|
|
838
|
+
scheduleEffect?: (execute: () => void) => void;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* An EffectScheduler is responsible for executing side effects in response to changes in state.
|
|
842
|
+
*
|
|
843
|
+
* You probably don't need to use this directly unless you're integrating this library with a framework of some kind.
|
|
844
|
+
*
|
|
845
|
+
* Instead, use the {@link react} and {@link reactor} functions.
|
|
846
|
+
*
|
|
847
|
+
* @example
|
|
848
|
+
* ```ts
|
|
849
|
+
* const render = new EffectScheduler('render', drawToCanvas)
|
|
850
|
+
*
|
|
851
|
+
* render.attach()
|
|
852
|
+
* render.execute()
|
|
853
|
+
* ```
|
|
854
|
+
*
|
|
855
|
+
* @public
|
|
856
|
+
*/
|
|
857
|
+
declare const EffectScheduler: new <Result>(name: string, runEffect: (lastReactedEpoch: number) => Result, options?: EffectSchedulerOptions) => EffectScheduler<Result>;
|
|
858
|
+
/** @public */
|
|
859
|
+
interface EffectScheduler<Result> {
|
|
860
|
+
/**
|
|
861
|
+
* Whether this scheduler is attached and actively listening to its parents.
|
|
862
|
+
* @public
|
|
863
|
+
*/
|
|
864
|
+
readonly isActivelyListening: boolean;
|
|
865
|
+
/** @internal */
|
|
866
|
+
readonly lastTraversedEpoch: number;
|
|
867
|
+
/** @public */
|
|
868
|
+
readonly name: string;
|
|
869
|
+
/** @internal */
|
|
870
|
+
__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null;
|
|
871
|
+
/**
|
|
872
|
+
* The number of times this effect has been scheduled.
|
|
873
|
+
* @public
|
|
874
|
+
*/
|
|
875
|
+
readonly scheduleCount: number;
|
|
876
|
+
/** @internal */
|
|
877
|
+
readonly parentSet: ArraySet<Signal<any, any>>;
|
|
878
|
+
/** @internal */
|
|
879
|
+
readonly parentEpochs: number[];
|
|
880
|
+
/** @internal */
|
|
881
|
+
readonly parents: Signal<any, any>[];
|
|
882
|
+
/** @internal */
|
|
883
|
+
maybeScheduleEffect(): void;
|
|
884
|
+
/** @internal */
|
|
885
|
+
scheduleEffect(): void;
|
|
886
|
+
/** @internal */
|
|
887
|
+
maybeExecute(): void;
|
|
888
|
+
/**
|
|
889
|
+
* Makes this scheduler become 'actively listening' to its parents.
|
|
890
|
+
* If it has been executed before it will immediately become eligible to receive 'maybeScheduleEffect' calls.
|
|
891
|
+
* If it has not executed before it will need to be manually executed once to become eligible for scheduling, i.e. by calling `EffectScheduler.execute`.
|
|
892
|
+
* @public
|
|
893
|
+
*/
|
|
894
|
+
attach(): void;
|
|
895
|
+
/**
|
|
896
|
+
* Makes this scheduler stop 'actively listening' to its parents.
|
|
897
|
+
* It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.
|
|
898
|
+
* @public
|
|
899
|
+
*/
|
|
900
|
+
detach(): void;
|
|
901
|
+
/**
|
|
902
|
+
* Executes the effect immediately and returns the result.
|
|
903
|
+
* @returns The result of the effect.
|
|
904
|
+
* @public
|
|
905
|
+
*/
|
|
906
|
+
execute(): Result;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Starts a new effect scheduler, scheduling the effect immediately.
|
|
910
|
+
*
|
|
911
|
+
* Returns a function that can be called to stop the scheduler.
|
|
912
|
+
*
|
|
913
|
+
* @example
|
|
914
|
+
* ```ts
|
|
915
|
+
* const color = atom('color', 'red')
|
|
916
|
+
* const stop = react('set style', () => {
|
|
917
|
+
* divElem.style.color = color.get()
|
|
918
|
+
* })
|
|
919
|
+
* color.set('blue')
|
|
920
|
+
* // divElem.style.color === 'blue'
|
|
921
|
+
* stop()
|
|
922
|
+
* color.set('green')
|
|
923
|
+
* // divElem.style.color === 'blue'
|
|
924
|
+
* ```
|
|
925
|
+
*
|
|
926
|
+
*
|
|
927
|
+
* Also useful in React applications for running effects outside of the render cycle.
|
|
928
|
+
*
|
|
929
|
+
* @example
|
|
930
|
+
* ```ts
|
|
931
|
+
* useEffect(() => react('set style', () => {
|
|
932
|
+
* divRef.current.style.color = color.get()
|
|
933
|
+
* }), [])
|
|
934
|
+
* ```
|
|
935
|
+
*
|
|
936
|
+
* @public
|
|
937
|
+
*/
|
|
938
|
+
declare function react(name: string, fn: (lastReactedEpoch: number) => any, options?: EffectSchedulerOptions): () => void;
|
|
939
|
+
/**
|
|
940
|
+
* The reactor is a user-friendly interface for starting and stopping an `EffectScheduler`.
|
|
941
|
+
*
|
|
942
|
+
* Calling `.start()` will attach the scheduler and execute the effect immediately the first time it is called.
|
|
943
|
+
*
|
|
944
|
+
* If the reactor is stopped, calling `.start()` will re-attach the scheduler but will only execute the effect if any of its parents have changed since it was stopped.
|
|
945
|
+
*
|
|
946
|
+
* You can create a reactor with {@link reactor}.
|
|
947
|
+
* @public
|
|
948
|
+
*/
|
|
949
|
+
interface Reactor<T = unknown> {
|
|
950
|
+
/**
|
|
951
|
+
* The underlying effect scheduler.
|
|
952
|
+
* @public
|
|
953
|
+
*/
|
|
954
|
+
scheduler: EffectScheduler<T>;
|
|
955
|
+
/**
|
|
956
|
+
* Start the scheduler. The first time this is called the effect will be scheduled immediately.
|
|
957
|
+
*
|
|
958
|
+
* If the reactor is stopped, calling this will start the scheduler again but will only execute the effect if any of its parents have changed since it was stopped.
|
|
959
|
+
*
|
|
960
|
+
* If you need to force re-execution of the effect, pass `{ force: true }`.
|
|
961
|
+
* @public
|
|
962
|
+
*/
|
|
963
|
+
start(options?: {
|
|
964
|
+
force?: boolean;
|
|
965
|
+
}): void;
|
|
966
|
+
/**
|
|
967
|
+
* Stop the scheduler.
|
|
968
|
+
* @public
|
|
969
|
+
*/
|
|
970
|
+
stop(): void;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Creates a {@link Reactor}, which is a thin wrapper around an `EffectScheduler`.
|
|
974
|
+
*
|
|
975
|
+
* @public
|
|
976
|
+
*/
|
|
977
|
+
declare function reactor<Result>(name: string, fn: (lastReactedEpoch: number) => Result, options?: EffectSchedulerOptions): Reactor<Result>;
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* @public
|
|
981
|
+
*/
|
|
982
|
+
declare const EMPTY_ARRAY: [];
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Type guard function that determines whether a value is a signal (either an Atom or Computed).
|
|
986
|
+
*
|
|
987
|
+
* This utility function is helpful when working with mixed data types and you need to
|
|
988
|
+
* differentiate between regular values and reactive signal containers. It returns `true`
|
|
989
|
+
* if the provided value is either an atomic signal created with `atom()` or a computed
|
|
990
|
+
* signal created with `computed()`.
|
|
991
|
+
*
|
|
992
|
+
* @param value - The value to check, can be of any type
|
|
993
|
+
* @returns `true` if the value is a Signal (Atom or Computed), `false` otherwise
|
|
994
|
+
*
|
|
995
|
+
* @example
|
|
996
|
+
* ```ts
|
|
997
|
+
* import { atom, computed, isSignal } from '@ibodr/state'
|
|
998
|
+
*
|
|
999
|
+
* const count = atom('count', 5)
|
|
1000
|
+
* const doubled = computed('doubled', () => count.get() * 2)
|
|
1001
|
+
* const regularValue = 'hello'
|
|
1002
|
+
*
|
|
1003
|
+
* console.log(isSignal(count)) // true
|
|
1004
|
+
* console.log(isSignal(doubled)) // true
|
|
1005
|
+
* console.log(isSignal(regularValue)) // false
|
|
1006
|
+
* console.log(isSignal(null)) // false
|
|
1007
|
+
* ```
|
|
1008
|
+
*
|
|
1009
|
+
* @public
|
|
1010
|
+
*/
|
|
1011
|
+
declare function isSignal(value: any): value is Signal<any>;
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Creates a new {@link Atom} that persists its value to localStorage.
|
|
1015
|
+
*
|
|
1016
|
+
* The atom is automatically synced with localStorage - changes to the atom are saved to localStorage,
|
|
1017
|
+
* and the initial value is read from localStorage if it exists. Returns both the atom and a cleanup
|
|
1018
|
+
* function that should be called to stop syncing when the atom is no longer needed. If you need to delete
|
|
1019
|
+
* the atom, you should do it manually after all cleanup functions have been called.
|
|
1020
|
+
*
|
|
1021
|
+
* @example
|
|
1022
|
+
* ```ts
|
|
1023
|
+
* const [theme, cleanup] = localStorageAtom('theme', 'light')
|
|
1024
|
+
*
|
|
1025
|
+
* theme.get() // 'light' or value from localStorage if it exists
|
|
1026
|
+
*
|
|
1027
|
+
* theme.set('dark') // updates atom and saves to localStorage
|
|
1028
|
+
*
|
|
1029
|
+
* // When done:
|
|
1030
|
+
* cleanup() // stops syncing to localStorage
|
|
1031
|
+
* ```
|
|
1032
|
+
*
|
|
1033
|
+
* @param name - The localStorage key and atom name. This is used for both localStorage persistence
|
|
1034
|
+
* and debugging/profiling purposes.
|
|
1035
|
+
* @param initialValue - The initial value of the atom, used if no value exists in localStorage.
|
|
1036
|
+
* @param options - Optional atom configuration. See {@link AtomOptions}.
|
|
1037
|
+
* @returns A tuple containing the atom and a cleanup function to stop localStorage syncing.
|
|
1038
|
+
* @public
|
|
1039
|
+
*/
|
|
1040
|
+
declare function localStorageAtom<Value, Diff = unknown>(name: string, initialValue: Value, options?: AtomOptions<Value, Diff>): [Atom<Value, Diff>, () => void];
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Batches state updates, deferring side effects until after the transaction completes.
|
|
1044
|
+
* Unlike {@link transact}, this function always creates a new transaction, allowing for nested transactions.
|
|
1045
|
+
*
|
|
1046
|
+
* @example
|
|
1047
|
+
* ```ts
|
|
1048
|
+
* const firstName = atom('firstName', 'John')
|
|
1049
|
+
* const lastName = atom('lastName', 'Doe')
|
|
1050
|
+
*
|
|
1051
|
+
* react('greet', () => {
|
|
1052
|
+
* console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
|
1053
|
+
* })
|
|
1054
|
+
*
|
|
1055
|
+
* // Logs "Hello, John Doe!"
|
|
1056
|
+
*
|
|
1057
|
+
* transaction(() => {
|
|
1058
|
+
* firstName.set('Jane')
|
|
1059
|
+
* lastName.set('Smith')
|
|
1060
|
+
* })
|
|
1061
|
+
*
|
|
1062
|
+
* // Logs "Hello, Jane Smith!"
|
|
1063
|
+
* ```
|
|
1064
|
+
*
|
|
1065
|
+
* If the function throws, the transaction is aborted and any signals that were updated during the transaction revert to their state before the transaction began.
|
|
1066
|
+
*
|
|
1067
|
+
* @example
|
|
1068
|
+
* ```ts
|
|
1069
|
+
* const firstName = atom('firstName', 'John')
|
|
1070
|
+
* const lastName = atom('lastName', 'Doe')
|
|
1071
|
+
*
|
|
1072
|
+
* react('greet', () => {
|
|
1073
|
+
* console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
|
1074
|
+
* })
|
|
1075
|
+
*
|
|
1076
|
+
* // Logs "Hello, John Doe!"
|
|
1077
|
+
*
|
|
1078
|
+
* transaction(() => {
|
|
1079
|
+
* firstName.set('Jane')
|
|
1080
|
+
* throw new Error('oops')
|
|
1081
|
+
* })
|
|
1082
|
+
*
|
|
1083
|
+
* // Does not log
|
|
1084
|
+
* // firstName.get() === 'John'
|
|
1085
|
+
* ```
|
|
1086
|
+
*
|
|
1087
|
+
* A `rollback` callback is passed into the function.
|
|
1088
|
+
* Calling this will prevent the transaction from committing and will revert any signals that were updated during the transaction to their state before the transaction began.
|
|
1089
|
+
*
|
|
1090
|
+
* @example
|
|
1091
|
+
* ```ts
|
|
1092
|
+
* const firstName = atom('firstName', 'John')
|
|
1093
|
+
* const lastName = atom('lastName', 'Doe')
|
|
1094
|
+
*
|
|
1095
|
+
* react('greet', () => {
|
|
1096
|
+
* console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
|
1097
|
+
* })
|
|
1098
|
+
*
|
|
1099
|
+
* // Logs "Hello, John Doe!"
|
|
1100
|
+
*
|
|
1101
|
+
* transaction((rollback) => {
|
|
1102
|
+
* firstName.set('Jane')
|
|
1103
|
+
* lastName.set('Smith')
|
|
1104
|
+
* rollback()
|
|
1105
|
+
* })
|
|
1106
|
+
*
|
|
1107
|
+
* // Does not log
|
|
1108
|
+
* // firstName.get() === 'John'
|
|
1109
|
+
* // lastName.get() === 'Doe'
|
|
1110
|
+
* ```
|
|
1111
|
+
*
|
|
1112
|
+
* @param fn - The function to run in a transaction, called with a function to roll back the change.
|
|
1113
|
+
* @returns The return value of the function
|
|
1114
|
+
* @public
|
|
1115
|
+
*/
|
|
1116
|
+
declare function transaction<T>(fn: (rollback: () => void) => T): T;
|
|
1117
|
+
/**
|
|
1118
|
+
* Like {@link transaction}, but does not create a new transaction if there is already one in progress.
|
|
1119
|
+
* This is the preferred way to batch state updates when you don't need the rollback functionality.
|
|
1120
|
+
*
|
|
1121
|
+
* @example
|
|
1122
|
+
* ```ts
|
|
1123
|
+
* const count = atom('count', 0)
|
|
1124
|
+
* const doubled = atom('doubled', 0)
|
|
1125
|
+
*
|
|
1126
|
+
* react('update doubled', () => {
|
|
1127
|
+
* console.log(`Count: ${count.get()}, Doubled: ${doubled.get()}`)
|
|
1128
|
+
* })
|
|
1129
|
+
*
|
|
1130
|
+
* // This batches both updates into a single reaction
|
|
1131
|
+
* transact(() => {
|
|
1132
|
+
* count.set(5)
|
|
1133
|
+
* doubled.set(count.get() * 2)
|
|
1134
|
+
* })
|
|
1135
|
+
* // Logs: "Count: 5, Doubled: 10"
|
|
1136
|
+
* ```
|
|
1137
|
+
*
|
|
1138
|
+
* @param fn - The function to run in a transaction
|
|
1139
|
+
* @returns The return value of the function
|
|
1140
|
+
* @public
|
|
1141
|
+
*/
|
|
1142
|
+
declare function transact<T>(fn: () => T): T;
|
|
1143
|
+
/**
|
|
1144
|
+
* Defers the execution of asynchronous effects until they can be properly handled.
|
|
1145
|
+
* This function creates an asynchronous transaction context that batches state updates
|
|
1146
|
+
* across async operations while preventing conflicts with synchronous transactions.
|
|
1147
|
+
*
|
|
1148
|
+
* @example
|
|
1149
|
+
* ```ts
|
|
1150
|
+
* const data = atom('data', null)
|
|
1151
|
+
* const loading = atom('loading', false)
|
|
1152
|
+
*
|
|
1153
|
+
* await deferAsyncEffects(async () => {
|
|
1154
|
+
* loading.set(true)
|
|
1155
|
+
* const result = await fetch('/api/data')
|
|
1156
|
+
* const json = await result.json()
|
|
1157
|
+
* data.set(json)
|
|
1158
|
+
* loading.set(false)
|
|
1159
|
+
* })
|
|
1160
|
+
* ```
|
|
1161
|
+
*
|
|
1162
|
+
* @param fn - The async function to execute within the deferred context
|
|
1163
|
+
* @returns A promise that resolves to the return value of the function
|
|
1164
|
+
* @throws Will throw if called during a synchronous transaction
|
|
1165
|
+
* @internal
|
|
1166
|
+
*/
|
|
1167
|
+
declare function deferAsyncEffects<T>(fn: () => Promise<T>): Promise<T | undefined>;
|
|
1168
|
+
|
|
1169
|
+
export { ArraySet, type Atom, type AtomOptions, type Child, type ComputeDiff, type Computed, type ComputedOptions, EMPTY_ARRAY, EffectScheduler, type EffectSchedulerOptions, RESET_VALUE, type Reactor, type Signal, UNINITIALIZED, WithDiff, atom, computed, deferAsyncEffects, getComputedInstance, isAtom, isSignal, isUninitialized, localStorageAtom, react, reactor, transact, transaction, unsafe__withoutCapture, whyAmIRunning, withDiff };
|