@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.
Files changed (56) hide show
  1. package/dist-cjs/index.d.ts +358 -43
  2. package/dist-cjs/index.js +3 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/ArraySet.js +90 -6
  5. package/dist-cjs/lib/ArraySet.js.map +2 -2
  6. package/dist-cjs/lib/Atom.js +70 -0
  7. package/dist-cjs/lib/Atom.js.map +2 -2
  8. package/dist-cjs/lib/Computed.js +1 -1
  9. package/dist-cjs/lib/Computed.js.map +2 -2
  10. package/dist-cjs/lib/HistoryBuffer.js +57 -9
  11. package/dist-cjs/lib/HistoryBuffer.js.map +2 -2
  12. package/dist-cjs/lib/capture.js.map +2 -2
  13. package/dist-cjs/lib/constants.js.map +2 -2
  14. package/dist-cjs/lib/helpers.js.map +2 -2
  15. package/dist-cjs/lib/isSignal.js.map +2 -2
  16. package/dist-cjs/lib/localStorageAtom.js +60 -0
  17. package/dist-cjs/lib/localStorageAtom.js.map +7 -0
  18. package/dist-cjs/lib/transactions.js.map +2 -2
  19. package/dist-cjs/lib/types.js.map +2 -2
  20. package/dist-cjs/lib/warnings.js.map +2 -2
  21. package/dist-esm/index.d.mts +358 -43
  22. package/dist-esm/index.mjs +3 -1
  23. package/dist-esm/index.mjs.map +2 -2
  24. package/dist-esm/lib/ArraySet.mjs +90 -6
  25. package/dist-esm/lib/ArraySet.mjs.map +2 -2
  26. package/dist-esm/lib/Atom.mjs +70 -0
  27. package/dist-esm/lib/Atom.mjs.map +2 -2
  28. package/dist-esm/lib/Computed.mjs +1 -1
  29. package/dist-esm/lib/Computed.mjs.map +2 -2
  30. package/dist-esm/lib/HistoryBuffer.mjs +57 -9
  31. package/dist-esm/lib/HistoryBuffer.mjs.map +2 -2
  32. package/dist-esm/lib/capture.mjs.map +2 -2
  33. package/dist-esm/lib/constants.mjs.map +2 -2
  34. package/dist-esm/lib/helpers.mjs.map +2 -2
  35. package/dist-esm/lib/isSignal.mjs.map +2 -2
  36. package/dist-esm/lib/localStorageAtom.mjs +40 -0
  37. package/dist-esm/lib/localStorageAtom.mjs.map +7 -0
  38. package/dist-esm/lib/transactions.mjs.map +2 -2
  39. package/dist-esm/lib/types.mjs.map +2 -2
  40. package/dist-esm/lib/warnings.mjs.map +2 -2
  41. package/package.json +2 -2
  42. package/src/index.ts +1 -0
  43. package/src/lib/ArraySet.ts +101 -8
  44. package/src/lib/Atom.ts +92 -0
  45. package/src/lib/Computed.ts +169 -19
  46. package/src/lib/HistoryBuffer.ts +76 -10
  47. package/src/lib/__tests__/helpers.test.ts +168 -0
  48. package/src/lib/__tests__/localStorageAtom.test.ts +183 -0
  49. package/src/lib/capture.ts +56 -1
  50. package/src/lib/constants.ts +25 -1
  51. package/src/lib/helpers.ts +137 -13
  52. package/src/lib/isSignal.ts +24 -0
  53. package/src/lib/localStorageAtom.ts +87 -0
  54. package/src/lib/transactions.ts +79 -10
  55. package/src/lib/types.ts +173 -19
  56. 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
+ }
@@ -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
- * print(`Hello, ${firstName.get()} ${lastName.get()}!`)
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
- * print(`Hello, ${firstName.get()} ${lastName.get()}!`)
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
- * print(`Hello, ${firstName.get()} ${lastName.get()}!`)
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
- * @param fn - The function to run in a transaction.
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
- /** @public */
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
- /** @public */
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 Signal is a reactive value container. The value may change over time, and it may keep track of the diffs between sequential values.
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
- * - Atomic signals, created using {@link atom}. These are mutable references to values that can be changed using {@link Atom.set}.
15
- * - Computed signals, created using `computed`. These are values that are computed from other signals. They are recomputed lazily if their dependencies change.
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
- * The name of the signal. This is used at runtime for debugging and perf profiling only. It does not need to be globally unique.
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
- * The current value of the signal. This is a reactive value, and will update when the signal changes.
26
- * Any computed signal that depends on this signal will be lazily recomputed if this signal changes.
27
- * Any effect that depends on this signal will be rescheduled if this signal changes.
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. Note that this is not the same as when the value was last computed.
33
- * A signal may recompute it's value without changing it.
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
- * Returns the sequence of diffs between the the value at the given epoch and the current value.
38
- * Returns the `RESET_VALUE` constant if there is not enough information to compute the diff sequence.
39
- * @param epoch - The epoch to get diffs since.
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
- * Returns the current value of the signal without capturing it as a dependency.
44
- * Use this if you need to retrieve the signal's value in a hot loop where the performance overhead of dependency tracking is too high.
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
- /** @internal */
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
- * Computes the diff between the previous and current value.
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
- * If the diff cannot be computed for whatever reason, it should return {@link state#RESET_VALUE}.
218
+ * const count = atom('count', 0, { computeDiff: numberDiff })
219
+ * ```
66
220
  *
67
221
  * @public
68
222
  */
@@ -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