@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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { deleteFromLocalStorage, getFromLocalStorage, setInLocalStorage } from '@tldraw/utils'
|
|
2
|
+
import { Atom, atom, AtomOptions } from './Atom'
|
|
3
|
+
import { react } from './EffectScheduler'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a new {@link Atom} that persists its value to localStorage.
|
|
7
|
+
*
|
|
8
|
+
* The atom is automatically synced with localStorage - changes to the atom are saved to localStorage,
|
|
9
|
+
* and the initial value is read from localStorage if it exists. Returns both the atom and a cleanup
|
|
10
|
+
* function that should be called to stop syncing when the atom is no longer needed. If you need to delete
|
|
11
|
+
* the atom, you should do it manually after all cleanup functions have been called.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const [theme, cleanup] = localStorageAtom('theme', 'light')
|
|
16
|
+
*
|
|
17
|
+
* theme.get() // 'light' or value from localStorage if it exists
|
|
18
|
+
*
|
|
19
|
+
* theme.set('dark') // updates atom and saves to localStorage
|
|
20
|
+
*
|
|
21
|
+
* // When done:
|
|
22
|
+
* cleanup() // stops syncing to localStorage
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @param name - The localStorage key and atom name. This is used for both localStorage persistence
|
|
26
|
+
* and debugging/profiling purposes.
|
|
27
|
+
* @param initialValue - The initial value of the atom, used if no value exists in localStorage.
|
|
28
|
+
* @param options - Optional atom configuration. See {@link AtomOptions}.
|
|
29
|
+
* @returns A tuple containing the atom and a cleanup function to stop localStorage syncing.
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export function localStorageAtom<Value, Diff = unknown>(
|
|
33
|
+
name: string,
|
|
34
|
+
initialValue: Value,
|
|
35
|
+
options?: AtomOptions<Value, Diff>
|
|
36
|
+
): [Atom<Value, Diff>, () => void] {
|
|
37
|
+
// Try to restore the initial value from localStorage
|
|
38
|
+
let _initialValue = initialValue
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const value = getFromLocalStorage(name)
|
|
42
|
+
if (value) {
|
|
43
|
+
_initialValue = JSON.parse(value) as Value
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// If parsing fails, the stored value is corrupted - delete it and use the provided initial value
|
|
47
|
+
deleteFromLocalStorage(name)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Create the atom with the restored or initial value
|
|
51
|
+
const outAtom = atom(name, _initialValue, options)
|
|
52
|
+
|
|
53
|
+
// Set up automatic syncing: whenever the atom changes, save it to localStorage
|
|
54
|
+
const reactCleanup = react(`save ${name} to localStorage`, () => {
|
|
55
|
+
setInLocalStorage(name, JSON.stringify(outAtom.get()))
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Set up cross-tab sync: listen for storage events from other tabs
|
|
59
|
+
const handleStorageEvent = (event: StorageEvent) => {
|
|
60
|
+
// Only handle events for this specific key
|
|
61
|
+
if (event.key !== name) return
|
|
62
|
+
|
|
63
|
+
// If the value was deleted in another tab
|
|
64
|
+
if (event.newValue === null) {
|
|
65
|
+
outAtom.set(initialValue)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If the value was changed in another tab, update the atom
|
|
70
|
+
try {
|
|
71
|
+
const newValue = JSON.parse(event.newValue) as Value
|
|
72
|
+
outAtom.set(newValue)
|
|
73
|
+
} catch {
|
|
74
|
+
// If parsing fails, the stored value is corrupted; preserve the existing value
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
window.addEventListener('storage', handleStorageEvent)
|
|
79
|
+
|
|
80
|
+
// Combined cleanup function
|
|
81
|
+
const cleanup = () => {
|
|
82
|
+
reactCleanup()
|
|
83
|
+
window.removeEventListener('storage', handleStorageEvent)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return [outAtom, cleanup]
|
|
87
|
+
}
|
package/src/lib/transactions.ts
CHANGED
|
@@ -78,14 +78,35 @@ const inst = singleton('transactions', () => ({
|
|
|
78
78
|
reactionEpoch: GLOBAL_START_EPOCH + 1,
|
|
79
79
|
}))
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Gets the current reaction epoch, which is used to track when reactions are running.
|
|
83
|
+
* The reaction epoch is updated at the start of each reaction cycle.
|
|
84
|
+
*
|
|
85
|
+
* @returns The current reaction epoch number
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
81
88
|
export function getReactionEpoch() {
|
|
82
89
|
return inst.reactionEpoch
|
|
83
90
|
}
|
|
84
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Gets the current global epoch, which is incremented every time any atom changes.
|
|
94
|
+
* This is used to track changes across the entire reactive system.
|
|
95
|
+
*
|
|
96
|
+
* @returns The current global epoch number
|
|
97
|
+
* @public
|
|
98
|
+
*/
|
|
85
99
|
export function getGlobalEpoch() {
|
|
86
100
|
return inst.globalEpoch
|
|
87
101
|
}
|
|
88
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Checks whether any reactions are currently executing.
|
|
105
|
+
* When true, the system is in the middle of processing effects and side effects.
|
|
106
|
+
*
|
|
107
|
+
* @returns True if reactions are currently running, false otherwise
|
|
108
|
+
* @public
|
|
109
|
+
*/
|
|
89
110
|
export function getIsReacting() {
|
|
90
111
|
return inst.globalIsReacting
|
|
91
112
|
}
|
|
@@ -184,20 +205,27 @@ function traverseAtomForCleanup(atom: _Atom) {
|
|
|
184
205
|
atom.children.visit((child) => traverse(rs, child))
|
|
185
206
|
}
|
|
186
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Advances the global epoch counter by one.
|
|
210
|
+
* This is used internally to track when changes occur across the reactive system.
|
|
211
|
+
*
|
|
212
|
+
* @internal
|
|
213
|
+
*/
|
|
187
214
|
export function advanceGlobalEpoch() {
|
|
188
215
|
inst.globalEpoch++
|
|
189
216
|
}
|
|
190
217
|
|
|
191
218
|
/**
|
|
192
219
|
* Batches state updates, deferring side effects until after the transaction completes.
|
|
220
|
+
* Unlike {@link transact}, this function always creates a new transaction, allowing for nested transactions.
|
|
193
221
|
*
|
|
194
222
|
* @example
|
|
195
223
|
* ```ts
|
|
196
|
-
* const firstName = atom('John')
|
|
197
|
-
* const lastName = atom('Doe')
|
|
224
|
+
* const firstName = atom('firstName', 'John')
|
|
225
|
+
* const lastName = atom('lastName', 'Doe')
|
|
198
226
|
*
|
|
199
227
|
* react('greet', () => {
|
|
200
|
-
*
|
|
228
|
+
* console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
|
201
229
|
* })
|
|
202
230
|
*
|
|
203
231
|
* // Logs "Hello, John Doe!"
|
|
@@ -214,11 +242,11 @@ export function advanceGlobalEpoch() {
|
|
|
214
242
|
*
|
|
215
243
|
* @example
|
|
216
244
|
* ```ts
|
|
217
|
-
* const firstName = atom('John')
|
|
218
|
-
* const lastName = atom('Doe')
|
|
245
|
+
* const firstName = atom('firstName', 'John')
|
|
246
|
+
* const lastName = atom('lastName', 'Doe')
|
|
219
247
|
*
|
|
220
248
|
* react('greet', () => {
|
|
221
|
-
*
|
|
249
|
+
* console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
|
222
250
|
* })
|
|
223
251
|
*
|
|
224
252
|
* // Logs "Hello, John Doe!"
|
|
@@ -237,11 +265,11 @@ export function advanceGlobalEpoch() {
|
|
|
237
265
|
*
|
|
238
266
|
* @example
|
|
239
267
|
* ```ts
|
|
240
|
-
* const firstName = atom('John')
|
|
241
|
-
* const lastName = atom('Doe')
|
|
268
|
+
* const firstName = atom('firstName', 'John')
|
|
269
|
+
* const lastName = atom('lastName', 'Doe')
|
|
242
270
|
*
|
|
243
271
|
* react('greet', () => {
|
|
244
|
-
*
|
|
272
|
+
* console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)
|
|
245
273
|
* })
|
|
246
274
|
*
|
|
247
275
|
* // Logs "Hello, John Doe!"
|
|
@@ -258,6 +286,7 @@ export function advanceGlobalEpoch() {
|
|
|
258
286
|
* ```
|
|
259
287
|
*
|
|
260
288
|
* @param fn - The function to run in a transaction, called with a function to roll back the change.
|
|
289
|
+
* @returns The return value of the function
|
|
261
290
|
* @public
|
|
262
291
|
*/
|
|
263
292
|
export function transaction<T>(fn: (rollback: () => void) => T) {
|
|
@@ -299,8 +328,27 @@ export function transaction<T>(fn: (rollback: () => void) => T) {
|
|
|
299
328
|
|
|
300
329
|
/**
|
|
301
330
|
* Like {@link transaction}, but does not create a new transaction if there is already one in progress.
|
|
331
|
+
* This is the preferred way to batch state updates when you don't need the rollback functionality.
|
|
302
332
|
*
|
|
303
|
-
* @
|
|
333
|
+
* @example
|
|
334
|
+
* ```ts
|
|
335
|
+
* const count = atom('count', 0)
|
|
336
|
+
* const doubled = atom('doubled', 0)
|
|
337
|
+
*
|
|
338
|
+
* react('update doubled', () => {
|
|
339
|
+
* console.log(`Count: ${count.get()}, Doubled: ${doubled.get()}`)
|
|
340
|
+
* })
|
|
341
|
+
*
|
|
342
|
+
* // This batches both updates into a single reaction
|
|
343
|
+
* transact(() => {
|
|
344
|
+
* count.set(5)
|
|
345
|
+
* doubled.set(count.get() * 2)
|
|
346
|
+
* })
|
|
347
|
+
* // Logs: "Count: 5, Doubled: 10"
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* @param fn - The function to run in a transaction
|
|
351
|
+
* @returns The return value of the function
|
|
304
352
|
* @public
|
|
305
353
|
*/
|
|
306
354
|
export function transact<T>(fn: () => T): T {
|
|
@@ -311,6 +359,27 @@ export function transact<T>(fn: () => T): T {
|
|
|
311
359
|
}
|
|
312
360
|
|
|
313
361
|
/**
|
|
362
|
+
* Defers the execution of asynchronous effects until they can be properly handled.
|
|
363
|
+
* This function creates an asynchronous transaction context that batches state updates
|
|
364
|
+
* across async operations while preventing conflicts with synchronous transactions.
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```ts
|
|
368
|
+
* const data = atom('data', null)
|
|
369
|
+
* const loading = atom('loading', false)
|
|
370
|
+
*
|
|
371
|
+
* await deferAsyncEffects(async () => {
|
|
372
|
+
* loading.set(true)
|
|
373
|
+
* const result = await fetch('/api/data')
|
|
374
|
+
* const json = await result.json()
|
|
375
|
+
* data.set(json)
|
|
376
|
+
* loading.set(false)
|
|
377
|
+
* })
|
|
378
|
+
* ```
|
|
379
|
+
*
|
|
380
|
+
* @param fn - The async function to execute within the deferred context
|
|
381
|
+
* @returns A promise that resolves to the return value of the function
|
|
382
|
+
* @throws Will throw if called during a synchronous transaction
|
|
314
383
|
* @internal
|
|
315
384
|
*/
|
|
316
385
|
export async function deferAsyncEffects<T>(fn: () => Promise<T>) {
|
package/src/lib/types.ts
CHANGED
|
@@ -1,68 +1,222 @@
|
|
|
1
1
|
import { ArraySet } from './ArraySet'
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* A unique symbol used to indicate that a signal's value should be reset or that
|
|
5
|
+
* there is insufficient history to compute diffs between epochs.
|
|
6
|
+
*
|
|
7
|
+
* This value is returned by {@link Signal.getDiffSince} when the requested epoch
|
|
8
|
+
* is too far in the past and the diff sequence cannot be reconstructed.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { atom, getGlobalEpoch, RESET_VALUE } from '@tldraw/state'
|
|
13
|
+
*
|
|
14
|
+
* const count = atom('count', 0, { historyLength: 3 })
|
|
15
|
+
* const oldEpoch = getGlobalEpoch()
|
|
16
|
+
*
|
|
17
|
+
* // Make many changes that exceed history length
|
|
18
|
+
* count.set(1)
|
|
19
|
+
* count.set(2)
|
|
20
|
+
* count.set(3)
|
|
21
|
+
* count.set(4)
|
|
22
|
+
*
|
|
23
|
+
* const diffs = count.getDiffSince(oldEpoch)
|
|
24
|
+
* if (diffs === RESET_VALUE) {
|
|
25
|
+
* console.log('Too many changes, need to reset state')
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
4
31
|
export const RESET_VALUE: unique symbol = Symbol.for('com.tldraw.state/RESET_VALUE')
|
|
5
32
|
|
|
6
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Type representing the the unique symbol RESET_VALUE symbol, used in type annotations
|
|
35
|
+
* to indicate when a signal value should be reset or when diff computation
|
|
36
|
+
* cannot proceed due to insufficient history.
|
|
37
|
+
*
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
7
40
|
export type RESET_VALUE = typeof RESET_VALUE
|
|
8
41
|
|
|
9
42
|
/**
|
|
10
|
-
* A
|
|
43
|
+
* A reactive value container that can change over time and track diffs between sequential values.
|
|
44
|
+
*
|
|
45
|
+
* Signals are the foundation of the \@tldraw/state reactive system. They automatically manage
|
|
46
|
+
* dependencies and trigger updates when their values change. Any computed signal or effect
|
|
47
|
+
* that reads from this signal will be automatically recomputed when the signal's value changes.
|
|
11
48
|
*
|
|
12
49
|
* There are two types of signal:
|
|
50
|
+
* - **Atomic signals** - Created using `atom()`. These are mutable containers that can be
|
|
51
|
+
* directly updated using `set()` or `update()` methods.
|
|
52
|
+
* - **Computed signals** - Created using `computed()`. These derive their values from other
|
|
53
|
+
* signals and are automatically recomputed when dependencies change.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { atom, computed } from '@tldraw/state'
|
|
13
58
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
59
|
+
* // Create an atomic signal
|
|
60
|
+
* const count = atom('count', 0)
|
|
61
|
+
*
|
|
62
|
+
* // Create a computed signal that derives from the atom
|
|
63
|
+
* const doubled = computed('doubled', () => count.get() * 2)
|
|
64
|
+
*
|
|
65
|
+
* console.log(doubled.get()) // 0
|
|
66
|
+
* count.set(5)
|
|
67
|
+
* console.log(doubled.get()) // 10
|
|
68
|
+
* ```
|
|
16
69
|
*
|
|
17
70
|
* @public
|
|
18
71
|
*/
|
|
19
72
|
export interface Signal<Value, Diff = unknown> {
|
|
20
73
|
/**
|
|
21
|
-
*
|
|
74
|
+
* A human-readable identifier for this signal, used primarily for debugging and performance profiling.
|
|
75
|
+
*
|
|
76
|
+
* The name is displayed in debug output from {@link whyAmIRunning} and other diagnostic tools.
|
|
77
|
+
* It does not need to be globally unique within your application.
|
|
22
78
|
*/
|
|
23
79
|
name: string
|
|
24
80
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
81
|
+
* Gets the current value of the signal and establishes a dependency relationship.
|
|
82
|
+
*
|
|
83
|
+
* When called from within a computed signal or effect, this signal will be automatically
|
|
84
|
+
* tracked as a dependency. If this signal's value changes, any dependent computations
|
|
85
|
+
* or effects will be marked for re-execution.
|
|
86
|
+
*
|
|
87
|
+
* @returns The current value stored in the signal
|
|
28
88
|
*/
|
|
29
89
|
get(): Value
|
|
30
90
|
|
|
31
91
|
/**
|
|
32
|
-
* The epoch when this signal's value last changed.
|
|
33
|
-
*
|
|
92
|
+
* The global epoch number when this signal's value last changed.
|
|
93
|
+
*
|
|
94
|
+
* Note that this represents when the value actually changed, not when it was last computed.
|
|
95
|
+
* A computed signal may recalculate and produce the same value without changing its epoch.
|
|
96
|
+
* This is used internally for dependency tracking and history management.
|
|
34
97
|
*/
|
|
35
98
|
lastChangedEpoch: number
|
|
36
99
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
100
|
+
* Gets the sequence of diffs that occurred between a specific epoch and the current state.
|
|
101
|
+
*
|
|
102
|
+
* This method enables incremental synchronization by providing a list of changes that
|
|
103
|
+
* have occurred since a specific point in time. If the requested epoch is too far in
|
|
104
|
+
* the past or the signal doesn't have enough history, it returns the unique symbol RESET_VALUE
|
|
105
|
+
* to indicate that a full state reset is required.
|
|
106
|
+
*
|
|
107
|
+
* @param epoch - The epoch timestamp to get diffs since
|
|
108
|
+
* @returns An array of diff objects representing changes since the epoch, or the unique symbol RESET_VALUE if insufficient history is available
|
|
40
109
|
*/
|
|
41
110
|
getDiffSince(epoch: number): RESET_VALUE | Diff[]
|
|
42
111
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
112
|
+
* Gets the current value of the signal without establishing a dependency relationship.
|
|
113
|
+
*
|
|
114
|
+
* This method bypasses the automatic dependency tracking system, making it useful for
|
|
115
|
+
* performance-critical code paths where the overhead of dependency capture would be
|
|
116
|
+
* problematic. Use with caution as it breaks the reactive guarantees of the system.
|
|
117
|
+
*
|
|
118
|
+
* **Warning**: This method should only be used when you're certain that you don't need
|
|
119
|
+
* the calling context to react to changes in this signal.
|
|
120
|
+
*
|
|
121
|
+
* @param ignoreErrors - Whether to suppress errors during value retrieval (optional)
|
|
122
|
+
* @returns The current value without establishing dependencies
|
|
45
123
|
*/
|
|
46
124
|
__unsafe__getWithoutCapture(ignoreErrors?: boolean): Value
|
|
47
125
|
/** @internal */
|
|
48
126
|
children: ArraySet<Child>
|
|
49
127
|
}
|
|
50
128
|
|
|
51
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* Internal interface representing a child node in the signal dependency graph.
|
|
131
|
+
*
|
|
132
|
+
* This interface is used internally by the reactive system to manage dependencies
|
|
133
|
+
* between signals, computed values, and effects. Each child tracks its parent
|
|
134
|
+
* signals and maintains state needed for efficient dependency graph traversal
|
|
135
|
+
* and change propagation.
|
|
136
|
+
*
|
|
137
|
+
* @internal
|
|
138
|
+
*/
|
|
52
139
|
export interface Child {
|
|
140
|
+
/**
|
|
141
|
+
* The epoch when this child was last traversed during dependency graph updates.
|
|
142
|
+
* Used to prevent redundant traversals during change propagation.
|
|
143
|
+
*/
|
|
53
144
|
lastTraversedEpoch: number
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Set of parent signals that this child depends on.
|
|
148
|
+
* Used for efficient lookup and cleanup operations.
|
|
149
|
+
*/
|
|
54
150
|
readonly parentSet: ArraySet<Signal<any, any>>
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Array of parent signals that this child depends on.
|
|
154
|
+
* Maintained in parallel with parentSet for ordered access.
|
|
155
|
+
*/
|
|
55
156
|
readonly parents: Signal<any, any>[]
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Array of epochs corresponding to each parent signal.
|
|
160
|
+
* Used to detect which parents have changed since last computation.
|
|
161
|
+
*/
|
|
56
162
|
readonly parentEpochs: number[]
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Human-readable name for this child, used in debugging output.
|
|
166
|
+
*/
|
|
57
167
|
readonly name: string
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Whether this child is currently subscribed to change notifications.
|
|
171
|
+
* Used to optimize resource usage by unsubscribing inactive dependencies.
|
|
172
|
+
*/
|
|
58
173
|
isActivelyListening: boolean
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Debug information tracking ancestor epochs in the dependency graph.
|
|
177
|
+
* Only populated in debug builds for diagnostic purposes.
|
|
178
|
+
*/
|
|
59
179
|
__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null
|
|
60
180
|
}
|
|
61
181
|
|
|
62
182
|
/**
|
|
63
|
-
*
|
|
183
|
+
* A function type that computes the difference between two values of a signal.
|
|
184
|
+
*
|
|
185
|
+
* This function is used to generate incremental diffs that can be applied to
|
|
186
|
+
* reconstruct state changes over time. It's particularly useful for features
|
|
187
|
+
* like undo/redo, synchronization, and change tracking.
|
|
188
|
+
*
|
|
189
|
+
* The function should analyze the previous and current values and return a
|
|
190
|
+
* diff object that represents the change. If the diff cannot be computed
|
|
191
|
+
* (e.g., the values are too different or incompatible), it should return
|
|
192
|
+
* the unique symbol RESET_VALUE to indicate that a full state reset is required.
|
|
193
|
+
*
|
|
194
|
+
* @param previousValue - The previous value of the signal
|
|
195
|
+
* @param currentValue - The current value of the signal
|
|
196
|
+
* @param lastComputedEpoch - The epoch when the previous value was set
|
|
197
|
+
* @param currentEpoch - The epoch when the current value was set
|
|
198
|
+
* @returns A diff object representing the change, or the unique symbol RESET_VALUE if no diff can be computed
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* import { atom, RESET_VALUE } from '@tldraw/state'
|
|
203
|
+
*
|
|
204
|
+
* // Simple numeric diff
|
|
205
|
+
* const numberDiff: ComputeDiff<number, number> = (prev, curr) => curr - prev
|
|
206
|
+
*
|
|
207
|
+
* // Array diff with reset fallback
|
|
208
|
+
* const arrayDiff: ComputeDiff<string[], { added: string[], removed: string[] }> = (prev, curr) => {
|
|
209
|
+
* if (prev.length > 1000 || curr.length > 1000) {
|
|
210
|
+
* return RESET_VALUE // Too complex, force reset
|
|
211
|
+
* }
|
|
212
|
+
* return {
|
|
213
|
+
* added: curr.filter(item => !prev.includes(item)),
|
|
214
|
+
* removed: prev.filter(item => !curr.includes(item))
|
|
215
|
+
* }
|
|
216
|
+
* }
|
|
64
217
|
*
|
|
65
|
-
*
|
|
218
|
+
* const count = atom('count', 0, { computeDiff: numberDiff })
|
|
219
|
+
* ```
|
|
66
220
|
*
|
|
67
221
|
* @public
|
|
68
222
|
*/
|
package/src/lib/warnings.ts
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flag to track whether the computed getter deprecation warning has already been shown.
|
|
3
|
+
* Prevents the same warning from being logged multiple times during application runtime.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
1
7
|
let didWarnComputedGetter = false
|
|
2
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Logs a deprecation warning for the deprecated `@computed` getter decorator syntax.
|
|
11
|
+
* This function is called internally when the library detects usage of `@computed`
|
|
12
|
+
* on a getter method instead of the recommended method syntax.
|
|
13
|
+
*
|
|
14
|
+
* The warning is only shown once per application session to avoid spam in the console.
|
|
15
|
+
* It provides clear guidance on how to migrate from the deprecated getter syntax
|
|
16
|
+
* to the current method-based approach.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* // Deprecated pattern that triggers this warning:
|
|
21
|
+
* class MyClass {
|
|
22
|
+
* @computed
|
|
23
|
+
* get value() {
|
|
24
|
+
* return this.someAtom.get()
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* // Recommended pattern:
|
|
29
|
+
* class MyClass {
|
|
30
|
+
* @computed
|
|
31
|
+
* getValue() {
|
|
32
|
+
* return this.someAtom.get()
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
3
39
|
export function logComputedGetterWarning() {
|
|
4
40
|
if (didWarnComputedGetter) return
|
|
5
41
|
didWarnComputedGetter = true
|