@tldraw/state 4.0.2 → 4.1.0-canary.0259516ffb8c
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-cjs/index.d.ts +358 -43
- package/dist-cjs/index.js +3 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/ArraySet.js +90 -6
- package/dist-cjs/lib/ArraySet.js.map +2 -2
- package/dist-cjs/lib/Atom.js +70 -0
- package/dist-cjs/lib/Atom.js.map +2 -2
- package/dist-cjs/lib/Computed.js +1 -1
- package/dist-cjs/lib/Computed.js.map +2 -2
- package/dist-cjs/lib/HistoryBuffer.js +57 -9
- package/dist-cjs/lib/HistoryBuffer.js.map +2 -2
- package/dist-cjs/lib/capture.js.map +2 -2
- package/dist-cjs/lib/constants.js.map +2 -2
- package/dist-cjs/lib/helpers.js.map +2 -2
- package/dist-cjs/lib/isSignal.js.map +2 -2
- package/dist-cjs/lib/localStorageAtom.js +60 -0
- package/dist-cjs/lib/localStorageAtom.js.map +7 -0
- package/dist-cjs/lib/transactions.js.map +2 -2
- package/dist-cjs/lib/types.js.map +2 -2
- package/dist-cjs/lib/warnings.js.map +2 -2
- package/dist-esm/index.d.mts +358 -43
- package/dist-esm/index.mjs +3 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/ArraySet.mjs +90 -6
- package/dist-esm/lib/ArraySet.mjs.map +2 -2
- package/dist-esm/lib/Atom.mjs +70 -0
- package/dist-esm/lib/Atom.mjs.map +2 -2
- package/dist-esm/lib/Computed.mjs +1 -1
- package/dist-esm/lib/Computed.mjs.map +2 -2
- package/dist-esm/lib/HistoryBuffer.mjs +57 -9
- package/dist-esm/lib/HistoryBuffer.mjs.map +2 -2
- package/dist-esm/lib/capture.mjs.map +2 -2
- package/dist-esm/lib/constants.mjs.map +2 -2
- package/dist-esm/lib/helpers.mjs.map +2 -2
- package/dist-esm/lib/isSignal.mjs.map +2 -2
- package/dist-esm/lib/localStorageAtom.mjs +40 -0
- package/dist-esm/lib/localStorageAtom.mjs.map +7 -0
- package/dist-esm/lib/transactions.mjs.map +2 -2
- package/dist-esm/lib/types.mjs.map +2 -2
- package/dist-esm/lib/warnings.mjs.map +2 -2
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/lib/ArraySet.ts +101 -8
- package/src/lib/Atom.ts +92 -0
- package/src/lib/Computed.ts +169 -19
- package/src/lib/HistoryBuffer.ts +76 -10
- package/src/lib/__tests__/helpers.test.ts +168 -0
- package/src/lib/__tests__/localStorageAtom.test.ts +183 -0
- package/src/lib/capture.ts +56 -1
- package/src/lib/constants.ts +25 -1
- package/src/lib/helpers.ts +137 -13
- package/src/lib/isSignal.ts +24 -0
- package/src/lib/localStorageAtom.ts +87 -0
- package/src/lib/transactions.ts +79 -10
- package/src/lib/types.ts +173 -19
- package/src/lib/warnings.ts +36 -0
package/src/lib/Atom.ts
CHANGED
|
@@ -66,6 +66,8 @@ export interface Atom<Value, Diff = unknown> extends Signal<Value, Diff> {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
+
* Internal implementation of the Atom interface. This class should not be used directly - use the {@link atom} function instead.
|
|
70
|
+
*
|
|
69
71
|
* @internal
|
|
70
72
|
*/
|
|
71
73
|
class __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {
|
|
@@ -85,25 +87,76 @@ class __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {
|
|
|
85
87
|
this.computeDiff = options.computeDiff
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Custom equality function for comparing values, or null to use default equality.
|
|
92
|
+
* @internal
|
|
93
|
+
*/
|
|
88
94
|
readonly isEqual: null | ((a: any, b: any) => boolean)
|
|
89
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Optional function to compute diffs between old and new values.
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
90
100
|
computeDiff?: ComputeDiff<Value, Diff>
|
|
91
101
|
|
|
102
|
+
/**
|
|
103
|
+
* The global epoch when this atom was last changed.
|
|
104
|
+
* @internal
|
|
105
|
+
*/
|
|
92
106
|
lastChangedEpoch = getGlobalEpoch()
|
|
93
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Set of child signals that depend on this atom.
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
94
112
|
children = new ArraySet<Child>()
|
|
95
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Optional history buffer for tracking changes over time.
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
96
118
|
historyBuffer?: HistoryBuffer<Diff>
|
|
97
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Gets the current value without capturing it as a dependency in the current reactive context.
|
|
122
|
+
* This is unsafe because it breaks the reactivity chain - use with caution.
|
|
123
|
+
*
|
|
124
|
+
* @param _ignoreErrors - Unused parameter for API compatibility
|
|
125
|
+
* @returns The current value
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
98
128
|
__unsafe__getWithoutCapture(_ignoreErrors?: boolean): Value {
|
|
99
129
|
return this.current
|
|
100
130
|
}
|
|
101
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Gets the current value of this atom. When called within a computed signal or reaction,
|
|
134
|
+
* this atom will be automatically captured as a dependency.
|
|
135
|
+
*
|
|
136
|
+
* @returns The current value
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* const count = atom('count', 5)
|
|
140
|
+
* console.log(count.get()) // 5
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
102
143
|
get() {
|
|
103
144
|
maybeCaptureParent(this)
|
|
104
145
|
return this.current
|
|
105
146
|
}
|
|
106
147
|
|
|
148
|
+
/**
|
|
149
|
+
* 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.
|
|
150
|
+
*
|
|
151
|
+
* @param value - The new value to set
|
|
152
|
+
* @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}
|
|
153
|
+
* @returns The new value
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* const count = atom('count', 0)
|
|
157
|
+
* count.set(5) // count.get() is now 5
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
107
160
|
set(value: Value, diff?: Diff): Value {
|
|
108
161
|
// If the value has not changed, do nothing.
|
|
109
162
|
if (this.isEqual?.(this.current, value) ?? equals(this.current, value)) {
|
|
@@ -136,10 +189,29 @@ class __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {
|
|
|
136
189
|
return value
|
|
137
190
|
}
|
|
138
191
|
|
|
192
|
+
/**
|
|
193
|
+
* 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.
|
|
194
|
+
*
|
|
195
|
+
* @param updater - A function that takes the current value and returns the new value
|
|
196
|
+
* @returns The new value
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* const count = atom('count', 5)
|
|
200
|
+
* count.update(n => n + 1) // count.get() is now 6
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
139
203
|
update(updater: (value: Value) => Value): Value {
|
|
140
204
|
return this.set(updater(this.current))
|
|
141
205
|
}
|
|
142
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Gets all the diffs that have occurred since the given epoch. When called within a computed
|
|
209
|
+
* signal or reaction, this atom will be automatically captured as a dependency.
|
|
210
|
+
*
|
|
211
|
+
* @param epoch - The epoch to get changes since
|
|
212
|
+
* @returns An array of diffs, or RESET_VALUE if history is insufficient
|
|
213
|
+
* @internal
|
|
214
|
+
*/
|
|
143
215
|
getDiffSince(epoch: number): RESET_VALUE | Diff[] {
|
|
144
216
|
maybeCaptureParent(this)
|
|
145
217
|
|
|
@@ -152,7 +224,16 @@ class __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {
|
|
|
152
224
|
}
|
|
153
225
|
}
|
|
154
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Singleton reference to the Atom constructor. Used internally to create atom instances.
|
|
229
|
+
* @internal
|
|
230
|
+
*/
|
|
155
231
|
export const _Atom = singleton('Atom', () => __Atom__)
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Type alias for instances of the internal Atom class.
|
|
235
|
+
* @internal
|
|
236
|
+
*/
|
|
156
237
|
export type _Atom = InstanceType<typeof _Atom>
|
|
157
238
|
|
|
158
239
|
/**
|
|
@@ -192,6 +273,17 @@ export function atom<Value, Diff = unknown>(
|
|
|
192
273
|
|
|
193
274
|
/**
|
|
194
275
|
* Returns true if the given value is an {@link Atom}.
|
|
276
|
+
*
|
|
277
|
+
* @param value - The value to check
|
|
278
|
+
* @returns True if the value is an Atom, false otherwise
|
|
279
|
+
* @example
|
|
280
|
+
* ```ts
|
|
281
|
+
* const myAtom = atom('test', 42)
|
|
282
|
+
* const notAtom = 'hello'
|
|
283
|
+
*
|
|
284
|
+
* console.log(isAtom(myAtom)) // true
|
|
285
|
+
* console.log(isAtom(notAtom)) // false
|
|
286
|
+
* ```
|
|
195
287
|
* @public
|
|
196
288
|
*/
|
|
197
289
|
export function isAtom(value: unknown): value is Atom<unknown> {
|
package/src/lib/Computed.ts
CHANGED
|
@@ -10,13 +10,28 @@ import { Child, ComputeDiff, RESET_VALUE, Signal } from './types'
|
|
|
10
10
|
import { logComputedGetterWarning } from './warnings'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
+
* A special symbol used to indicate that a computed signal has not been initialized yet.
|
|
14
|
+
* This is passed as the `previousValue` parameter to a computed signal function on its first run.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const count = atom('count', 0)
|
|
19
|
+
* const double = computed('double', (prevValue) => {
|
|
20
|
+
* if (isUninitialized(prevValue)) {
|
|
21
|
+
* console.log('First computation!')
|
|
22
|
+
* }
|
|
23
|
+
* return count.get() * 2
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
13
27
|
* @public
|
|
14
28
|
*/
|
|
15
29
|
export const UNINITIALIZED = Symbol.for('com.tldraw.state/UNINITIALIZED')
|
|
16
30
|
/**
|
|
17
31
|
* The type of the first value passed to a computed signal function as the 'prevValue' parameter.
|
|
32
|
+
* This type represents the uninitialized state of a computed signal before its first calculation.
|
|
18
33
|
*
|
|
19
|
-
* @see {@link isUninitialized}
|
|
34
|
+
* @see {@link isUninitialized}
|
|
20
35
|
* @public
|
|
21
36
|
*/
|
|
22
37
|
export type UNINITIALIZED = typeof UNINITIALIZED
|
|
@@ -44,7 +59,25 @@ export function isUninitialized(value: any): value is UNINITIALIZED {
|
|
|
44
59
|
return value === UNINITIALIZED
|
|
45
60
|
}
|
|
46
61
|
|
|
47
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* A singleton class used to wrap computed signal values along with their diffs.
|
|
64
|
+
* This class is used internally by the {@link withDiff} function to provide both
|
|
65
|
+
* the computed value and its diff to the signal system.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* const count = atom('count', 0)
|
|
70
|
+
* const double = computed('double', (prevValue) => {
|
|
71
|
+
* const nextValue = count.get() * 2
|
|
72
|
+
* if (isUninitialized(prevValue)) {
|
|
73
|
+
* return nextValue
|
|
74
|
+
* }
|
|
75
|
+
* return withDiff(nextValue, nextValue - prevValue)
|
|
76
|
+
* })
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
48
81
|
export const WithDiff = singleton(
|
|
49
82
|
'WithDiff',
|
|
50
83
|
() =>
|
|
@@ -56,9 +89,20 @@ export const WithDiff = singleton(
|
|
|
56
89
|
}
|
|
57
90
|
)
|
|
58
91
|
|
|
59
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Interface representing a value wrapped with its corresponding diff.
|
|
94
|
+
* Used in incremental computation to provide both the new value and the diff from the previous value.
|
|
95
|
+
*
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
60
98
|
export interface WithDiff<Value, Diff> {
|
|
99
|
+
/**
|
|
100
|
+
* The computed value.
|
|
101
|
+
*/
|
|
61
102
|
value: Value
|
|
103
|
+
/**
|
|
104
|
+
* The diff between the previous and current value.
|
|
105
|
+
*/
|
|
62
106
|
diff: Diff
|
|
63
107
|
}
|
|
64
108
|
|
|
@@ -89,7 +133,17 @@ export function withDiff<Value, Diff>(value: Value, diff: Diff): WithDiff<Value,
|
|
|
89
133
|
}
|
|
90
134
|
|
|
91
135
|
/**
|
|
92
|
-
* Options for
|
|
136
|
+
* Options for configuring computed signals. Used when calling `computed` or using the `@computed` decorator.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* const greeting = computed('greeting', () => `Hello ${name.get()}!`, {
|
|
141
|
+
* historyLength: 10,
|
|
142
|
+
* isEqual: (a, b) => a === b,
|
|
143
|
+
* computeDiff: (oldVal, newVal) => ({ type: 'change', from: oldVal, to: newVal })
|
|
144
|
+
* })
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
93
147
|
* @public
|
|
94
148
|
*/
|
|
95
149
|
export interface ComputedOptions<Value, Diff> {
|
|
@@ -119,13 +173,27 @@ export interface ComputedOptions<Value, Diff> {
|
|
|
119
173
|
}
|
|
120
174
|
|
|
121
175
|
/**
|
|
122
|
-
* A computed signal created via `computed
|
|
176
|
+
* A computed signal created via the `computed` function or `@computed` decorator.
|
|
177
|
+
* Computed signals derive their values from other signals and automatically update when their dependencies change.
|
|
178
|
+
* They use lazy evaluation, only recalculating when accessed and dependencies have changed.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* const firstName = atom('firstName', 'John')
|
|
183
|
+
* const lastName = atom('lastName', 'Doe')
|
|
184
|
+
* const fullName = computed('fullName', () => `${firstName.get()} ${lastName.get()}`)
|
|
185
|
+
*
|
|
186
|
+
* console.log(fullName.get()) // "John Doe"
|
|
187
|
+
* firstName.set('Jane')
|
|
188
|
+
* console.log(fullName.get()) // "Jane Doe"
|
|
189
|
+
* ```
|
|
123
190
|
*
|
|
124
191
|
* @public
|
|
125
192
|
*/
|
|
126
193
|
export interface Computed<Value, Diff = unknown> extends Signal<Value, Diff> {
|
|
127
194
|
/**
|
|
128
|
-
* Whether this computed
|
|
195
|
+
* Whether this computed signal is involved in an actively-running effect graph.
|
|
196
|
+
* Returns true if there are any reactions or other computed signals depending on this one.
|
|
129
197
|
* @public
|
|
130
198
|
*/
|
|
131
199
|
readonly isActivelyListening: boolean
|
|
@@ -286,7 +354,19 @@ class __UNSAFE__Computed<Value, Diff = unknown> implements Computed<Value, Diff>
|
|
|
286
354
|
}
|
|
287
355
|
}
|
|
288
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Singleton reference to the computed signal implementation class.
|
|
359
|
+
* Used internally by the library to create computed signal instances.
|
|
360
|
+
*
|
|
361
|
+
* @internal
|
|
362
|
+
*/
|
|
289
363
|
export const _Computed = singleton('Computed', () => __UNSAFE__Computed)
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Type alias for the computed signal implementation class.
|
|
367
|
+
*
|
|
368
|
+
* @internal
|
|
369
|
+
*/
|
|
290
370
|
export type _Computed = InstanceType<typeof __UNSAFE__Computed>
|
|
291
371
|
|
|
292
372
|
function computedMethodLegacyDecorator(
|
|
@@ -437,7 +517,9 @@ export function getComputedInstance<Obj extends object, Prop extends keyof Obj>(
|
|
|
437
517
|
}
|
|
438
518
|
|
|
439
519
|
/**
|
|
440
|
-
* Creates a computed signal.
|
|
520
|
+
* Creates a computed signal that derives its value from other signals.
|
|
521
|
+
* Computed signals automatically update when their dependencies change and use lazy evaluation
|
|
522
|
+
* for optimal performance.
|
|
441
523
|
*
|
|
442
524
|
* @example
|
|
443
525
|
* ```ts
|
|
@@ -475,10 +557,10 @@ export function getComputedInstance<Obj extends object, Prop extends keyof Obj>(
|
|
|
475
557
|
* }
|
|
476
558
|
* ```
|
|
477
559
|
*
|
|
478
|
-
* @param name - The name of the signal
|
|
479
|
-
* @param compute - The function that computes the value of the signal.
|
|
480
|
-
* @param options -
|
|
481
|
-
*
|
|
560
|
+
* @param name - The name of the signal for debugging purposes
|
|
561
|
+
* @param compute - The function that computes the value of the signal. Receives the previous value and last computed epoch
|
|
562
|
+
* @param options - Optional configuration for the computed signal
|
|
563
|
+
* @returns A new computed signal
|
|
482
564
|
* @public
|
|
483
565
|
*/
|
|
484
566
|
export function computed<Value, Diff = unknown>(
|
|
@@ -490,7 +572,23 @@ export function computed<Value, Diff = unknown>(
|
|
|
490
572
|
options?: ComputedOptions<Value, Diff>
|
|
491
573
|
): Computed<Value, Diff>
|
|
492
574
|
/**
|
|
493
|
-
*
|
|
575
|
+
* TC39 decorator for creating computed methods in classes.
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* ```ts
|
|
579
|
+
* class MyClass {
|
|
580
|
+
* value = atom('value', 10)
|
|
581
|
+
*
|
|
582
|
+
* @computed
|
|
583
|
+
* doubled() {
|
|
584
|
+
* return this.value.get() * 2
|
|
585
|
+
* }
|
|
586
|
+
* }
|
|
587
|
+
* ```
|
|
588
|
+
*
|
|
589
|
+
* @param compute - The method to be decorated
|
|
590
|
+
* @param context - The decorator context provided by TypeScript
|
|
591
|
+
* @returns The decorated method
|
|
494
592
|
* @public
|
|
495
593
|
*/
|
|
496
594
|
export function computed<This extends object, Value>(
|
|
@@ -498,16 +596,48 @@ export function computed<This extends object, Value>(
|
|
|
498
596
|
context: ClassMethodDecoratorContext<This, () => Value>
|
|
499
597
|
): () => Value
|
|
500
598
|
/**
|
|
501
|
-
*
|
|
599
|
+
* Legacy TypeScript decorator for creating computed methods in classes.
|
|
502
600
|
*
|
|
503
|
-
* @
|
|
601
|
+
* @example
|
|
602
|
+
* ```ts
|
|
603
|
+
* class MyClass {
|
|
604
|
+
* value = atom('value', 10)
|
|
605
|
+
*
|
|
606
|
+
* @computed
|
|
607
|
+
* doubled() {
|
|
608
|
+
* return this.value.get() * 2
|
|
609
|
+
* }
|
|
610
|
+
* }
|
|
611
|
+
* ```
|
|
612
|
+
*
|
|
613
|
+
* @param target - The class prototype
|
|
614
|
+
* @param key - The property key
|
|
615
|
+
* @param descriptor - The property descriptor
|
|
616
|
+
* @returns The modified property descriptor
|
|
617
|
+
* @public
|
|
618
|
+
*/
|
|
504
619
|
export function computed(
|
|
505
620
|
target: any,
|
|
506
621
|
key: string,
|
|
507
622
|
descriptor: PropertyDescriptor
|
|
508
623
|
): PropertyDescriptor
|
|
509
624
|
/**
|
|
510
|
-
*
|
|
625
|
+
* Decorator factory for creating computed methods with options.
|
|
626
|
+
*
|
|
627
|
+
* @example
|
|
628
|
+
* ```ts
|
|
629
|
+
* class MyClass {
|
|
630
|
+
* items = atom('items', [1, 2, 3])
|
|
631
|
+
*
|
|
632
|
+
* @computed({ historyLength: 10 })
|
|
633
|
+
* sum() {
|
|
634
|
+
* return this.items.get().reduce((a, b) => a + b, 0)
|
|
635
|
+
* }
|
|
636
|
+
* }
|
|
637
|
+
* ```
|
|
638
|
+
*
|
|
639
|
+
* @param options - Configuration options for the computed signal
|
|
640
|
+
* @returns A decorator function that can be applied to methods
|
|
511
641
|
* @public
|
|
512
642
|
*/
|
|
513
643
|
export function computed<Value, Diff = unknown>(
|
|
@@ -518,7 +648,17 @@ export function computed<Value, Diff = unknown>(
|
|
|
518
648
|
context: ClassMethodDecoratorContext<This, () => Value>
|
|
519
649
|
) => () => Value)
|
|
520
650
|
|
|
521
|
-
/**
|
|
651
|
+
/**
|
|
652
|
+
* Implementation function that handles all computed signal creation and decoration scenarios.
|
|
653
|
+
* This function is overloaded to support multiple usage patterns:
|
|
654
|
+
* - Creating computed signals directly
|
|
655
|
+
* - Using as a TC39 decorator
|
|
656
|
+
* - Using as a legacy decorator
|
|
657
|
+
* - Using as a decorator factory with options
|
|
658
|
+
*
|
|
659
|
+
* @returns Either a computed signal instance or a decorator function depending on usage
|
|
660
|
+
* @public
|
|
661
|
+
*/
|
|
522
662
|
export function computed() {
|
|
523
663
|
if (arguments.length === 1) {
|
|
524
664
|
const options = arguments[0]
|
|
@@ -532,11 +672,21 @@ export function computed() {
|
|
|
532
672
|
|
|
533
673
|
/**
|
|
534
674
|
* Returns true if the given value is a computed signal.
|
|
675
|
+
* This is a type guard function that can be used to check if a value is a computed signal instance.
|
|
676
|
+
*
|
|
677
|
+
* @example
|
|
678
|
+
* ```ts
|
|
679
|
+
* const count = atom('count', 0)
|
|
680
|
+
* const double = computed('double', () => count.get() * 2)
|
|
681
|
+
*
|
|
682
|
+
* console.log(isComputed(count)) // false
|
|
683
|
+
* console.log(isComputed(double)) // true
|
|
684
|
+
* ```
|
|
535
685
|
*
|
|
536
|
-
* @param value
|
|
537
|
-
* @returns
|
|
686
|
+
* @param value - The value to check
|
|
687
|
+
* @returns True if the value is a computed signal, false otherwise
|
|
538
688
|
* @public
|
|
539
689
|
*/
|
|
540
690
|
export function isComputed(value: any): value is Computed<any> {
|
|
541
|
-
return value && value instanceof _Computed
|
|
691
|
+
return !!(value && value instanceof _Computed)
|
|
542
692
|
}
|
package/src/lib/HistoryBuffer.ts
CHANGED
|
@@ -1,28 +1,71 @@
|
|
|
1
1
|
import { RESET_VALUE } from './types'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A tuple representing a range of epochs and the associated diff.
|
|
5
|
+
* Used internally by HistoryBuffer to store change information.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
3
9
|
type RangeTuple<Diff> = [fromEpoch: number, toEpoch: number, diff: Diff]
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
|
-
* A
|
|
12
|
+
* A circular buffer that stores diffs between sequential values of an atom or computed signal.
|
|
13
|
+
* This enables efficient change tracking and history retrieval for features like undo/redo.
|
|
14
|
+
*
|
|
15
|
+
* The buffer uses a wrap-around strategy to maintain a fixed-size history of the most recent
|
|
16
|
+
* changes, automatically overwriting older entries when the capacity is exceeded.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const buffer = new HistoryBuffer<string>(5)
|
|
21
|
+
* buffer.pushEntry(0, 1, 'first change')
|
|
22
|
+
* buffer.pushEntry(1, 2, 'second change')
|
|
23
|
+
* const changes = buffer.getChangesSince(0) // ['first change', 'second change']
|
|
24
|
+
* ```
|
|
7
25
|
*
|
|
8
26
|
* @internal
|
|
9
27
|
*/
|
|
10
28
|
export class HistoryBuffer<Diff> {
|
|
29
|
+
/**
|
|
30
|
+
* Current write position in the circular buffer.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
11
33
|
private index = 0
|
|
12
34
|
|
|
13
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Circular buffer storing range tuples. Uses undefined to represent empty slots.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
14
39
|
buffer: Array<RangeTuple<Diff> | undefined>
|
|
15
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new HistoryBuffer with the specified capacity.
|
|
43
|
+
*
|
|
44
|
+
* capacity - Maximum number of diffs to store in the buffer
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const buffer = new HistoryBuffer<number>(10) // Store up to 10 diffs
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
16
50
|
constructor(private readonly capacity: number) {
|
|
17
51
|
this.buffer = new Array(capacity)
|
|
18
52
|
}
|
|
19
53
|
|
|
20
54
|
/**
|
|
21
|
-
*
|
|
55
|
+
* Adds a diff entry to the history buffer, representing a change between two epochs.
|
|
56
|
+
*
|
|
57
|
+
* If the diff is undefined, the operation is ignored. If the diff is RESET_VALUE,
|
|
58
|
+
* the entire buffer is cleared to indicate that historical tracking should restart.
|
|
22
59
|
*
|
|
23
|
-
* @param lastComputedEpoch - The epoch when the
|
|
24
|
-
* @param currentEpoch - The current
|
|
25
|
-
* @param diff - The diff
|
|
60
|
+
* @param lastComputedEpoch - The epoch when the previous value was computed
|
|
61
|
+
* @param currentEpoch - The epoch when the current value was computed
|
|
62
|
+
* @param diff - The diff representing the change, or RESET_VALUE to clear history
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const buffer = new HistoryBuffer<string>(5)
|
|
66
|
+
* buffer.pushEntry(0, 1, 'added text')
|
|
67
|
+
* buffer.pushEntry(1, 2, RESET_VALUE) // Clears the buffer
|
|
68
|
+
* ```
|
|
26
69
|
*/
|
|
27
70
|
pushEntry(lastComputedEpoch: number, currentEpoch: number, diff: Diff | RESET_VALUE) {
|
|
28
71
|
if (diff === undefined) {
|
|
@@ -42,7 +85,16 @@ export class HistoryBuffer<Diff> {
|
|
|
42
85
|
}
|
|
43
86
|
|
|
44
87
|
/**
|
|
45
|
-
*
|
|
88
|
+
* Clears all entries from the history buffer and resets the write position.
|
|
89
|
+
* This is called when a RESET_VALUE diff is encountered.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* const buffer = new HistoryBuffer<string>(5)
|
|
94
|
+
* buffer.pushEntry(0, 1, 'change')
|
|
95
|
+
* buffer.clear()
|
|
96
|
+
* console.log(buffer.getChangesSince(0)) // RESET_VALUE
|
|
97
|
+
* ```
|
|
46
98
|
*/
|
|
47
99
|
clear() {
|
|
48
100
|
this.index = 0
|
|
@@ -50,10 +102,24 @@ export class HistoryBuffer<Diff> {
|
|
|
50
102
|
}
|
|
51
103
|
|
|
52
104
|
/**
|
|
53
|
-
*
|
|
105
|
+
* Retrieves all diffs that occurred since the specified epoch.
|
|
106
|
+
*
|
|
107
|
+
* The method searches backwards through the circular buffer to find changes
|
|
108
|
+
* that occurred after the given epoch. If insufficient history is available
|
|
109
|
+
* or the requested epoch is too old, returns RESET_VALUE indicating that
|
|
110
|
+
* a complete state rebuild is required.
|
|
54
111
|
*
|
|
55
|
-
* @param sinceEpoch - The epoch to
|
|
56
|
-
* @returns
|
|
112
|
+
* @param sinceEpoch - The epoch from which to retrieve changes
|
|
113
|
+
* @returns Array of diffs since the epoch, or RESET_VALUE if history is insufficient
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const buffer = new HistoryBuffer<string>(5)
|
|
117
|
+
* buffer.pushEntry(0, 1, 'first')
|
|
118
|
+
* buffer.pushEntry(1, 2, 'second')
|
|
119
|
+
* const changes = buffer.getChangesSince(0) // ['first', 'second']
|
|
120
|
+
* const recentChanges = buffer.getChangesSince(1) // ['second']
|
|
121
|
+
* const tooOld = buffer.getChangesSince(-100) // RESET_VALUE
|
|
122
|
+
* ```
|
|
57
123
|
*/
|
|
58
124
|
getChangesSince(sinceEpoch: number): RESET_VALUE | Diff[] {
|
|
59
125
|
const { index, capacity, buffer } = this
|