@tldraw/state 5.3.2 → 5.4.0-canary.02cd0bd3b597

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 (71) hide show
  1. package/DOCS.md +64 -63
  2. package/README.md +35 -36
  3. package/dist-cjs/index.d.ts +26 -27
  4. package/dist-cjs/index.js +1 -1
  5. package/dist-cjs/lib/ArraySet.js +47 -144
  6. package/dist-cjs/lib/ArraySet.js.map +2 -2
  7. package/dist-cjs/lib/Atom.js +12 -26
  8. package/dist-cjs/lib/Atom.js.map +2 -2
  9. package/dist-cjs/lib/Computed.js +36 -64
  10. package/dist-cjs/lib/Computed.js.map +2 -2
  11. package/dist-cjs/lib/EffectScheduler.js +1 -1
  12. package/dist-cjs/lib/EffectScheduler.js.map +2 -2
  13. package/dist-cjs/lib/HistoryBuffer.js +8 -8
  14. package/dist-cjs/lib/HistoryBuffer.js.map +2 -2
  15. package/dist-cjs/lib/capture.js +1 -3
  16. package/dist-cjs/lib/capture.js.map +2 -2
  17. package/dist-cjs/lib/constants.js.map +2 -2
  18. package/dist-cjs/lib/helpers.js +3 -11
  19. package/dist-cjs/lib/helpers.js.map +2 -2
  20. package/dist-cjs/lib/localStorageAtom.js +7 -2
  21. package/dist-cjs/lib/localStorageAtom.js.map +2 -2
  22. package/dist-cjs/lib/transactions.js +11 -19
  23. package/dist-cjs/lib/transactions.js.map +2 -2
  24. package/dist-cjs/lib/types.js.map +1 -1
  25. package/dist-cjs/lib/warnings.js +2 -4
  26. package/dist-cjs/lib/warnings.js.map +2 -2
  27. package/dist-esm/index.d.mts +26 -27
  28. package/dist-esm/index.mjs +1 -1
  29. package/dist-esm/lib/ArraySet.mjs +47 -144
  30. package/dist-esm/lib/ArraySet.mjs.map +2 -2
  31. package/dist-esm/lib/Atom.mjs +12 -26
  32. package/dist-esm/lib/Atom.mjs.map +2 -2
  33. package/dist-esm/lib/Computed.mjs +36 -64
  34. package/dist-esm/lib/Computed.mjs.map +2 -2
  35. package/dist-esm/lib/EffectScheduler.mjs +1 -1
  36. package/dist-esm/lib/EffectScheduler.mjs.map +2 -2
  37. package/dist-esm/lib/HistoryBuffer.mjs +8 -8
  38. package/dist-esm/lib/HistoryBuffer.mjs.map +2 -2
  39. package/dist-esm/lib/capture.mjs +1 -3
  40. package/dist-esm/lib/capture.mjs.map +2 -2
  41. package/dist-esm/lib/constants.mjs.map +2 -2
  42. package/dist-esm/lib/helpers.mjs +3 -11
  43. package/dist-esm/lib/helpers.mjs.map +2 -2
  44. package/dist-esm/lib/localStorageAtom.mjs +7 -2
  45. package/dist-esm/lib/localStorageAtom.mjs.map +2 -2
  46. package/dist-esm/lib/transactions.mjs +11 -19
  47. package/dist-esm/lib/transactions.mjs.map +2 -2
  48. package/dist-esm/lib/types.mjs.map +1 -1
  49. package/dist-esm/lib/warnings.mjs +2 -4
  50. package/dist-esm/lib/warnings.mjs.map +2 -2
  51. package/package.json +2 -2
  52. package/src/lib/ArraySet.ts +68 -176
  53. package/src/lib/Atom.ts +27 -31
  54. package/src/lib/Computed.ts +68 -96
  55. package/src/lib/EffectScheduler.ts +9 -8
  56. package/src/lib/HistoryBuffer.ts +12 -10
  57. package/src/lib/__tests__/ArraySet.test.ts +39 -13
  58. package/src/lib/__tests__/EffectScheduler.test.ts +18 -0
  59. package/src/lib/__tests__/HistoryBuffer.test.ts +6 -3
  60. package/src/lib/__tests__/computed.test.ts +75 -0
  61. package/src/lib/__tests__/errors.test.ts +24 -0
  62. package/src/lib/__tests__/helpers.test.ts +7 -11
  63. package/src/lib/__tests__/history.test.ts +32 -2
  64. package/src/lib/__tests__/localStorageAtom.test.ts +15 -0
  65. package/src/lib/capture.ts +13 -13
  66. package/src/lib/constants.ts +3 -22
  67. package/src/lib/helpers.ts +15 -140
  68. package/src/lib/localStorageAtom.ts +9 -2
  69. package/src/lib/transactions.ts +23 -47
  70. package/src/lib/types.ts +7 -7
  71. package/src/lib/warnings.ts +2 -10
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/Computed.ts"],
4
- "sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { assert } from '@tldraw/utils'\nimport { ArraySet } from './ArraySet'\nimport { maybeCaptureParent, startCapturingParents, stopCapturingParents } from './capture'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { EMPTY_ARRAY, equals, haveParentsChanged, singleton } from './helpers'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { getGlobalEpoch, getIsReacting, getReactionEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\nimport { logComputedGetterWarning } from './warnings'\n\n/**\n * A special symbol used to indicate that a computed signal has not been initialized yet.\n * This is passed as the `previousValue` parameter to a computed signal function on its first run.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * if (isUninitialized(prevValue)) {\n * console.log('First computation!')\n * }\n * return count.get() * 2\n * })\n * ```\n *\n * @public\n */\nexport const UNINITIALIZED = Symbol.for('com.tldraw.state/UNINITIALIZED')\n/**\n * The type of the first value passed to a computed signal function as the 'prevValue' parameter.\n * This type represents the uninitialized state of a computed signal before its first calculation.\n *\n * @see {@link isUninitialized}\n * @public\n */\nexport type UNINITIALIZED = typeof UNINITIALIZED\n\n/**\n * Call this inside a computed signal function to determine whether it is the first time the function is being called.\n *\n * Mainly useful for incremental signal computation.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * if (isUninitialized(prevValue)) {\n * print('First time!')\n * }\n * return count.get() * 2\n * })\n * ```\n *\n * @param value - The value to check.\n * @public\n */\nexport function isUninitialized(value: any): value is UNINITIALIZED {\n\treturn value === UNINITIALIZED\n}\n\n/**\n * A singleton class used to wrap computed signal values along with their diffs.\n * This class is used internally by the {@link withDiff} function to provide both\n * the computed value and its diff to the signal system.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * const nextValue = count.get() * 2\n * if (isUninitialized(prevValue)) {\n * return nextValue\n * }\n * return withDiff(nextValue, nextValue - prevValue)\n * })\n * ```\n *\n * @public\n */\nexport const WithDiff = singleton(\n\t'WithDiff',\n\t() =>\n\t\tclass WithDiff<Value, Diff> {\n\t\t\tconstructor(\n\t\t\t\tpublic value: Value,\n\t\t\t\tpublic diff: Diff\n\t\t\t) {}\n\t\t}\n)\n\n/**\n * Interface representing a value wrapped with its corresponding diff.\n * Used in incremental computation to provide both the new value and the diff from the previous value.\n *\n * @public\n */\nexport interface WithDiff<Value, Diff> {\n\t/**\n\t * The computed value.\n\t */\n\tvalue: Value\n\t/**\n\t * The diff between the previous and current value.\n\t */\n\tdiff: Diff\n}\n\n/**\n * When writing incrementally-computed signals it is convenient (and usually more performant) to incrementally compute the diff too.\n *\n * 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}.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * const nextValue = count.get() * 2\n * if (isUninitialized(prevValue)) {\n * return nextValue\n * }\n * return withDiff(nextValue, nextValue - prevValue)\n * }, { historyLength: 10 })\n * ```\n *\n *\n * @param value - The value.\n * @param diff - The diff.\n * @public\n */\nexport function withDiff<Value, Diff>(value: Value, diff: Diff): WithDiff<Value, Diff> {\n\treturn new WithDiff(value, diff)\n}\n\n/**\n * Options for configuring computed signals. Used when calling `computed` or using the `@computed` decorator.\n *\n * @example\n * ```ts\n * const greeting = computed('greeting', () => `Hello ${name.get()}!`, {\n * historyLength: 10,\n * isEqual: (a, b) => a === b,\n * computeDiff: (oldVal, newVal) => ({ type: 'change', from: oldVal, to: newVal })\n * })\n * ```\n *\n * @public\n */\nexport interface ComputedOptions<Value, Diff> {\n\t/**\n\t * The maximum number of diffs to keep in the history buffer.\n\t *\n\t * 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.\n\t *\n\t * 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).\n\t *\n\t * Otherwise, set this to a higher number based on your usage pattern and memory constraints.\n\t *\n\t */\n\thistoryLength?: number\n\t/**\n\t * 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}.\n\t */\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\t/**\n\t * If provided, this will be used to compare the old and new values of the computed to determine if the value has changed.\n\t * 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.\n\t * @param a - The old value\n\t * @param b - The new value\n\t * @returns True if the values are equal, false otherwise.\n\t */\n\tisEqual?(a: any, b: any): boolean\n}\n\n/**\n * A computed signal created via the `computed` function or `@computed` decorator.\n * Computed signals derive their values from other signals and automatically update when their dependencies change.\n * They use lazy evaluation, only recalculating when accessed and dependencies have changed.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n * const fullName = computed('fullName', () => `${firstName.get()} ${lastName.get()}`)\n *\n * console.log(fullName.get()) // \"John Doe\"\n * firstName.set('Jane')\n * console.log(fullName.get()) // \"Jane Doe\"\n * ```\n *\n * @public\n */\nexport interface Computed<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Whether this computed signal is involved in an actively-running effect graph.\n\t * Returns true if there are any reactions or other computed signals depending on this one.\n\t * @public\n\t */\n\treadonly isActivelyListening: boolean\n\n\t/** @internal */\n\treadonly parentSet: ArraySet<Signal<any, any>>\n\t/** @internal */\n\treadonly parents: Signal<any, any>[]\n\t/** @internal */\n\treadonly parentEpochs: number[]\n}\n\n/**\n * @internal\n */\nclass __UNSAFE__Computed<Value, Diff = unknown> implements Computed<Value, Diff> {\n\treadonly __isComputed = true as const\n\tlastChangedEpoch = GLOBAL_START_EPOCH\n\tlastTraversedEpoch = GLOBAL_START_EPOCH\n\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null = null\n\n\t/**\n\t * The epoch when the reactor was last checked.\n\t */\n\tprivate lastCheckedEpoch = GLOBAL_START_EPOCH\n\n\tparentSet = new ArraySet<Signal<any, any>>()\n\tparents: Signal<any, any>[] = []\n\tparentEpochs: number[] = []\n\n\tchildren = new ArraySet<Child>()\n\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget isActivelyListening(): boolean {\n\t\treturn !this.children.isEmpty\n\t}\n\n\thistoryBuffer?: HistoryBuffer<Diff>\n\n\t// The last-computed value of this signal.\n\tprivate state: Value = UNINITIALIZED as unknown as Value\n\t// If the signal throws an error we stash it so we can rethrow it on the next get()\n\tprivate error: null | { thrownValue: any } = null\n\n\tprivate computeDiff?: ComputeDiff<Value, Diff>\n\n\tprivate readonly isEqual: (a: any, b: any) => boolean\n\n\tconstructor(\n\t\t/**\n\t\t * The name of the signal. This is used for debugging and performance profiling purposes. It does not need to be globally unique.\n\t\t */\n\t\tpublic readonly name: string,\n\t\t/**\n\t\t * The function that computes the value of the signal.\n\t\t */\n\t\tprivate readonly derive: (\n\t\t\tpreviousValue: Value | UNINITIALIZED,\n\t\t\tlastComputedEpoch: number\n\t\t) => Value | WithDiff<Value, Diff>,\n\t\toptions?: ComputedOptions<Value, Diff>\n\t) {\n\t\tif (options?.historyLength) {\n\t\t\tthis.historyBuffer = new HistoryBuffer(options.historyLength)\n\t\t}\n\t\tthis.computeDiff = options?.computeDiff\n\t\tthis.isEqual = options?.isEqual ?? equals\n\t}\n\n\t__unsafe__getWithoutCapture(ignoreErrors?: boolean): Value {\n\t\tconst isNew = this.lastChangedEpoch === GLOBAL_START_EPOCH\n\n\t\tconst globalEpoch = getGlobalEpoch()\n\n\t\tif (\n\t\t\t!isNew &&\n\t\t\t(this.lastCheckedEpoch === globalEpoch ||\n\t\t\t\t(this.isActivelyListening &&\n\t\t\t\t\tgetIsReacting() &&\n\t\t\t\t\tthis.lastTraversedEpoch < getReactionEpoch()) ||\n\t\t\t\t!haveParentsChanged(this))\n\t\t) {\n\t\t\tthis.lastCheckedEpoch = globalEpoch\n\t\t\tif (this.error) {\n\t\t\t\tif (!ignoreErrors) {\n\t\t\t\t\tthrow this.error.thrownValue\n\t\t\t\t} else {\n\t\t\t\t\treturn this.state // will be UNINITIALIZED\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn this.state\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tstartCapturingParents(this)\n\t\t\tconst result = this.derive(this.state, this.lastCheckedEpoch)\n\t\t\tconst newState = result instanceof WithDiff ? result.value : result\n\t\t\tconst isUninitialized = this.state === UNINITIALIZED\n\t\t\tif (isUninitialized || !this.isEqual(this.state, newState)) {\n\t\t\t\tif (this.historyBuffer && !isUninitialized) {\n\t\t\t\t\tconst diff = result instanceof WithDiff ? result.diff : undefined\n\t\t\t\t\tthis.historyBuffer.pushEntry(\n\t\t\t\t\t\tthis.lastChangedEpoch,\n\t\t\t\t\t\tgetGlobalEpoch(),\n\t\t\t\t\t\tdiff ??\n\t\t\t\t\t\t\tthis.computeDiff?.(this.state, newState, this.lastCheckedEpoch, getGlobalEpoch()) ??\n\t\t\t\t\t\t\tRESET_VALUE\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthis.lastChangedEpoch = getGlobalEpoch()\n\t\t\t\tthis.state = newState\n\t\t\t}\n\t\t\tthis.error = null\n\t\t\tthis.lastCheckedEpoch = getGlobalEpoch()\n\n\t\t\treturn this.state\n\t\t} catch (e) {\n\t\t\t// if a derived value throws an error, we reset the state to UNINITIALIZED\n\t\t\tif (this.state !== UNINITIALIZED) {\n\t\t\t\tthis.state = UNINITIALIZED as unknown as Value\n\t\t\t\tthis.lastChangedEpoch = getGlobalEpoch()\n\t\t\t}\n\t\t\tthis.lastCheckedEpoch = getGlobalEpoch()\n\t\t\t// we also clear the history buffer if an error was thrown\n\t\t\tif (this.historyBuffer) {\n\t\t\t\tthis.historyBuffer.clear()\n\t\t\t}\n\t\t\tthis.error = { thrownValue: e }\n\t\t\t// we don't wish to propagate errors when derefed via haveParentsChanged()\n\t\t\tif (!ignoreErrors) throw e\n\t\t\treturn this.state\n\t\t} finally {\n\t\t\tstopCapturingParents()\n\t\t}\n\t}\n\n\tget(): Value {\n\t\ttry {\n\t\t\treturn this.__unsafe__getWithoutCapture()\n\t\t} finally {\n\t\t\t// if the deriver throws an error we still need to capture\n\t\t\tmaybeCaptureParent(this)\n\t\t}\n\t}\n\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[] {\n\t\t// we can ignore any errors thrown during derive\n\t\tthis.__unsafe__getWithoutCapture(true)\n\t\t// and we still need to capture this signal as a parent\n\t\tmaybeCaptureParent(this)\n\n\t\tif (epoch >= this.lastChangedEpoch) {\n\t\t\treturn EMPTY_ARRAY\n\t\t}\n\n\t\treturn this.historyBuffer?.getChangesSince(epoch) ?? RESET_VALUE\n\t}\n}\n\n/**\n * Singleton reference to the computed signal implementation class.\n * Used internally by the library to create computed signal instances.\n *\n * @internal\n */\nexport const _Computed = singleton('Computed', () => __UNSAFE__Computed)\n\n/**\n * Type alias for the computed signal implementation class.\n *\n * @internal\n */\nexport type _Computed = InstanceType<typeof __UNSAFE__Computed>\n\nfunction computedMethodLegacyDecorator(\n\toptions: ComputedOptions<any, any> = {},\n\t_target: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n) {\n\tconst originalMethod = descriptor.value\n\tconst derivationKey = Symbol.for('__@tldraw/state__computed__' + key)\n\n\tdescriptor.value = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(key, originalMethod!.bind(this) as any, options)\n\t\t\tObject.defineProperty(this, derivationKey, {\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: false,\n\t\t\t\twritable: false,\n\t\t\t\tvalue: d,\n\t\t\t})\n\t\t}\n\t\treturn d.get()\n\t}\n\tdescriptor.value[isComputedMethodKey] = true\n\n\treturn descriptor\n}\n\nfunction computedGetterLegacyDecorator(\n\toptions: ComputedOptions<any, any> = {},\n\t_target: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n) {\n\tconst originalMethod = descriptor.get\n\tconst derivationKey = Symbol.for('__@tldraw/state__computed__' + key)\n\n\tdescriptor.get = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(key, originalMethod!.bind(this) as any, options)\n\t\t\tObject.defineProperty(this, derivationKey, {\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: false,\n\t\t\t\twritable: false,\n\t\t\t\tvalue: d,\n\t\t\t})\n\t\t}\n\t\treturn d.get()\n\t}\n\n\treturn descriptor\n}\n\nfunction computedMethodTc39Decorator<This extends object, Value>(\n\toptions: ComputedOptions<Value, any>,\n\tcompute: () => Value,\n\tcontext: ClassMethodDecoratorContext<This, () => Value>\n) {\n\tassert(context.kind === 'method', '@computed can only be used on methods')\n\tconst derivationKey = Symbol.for('__@tldraw/state__computed__' + String(context.name))\n\n\tconst fn = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(String(context.name), compute.bind(this) as any, options)\n\t\t\tObject.defineProperty(this, derivationKey, {\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: false,\n\t\t\t\twritable: false,\n\t\t\t\tvalue: d,\n\t\t\t})\n\t\t}\n\t\treturn d.get()\n\t}\n\tfn[isComputedMethodKey] = true\n\treturn fn\n}\n\nfunction computedDecorator(\n\toptions: ComputedOptions<any, any> = {},\n\targs:\n\t\t| [target: any, key: string, descriptor: PropertyDescriptor]\n\t\t| [originalMethod: () => any, context: ClassMethodDecoratorContext]\n) {\n\tif (args.length === 2) {\n\t\tconst [originalMethod, context] = args\n\t\treturn computedMethodTc39Decorator(options, originalMethod, context)\n\t} else {\n\t\tconst [_target, key, descriptor] = args\n\t\tif (descriptor.get) {\n\t\t\tlogComputedGetterWarning()\n\t\t\treturn computedGetterLegacyDecorator(options, _target, key, descriptor)\n\t\t} else {\n\t\t\treturn computedMethodLegacyDecorator(options, _target, key, descriptor)\n\t\t}\n\t}\n}\n\nconst isComputedMethodKey = '@@__isComputedMethod__@@'\n\n/**\n * Retrieves the underlying computed instance for a given property created with the `computed`\n * decorator.\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom(0)\n *\n * @computed getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n *\n * const c = new Counter()\n * const remaining = getComputedInstance(c, 'getRemaining')\n * remaining.get() === 100 // true\n * c.count.set(13)\n * remaining.get() === 87 // true\n * ```\n *\n * @param obj - The object\n * @param propertyName - The property name\n * @public\n */\nexport function getComputedInstance<Obj extends object, Prop extends keyof Obj>(\n\tobj: Obj,\n\tpropertyName: Prop\n): Computed<Obj[Prop]> {\n\tconst key = Symbol.for('__@tldraw/state__computed__' + propertyName.toString())\n\tlet inst = obj[key as keyof typeof obj] as Computed<Obj[Prop]> | undefined\n\tif (!inst) {\n\t\t// deref to make sure it exists first\n\t\tconst val = obj[propertyName]\n\t\tif (typeof val === 'function' && (val as any)[isComputedMethodKey]) {\n\t\t\tval.call(obj)\n\t\t}\n\n\t\tinst = obj[key as keyof typeof obj] as Computed<Obj[Prop]> | undefined\n\t}\n\treturn inst as any\n}\n\n/**\n * Creates a computed signal that derives its value from other signals.\n * Computed signals automatically update when their dependencies change and use lazy evaluation\n * for optimal performance.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n * const greeting = computed('greeting', () => `Hello ${name.get()}!`)\n * console.log(greeting.get()) // 'Hello John!'\n * ```\n *\n * `computed` may also be used as a decorator for creating computed getter methods.\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom<number>(0)\n *\n * @computed getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n * ```\n *\n * You may optionally pass in a {@link ComputedOptions} when used as a decorator:\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom<number>(0)\n *\n * @computed({isEqual: (a, b) => a === b})\n * getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n * ```\n *\n * @param name - The name of the signal for debugging purposes\n * @param compute - The function that computes the value of the signal. Receives the previous value and last computed epoch\n * @param options - Optional configuration for the computed signal\n * @returns A new computed signal\n * @public\n */\nexport function computed<Value, Diff = unknown>(\n\tname: string,\n\tcompute: (\n\t\tpreviousValue: Value | typeof UNINITIALIZED,\n\t\tlastComputedEpoch: number\n\t) => Value | WithDiff<Value, Diff>,\n\toptions?: ComputedOptions<Value, Diff>\n): Computed<Value, Diff>\n/**\n * TC39 decorator for creating computed methods in classes.\n *\n * @example\n * ```ts\n * class MyClass {\n * value = atom('value', 10)\n *\n * @computed\n * doubled() {\n * return this.value.get() * 2\n * }\n * }\n * ```\n *\n * @param compute - The method to be decorated\n * @param context - The decorator context provided by TypeScript\n * @returns The decorated method\n * @public\n */\nexport function computed<This extends object, Value>(\n\tcompute: () => Value,\n\tcontext: ClassMethodDecoratorContext<This, () => Value>\n): () => Value\n/**\n * Legacy TypeScript decorator for creating computed methods in classes.\n *\n * @example\n * ```ts\n * class MyClass {\n * value = atom('value', 10)\n *\n * @computed\n * doubled() {\n * return this.value.get() * 2\n * }\n * }\n * ```\n *\n * @param target - The class prototype\n * @param key - The property key\n * @param descriptor - The property descriptor\n * @returns The modified property descriptor\n * @public\n */\nexport function computed(\n\ttarget: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n): PropertyDescriptor\n/**\n * Decorator factory for creating computed methods with options.\n *\n * @example\n * ```ts\n * class MyClass {\n * items = atom('items', [1, 2, 3])\n *\n * @computed({ historyLength: 10 })\n * sum() {\n * return this.items.get().reduce((a, b) => a + b, 0)\n * }\n * }\n * ```\n *\n * @param options - Configuration options for the computed signal\n * @returns A decorator function that can be applied to methods\n * @public\n */\nexport function computed<Value, Diff = unknown>(\n\toptions?: ComputedOptions<Value, Diff>\n): ((target: any, key: string, descriptor: PropertyDescriptor) => PropertyDescriptor) &\n\t(<This>(\n\t\tcompute: () => Value,\n\t\tcontext: ClassMethodDecoratorContext<This, () => Value>\n\t) => () => Value)\n\n/**\n * Implementation function that handles all computed signal creation and decoration scenarios.\n * This function is overloaded to support multiple usage patterns:\n * - Creating computed signals directly\n * - Using as a TC39 decorator\n * - Using as a legacy decorator\n * - Using as a decorator factory with options\n *\n * @returns Either a computed signal instance or a decorator function depending on usage\n * @public\n */\nexport function computed() {\n\tif (arguments.length === 1) {\n\t\tconst options = arguments[0]\n\t\treturn (...args: any) => computedDecorator(options, args)\n\t} else if (typeof arguments[0] === 'string') {\n\t\treturn new _Computed(arguments[0], arguments[1], arguments[2])\n\t} else {\n\t\treturn computedDecorator(undefined, arguments as any)\n\t}\n}\n\nimport { isComputed as _isComputed } from './isComputed'\n\n/**\n * Returns true if the given value is a computed signal.\n * @public\n */\nexport function isComputed(value: any): value is Computed<any> {\n\treturn _isComputed(value)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* eslint-disable prefer-rest-params */\nimport { assert } from '@tldraw/utils'\nimport { ArraySet } from './ArraySet'\nimport { maybeCaptureParent, startCapturingParents, stopCapturingParents } from './capture'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { EMPTY_ARRAY, equals, haveParentsChanged, singleton } from './helpers'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { getGlobalEpoch, getIsReacting, getReactionEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\nimport { logComputedGetterWarning } from './warnings'\n\n/**\n * A special symbol used to indicate that a computed signal has not been initialized yet.\n * This is passed as the `previousValue` parameter to a computed signal function on its first run.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * if (isUninitialized(prevValue)) {\n * console.log('First computation!')\n * }\n * return count.get() * 2\n * })\n * ```\n *\n * @public\n */\nexport const UNINITIALIZED = Symbol.for('com.tldraw.state/UNINITIALIZED')\n/**\n * The type of the first value passed to a computed signal function as the 'prevValue' parameter.\n * This type represents the uninitialized state of a computed signal before its first calculation.\n *\n * @see {@link isUninitialized}\n * @public\n */\nexport type UNINITIALIZED = typeof UNINITIALIZED\n\n/**\n * Call this inside a computed signal function to determine whether it is the first time the function is being called.\n *\n * Mainly useful for incremental signal computation.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * if (isUninitialized(prevValue)) {\n * print('First time!')\n * }\n * return count.get() * 2\n * })\n * ```\n *\n * @param value - The value to check.\n * @public\n */\nexport function isUninitialized(value: any): value is UNINITIALIZED {\n\treturn value === UNINITIALIZED\n}\n\n/**\n * A singleton class used to wrap computed signal values along with their diffs.\n * This class is used internally by the {@link withDiff} function to provide both\n * the computed value and its diff to the signal system.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * const nextValue = count.get() * 2\n * if (isUninitialized(prevValue)) {\n * return nextValue\n * }\n * return withDiff(nextValue, nextValue - prevValue)\n * })\n * ```\n *\n * @public\n */\nexport const WithDiff = singleton(\n\t'WithDiff',\n\t() =>\n\t\tclass WithDiff<Value, Diff> {\n\t\t\tconstructor(\n\t\t\t\tpublic value: Value,\n\t\t\t\tpublic diff: Diff\n\t\t\t) {}\n\t\t}\n)\n\n/**\n * Interface representing a value wrapped with its corresponding diff.\n * Used in incremental computation to provide both the new value and the diff from the previous value.\n *\n * @public\n */\nexport interface WithDiff<Value, Diff> {\n\t/**\n\t * The computed value.\n\t */\n\tvalue: Value\n\t/**\n\t * The diff between the previous and current value.\n\t */\n\tdiff: Diff\n}\n\n/**\n * When writing incrementally-computed signals it is convenient (and usually more performant) to incrementally compute the diff too.\n *\n * 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}.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const double = computed('double', (prevValue) => {\n * const nextValue = count.get() * 2\n * if (isUninitialized(prevValue)) {\n * return nextValue\n * }\n * return withDiff(nextValue, nextValue - prevValue)\n * }, { historyLength: 10 })\n * ```\n *\n *\n * @param value - The value.\n * @param diff - The diff.\n * @public\n */\nexport function withDiff<Value, Diff>(value: Value, diff: Diff): WithDiff<Value, Diff> {\n\treturn new WithDiff(value, diff)\n}\n\n/**\n * Options for configuring computed signals. Used when calling `computed` or using the `@computed` decorator.\n *\n * @example\n * ```ts\n * const greeting = computed('greeting', () => `Hello ${name.get()}!`, {\n * historyLength: 10,\n * isEqual: (a, b) => a === b,\n * computeDiff: (oldVal, newVal) => ({ type: 'change', from: oldVal, to: newVal })\n * })\n * ```\n *\n * @public\n */\nexport interface ComputedOptions<Value, Diff> {\n\t/**\n\t * The maximum number of diffs to keep in the history buffer.\n\t *\n\t * If you don't need diffs, leave this as `undefined` and no history buffer will be created. Diffs supplied via {@link withDiff} or {@link ComputedOptions.computeDiff} are only recorded when this is set.\n\t *\n\t * 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).\n\t *\n\t * Otherwise, set this to a higher number based on your usage pattern and memory constraints.\n\t *\n\t */\n\thistoryLength?: number\n\t/**\n\t * 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}.\n\t */\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\t/**\n\t * If provided, this will be used to compare the old and new values of the computed to determine if the value has changed.\n\t * 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.\n\t * @param a - The old value\n\t * @param b - The new value\n\t * @returns True if the values are equal, false otherwise.\n\t */\n\tisEqual?(a: any, b: any): boolean\n}\n\n/**\n * A computed signal created via the `computed` function or `@computed` decorator.\n * Computed signals derive their values from other signals and automatically update when their dependencies change.\n * They use lazy evaluation, only recalculating when accessed and dependencies have changed.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n * const fullName = computed('fullName', () => `${firstName.get()} ${lastName.get()}`)\n *\n * console.log(fullName.get()) // \"John Doe\"\n * firstName.set('Jane')\n * console.log(fullName.get()) // \"Jane Doe\"\n * ```\n *\n * @public\n */\nexport interface Computed<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Whether this computed signal is involved in an actively-running effect graph.\n\t * Returns true if there are any reactions or other computed signals depending on this one.\n\t * @public\n\t */\n\treadonly isActivelyListening: boolean\n\n\t/** @internal */\n\treadonly parentSet: ArraySet<Signal<any, any>>\n\t/** @internal */\n\treadonly parents: Signal<any, any>[]\n\t/** @internal */\n\treadonly parentEpochs: number[]\n}\n\n/**\n * @internal\n */\nclass __UNSAFE__Computed<Value, Diff = unknown> implements Computed<Value, Diff> {\n\treadonly __isComputed = true as const\n\tlastChangedEpoch = GLOBAL_START_EPOCH\n\tlastTraversedEpoch = GLOBAL_START_EPOCH\n\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null = null\n\n\t/**\n\t * The epoch when the reactor was last checked.\n\t */\n\tprivate lastCheckedEpoch = GLOBAL_START_EPOCH\n\n\tparentSet = new ArraySet<Signal<any, any>>()\n\tparents: Signal<any, any>[] = []\n\tparentEpochs: number[] = []\n\n\tchildren = new ArraySet<Child>()\n\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget isActivelyListening(): boolean {\n\t\treturn !this.children.isEmpty\n\t}\n\n\thistoryBuffer?: HistoryBuffer<Diff>\n\n\t// The last-computed value of this signal.\n\tprivate state: Value = UNINITIALIZED as unknown as Value\n\t// If the signal throws an error we stash it so we can rethrow it on the next get()\n\tprivate error: null | { thrownValue: any } = null\n\n\tprivate computeDiff?: ComputeDiff<Value, Diff>\n\n\tprivate readonly isEqual: (a: any, b: any) => boolean\n\n\tconstructor(\n\t\t/**\n\t\t * The name of the signal. This is used for debugging and performance profiling purposes. It does not need to be globally unique.\n\t\t */\n\t\tpublic readonly name: string,\n\t\t/**\n\t\t * The function that computes the value of the signal.\n\t\t */\n\t\tprivate readonly derive: (\n\t\t\tpreviousValue: Value | UNINITIALIZED,\n\t\t\tlastComputedEpoch: number\n\t\t) => Value | WithDiff<Value, Diff>,\n\t\toptions?: ComputedOptions<Value, Diff>\n\t) {\n\t\tif (options?.historyLength) {\n\t\t\tthis.historyBuffer = new HistoryBuffer(options.historyLength)\n\t\t}\n\t\tthis.computeDiff = options?.computeDiff\n\t\tthis.isEqual = options?.isEqual ?? equals\n\t}\n\n\t__unsafe__getWithoutCapture(ignoreErrors?: boolean): Value {\n\t\tconst isNew = this.lastChangedEpoch === GLOBAL_START_EPOCH\n\n\t\tconst globalEpoch = getGlobalEpoch()\n\n\t\tif (\n\t\t\t!isNew &&\n\t\t\t(this.lastCheckedEpoch === globalEpoch ||\n\t\t\t\t(this.isActivelyListening &&\n\t\t\t\t\tgetIsReacting() &&\n\t\t\t\t\tthis.lastTraversedEpoch < getReactionEpoch()) ||\n\t\t\t\t!haveParentsChanged(this))\n\t\t) {\n\t\t\tthis.lastCheckedEpoch = globalEpoch\n\t\t\tif (this.error) {\n\t\t\t\tif (!ignoreErrors) {\n\t\t\t\t\tthrow this.error.thrownValue\n\t\t\t\t} else {\n\t\t\t\t\treturn this.state // will be UNINITIALIZED\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn this.state\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tstartCapturingParents(this)\n\t\t\tconst result = this.derive(this.state, this.lastCheckedEpoch)\n\t\t\tconst newState = result instanceof WithDiff ? result.value : result\n\t\t\tconst isUninitialized = this.state === UNINITIALIZED\n\t\t\t// `derive` may have advanced the epoch, so re-read it here, but only once \u2014 the whole\n\t\t\t// commit below belongs to a single epoch.\n\t\t\tconst epoch = getGlobalEpoch()\n\t\t\tif (isUninitialized || !this.isEqual(this.state, newState)) {\n\t\t\t\tif (this.historyBuffer && !isUninitialized) {\n\t\t\t\t\t// Only `undefined` means \"no diff supplied\"; `null` can be a legitimate diff.\n\t\t\t\t\tconst diff = result instanceof WithDiff ? result.diff : undefined\n\t\t\t\t\tthis.historyBuffer.pushEntry(\n\t\t\t\t\t\tthis.lastChangedEpoch,\n\t\t\t\t\t\tepoch,\n\t\t\t\t\t\tdiff !== undefined\n\t\t\t\t\t\t\t? diff\n\t\t\t\t\t\t\t: this.computeDiff\n\t\t\t\t\t\t\t\t? this.computeDiff(this.state, newState, this.lastCheckedEpoch, epoch)\n\t\t\t\t\t\t\t\t: RESET_VALUE\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthis.lastChangedEpoch = epoch\n\t\t\t\tthis.state = newState\n\t\t\t}\n\t\t\tthis.error = null\n\t\t\tthis.lastCheckedEpoch = epoch\n\n\t\t\treturn this.state\n\t\t} catch (e) {\n\t\t\t// if a derived value throws an error, we reset the state to UNINITIALIZED\n\t\t\tconst epoch = getGlobalEpoch()\n\t\t\t// Entering the error state (from a value, or from never having computed) is a change;\n\t\t\t// throwing again while already in it is not. Checking `error` rather than `state` matters\n\t\t\t// for a first run that throws: `state` is already UNINITIALIZED then, and leaving\n\t\t\t// `lastChangedEpoch` at GLOBAL_START_EPOCH would keep `isNew` true and re-run `derive` on\n\t\t\t// every read instead of rethrowing the cached error.\n\t\t\tif (this.error === null) {\n\t\t\t\tthis.state = UNINITIALIZED as unknown as Value\n\t\t\t\tthis.lastChangedEpoch = epoch\n\t\t\t}\n\t\t\tthis.lastCheckedEpoch = epoch\n\t\t\t// we also clear the history buffer if an error was thrown\n\t\t\tif (this.historyBuffer) {\n\t\t\t\tthis.historyBuffer.clear()\n\t\t\t}\n\t\t\tthis.error = { thrownValue: e }\n\t\t\t// we don't wish to propagate errors when derefed via haveParentsChanged()\n\t\t\tif (!ignoreErrors) throw e\n\t\t\treturn this.state\n\t\t} finally {\n\t\t\tstopCapturingParents()\n\t\t}\n\t}\n\n\tget(): Value {\n\t\ttry {\n\t\t\treturn this.__unsafe__getWithoutCapture()\n\t\t} finally {\n\t\t\t// if the deriver throws an error we still need to capture\n\t\t\tmaybeCaptureParent(this)\n\t\t}\n\t}\n\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[] {\n\t\t// we can ignore any errors thrown during derive\n\t\tthis.__unsafe__getWithoutCapture(true)\n\t\t// and we still need to capture this signal as a parent\n\t\tmaybeCaptureParent(this)\n\n\t\tif (epoch >= this.lastChangedEpoch) {\n\t\t\treturn EMPTY_ARRAY\n\t\t}\n\n\t\treturn this.historyBuffer?.getChangesSince(epoch) ?? RESET_VALUE\n\t}\n}\n\n/**\n * Singleton reference to the computed signal implementation class.\n * Used internally by the library to create computed signal instances.\n *\n * @internal\n */\nexport const _Computed = singleton('Computed', () => __UNSAFE__Computed)\n\n/**\n * Type alias for the computed signal implementation class.\n *\n * @internal\n */\nexport type _Computed = InstanceType<typeof __UNSAFE__Computed>\n\n// Set on every decorated wrapper: the symbol its per-instance computed is stored under.\nconst derivationKeyKey = '@@__computedDerivationKey__@@'\n\n/**\n * Builds the method (or getter) body that `@computed` installs: a per-instance, lazily-created\n * computed over the original method. The storage symbol is unique per decoration rather than\n * `Symbol.for(name)` so that a subclass which overrides a `@computed` method and calls\n * `super.method()` reaches the superclass's computed instead of re-entering its own.\n */\nfunction makeComputedWrapper(name: string, compute: () => any, options: ComputedOptions<any, any>) {\n\tconst derivationKey = Symbol('__@tldraw/state__computed__' + name)\n\n\tconst wrapper = function (this: any) {\n\t\tlet d = this[derivationKey] as Computed<any> | undefined\n\n\t\tif (!d) {\n\t\t\td = new _Computed(name, compute.bind(this) as any, options)\n\t\t\tObject.defineProperty(this, derivationKey, {\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: false,\n\t\t\t\twritable: false,\n\t\t\t\tvalue: d,\n\t\t\t})\n\t\t}\n\t\treturn d.get()\n\t} as any\n\twrapper[derivationKeyKey] = derivationKey\n\treturn wrapper\n}\n\nfunction computedDecorator(\n\toptions: ComputedOptions<any, any> = {},\n\targs:\n\t\t| [target: any, key: string, descriptor: PropertyDescriptor]\n\t\t| [originalMethod: () => any, context: ClassMethodDecoratorContext]\n) {\n\tif (args.length === 2) {\n\t\tconst [originalMethod, context] = args\n\t\tassert(context.kind === 'method', '@computed can only be used on methods')\n\t\treturn makeComputedWrapper(String(context.name), originalMethod, options)\n\t} else {\n\t\tconst [_target, key, descriptor] = args\n\t\tif (descriptor.get) {\n\t\t\tlogComputedGetterWarning()\n\t\t\tdescriptor.get = makeComputedWrapper(key, descriptor.get, options)\n\t\t} else {\n\t\t\tdescriptor.value = makeComputedWrapper(key, descriptor.value, options)\n\t\t}\n\t\treturn descriptor\n\t}\n}\n\n/**\n * Retrieves the underlying computed instance for a given property created with the `computed`\n * decorator.\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom('count', 0)\n *\n * @computed getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n *\n * const c = new Counter()\n * const remaining = getComputedInstance(c, 'getRemaining')\n * remaining.get() === 100 // true\n * c.count.set(13)\n * remaining.get() === 87 // true\n * ```\n *\n * @param obj - The object\n * @param propertyName - The property name\n * @public\n */\nexport function getComputedInstance<Obj extends object, Prop extends keyof Obj>(\n\tobj: Obj,\n\tpropertyName: Prop\n): Computed<Obj[Prop] extends () => infer Value ? Value : Obj[Prop]> {\n\t// The nearest decorated wrapper (a method, or a legacy getter) on the prototype chain knows\n\t// which symbol the instance's computed is stored under.\n\tfor (let proto: any = obj; proto; proto = Object.getPrototypeOf(proto)) {\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(proto, propertyName)\n\t\tconst wrapper = descriptor?.get ?? descriptor?.value\n\t\tconst key = wrapper?.[derivationKeyKey]\n\t\tif (!key) continue\n\n\t\tlet inst = (obj as any)[key]\n\t\tif (!inst) {\n\t\t\t// calling the wrapper creates the computed (before it first derives, so a throwing derive\n\t\t\t// still leaves the instance behind)\n\t\t\ttry {\n\t\t\t\twrapper.call(obj)\n\t\t\t} catch {\n\t\t\t\t// the caller asked for the instance, not its value\n\t\t\t}\n\t\t\tinst = (obj as any)[key]\n\t\t}\n\t\treturn inst\n\t}\n\treturn undefined as any\n}\n\n/**\n * Creates a computed signal that derives its value from other signals.\n * Computed signals automatically update when their dependencies change and use lazy evaluation\n * for optimal performance.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n * const greeting = computed('greeting', () => `Hello ${name.get()}!`)\n * console.log(greeting.get()) // 'Hello John!'\n * ```\n *\n * `computed` may also be used as a decorator for creating computed getter methods.\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom('count', 0)\n *\n * @computed getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n * ```\n *\n * You may optionally pass in a {@link ComputedOptions} when used as a decorator:\n *\n * @example\n * ```ts\n * class Counter {\n * max = 100\n * count = atom('count', 0)\n *\n * @computed({isEqual: (a, b) => a === b})\n * getRemaining() {\n * return this.max - this.count.get()\n * }\n * }\n * ```\n *\n * @param name - The name of the signal for debugging purposes\n * @param compute - The function that computes the value of the signal. Receives the previous value and last computed epoch\n * @param options - Optional configuration for the computed signal\n * @returns A new computed signal\n * @public\n */\nexport function computed<Value, Diff = unknown>(\n\tname: string,\n\tcompute: (\n\t\tpreviousValue: Value | typeof UNINITIALIZED,\n\t\tlastComputedEpoch: number\n\t) => Value | WithDiff<Value, Diff>,\n\toptions?: ComputedOptions<Value, Diff>\n): Computed<Value, Diff>\n/**\n * TC39 decorator for creating computed methods in classes.\n *\n * @example\n * ```ts\n * class MyClass {\n * value = atom('value', 10)\n *\n * @computed\n * doubled() {\n * return this.value.get() * 2\n * }\n * }\n * ```\n *\n * @param compute - The method to be decorated\n * @param context - The decorator context provided by TypeScript\n * @returns The decorated method\n * @public\n */\nexport function computed<This extends object, Value>(\n\tcompute: () => Value,\n\tcontext: ClassMethodDecoratorContext<This, () => Value>\n): () => Value\n/**\n * Legacy TypeScript decorator for creating computed methods in classes.\n *\n * @example\n * ```ts\n * class MyClass {\n * value = atom('value', 10)\n *\n * @computed\n * doubled() {\n * return this.value.get() * 2\n * }\n * }\n * ```\n *\n * @param target - The class prototype\n * @param key - The property key\n * @param descriptor - The property descriptor\n * @returns The modified property descriptor\n * @public\n */\nexport function computed(\n\ttarget: any,\n\tkey: string,\n\tdescriptor: PropertyDescriptor\n): PropertyDescriptor\n/**\n * Decorator factory for creating computed methods with options.\n *\n * @example\n * ```ts\n * class MyClass {\n * items = atom('items', [1, 2, 3])\n *\n * @computed({ historyLength: 10 })\n * sum() {\n * return this.items.get().reduce((a, b) => a + b, 0)\n * }\n * }\n * ```\n *\n * @param options - Configuration options for the computed signal\n * @returns A decorator function that can be applied to methods\n * @public\n */\nexport function computed<Value, Diff = unknown>(\n\toptions?: ComputedOptions<Value, Diff>\n): ((target: any, key: string, descriptor: PropertyDescriptor) => PropertyDescriptor) &\n\t(<This>(\n\t\tcompute: () => Value,\n\t\tcontext: ClassMethodDecoratorContext<This, () => Value>\n\t) => () => Value)\n\n/**\n * Implementation function that handles all computed signal creation and decoration scenarios.\n * This function is overloaded to support multiple usage patterns:\n * - Creating computed signals directly\n * - Using as a TC39 decorator\n * - Using as a legacy decorator\n * - Using as a decorator factory with options\n *\n * @returns Either a computed signal instance or a decorator function depending on usage\n * @public\n */\nexport function computed() {\n\tif (arguments.length <= 1) {\n\t\t// decorator factory: `@computed(options)` or `@computed()`\n\t\tconst options = arguments[0]\n\t\treturn (...args: any) => computedDecorator(options, args)\n\t} else if (typeof arguments[0] === 'string') {\n\t\treturn new _Computed(arguments[0], arguments[1], arguments[2])\n\t} else {\n\t\treturn computedDecorator(undefined, arguments as any)\n\t}\n}\n\nimport { isComputed as _isComputed } from './isComputed'\n\n/**\n * Returns true if the given value is a computed signal.\n * @public\n */\nexport function isComputed(value: any): value is Computed<any> {\n\treturn _isComputed(value)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["isUninitialized"]
7
7
  }
@@ -50,7 +50,7 @@ class __EffectScheduler__ {
50
50
  maybeScheduleEffect() {
51
51
  if (!this._isActivelyListening) return;
52
52
  if (this.lastReactedEpoch === getGlobalEpoch()) return;
53
- if (this.parents.length && !haveParentsChanged(this)) {
53
+ if ((this.lastReactedEpoch !== GLOBAL_START_EPOCH || this.parents.length > 0) && !haveParentsChanged(this)) {
54
54
  this.lastReactedEpoch = getGlobalEpoch();
55
55
  return;
56
56
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/EffectScheduler.ts"],
4
- "sourcesContent": ["import { ArraySet } from './ArraySet'\nimport { startCapturingParents, stopCapturingParents } from './capture'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { attach, detach, haveParentsChanged, singleton } from './helpers'\nimport { getGlobalEpoch } from './transactions'\nimport { Signal } from './types'\n\n/** @public */\nexport interface EffectSchedulerOptions {\n\t/**\n\t * scheduleEffect is a function that will be called when the effect is scheduled.\n\t *\n\t * It can be used to defer running effects until a later time, for example to batch them together with requestAnimationFrame.\n\t *\n\t *\n\t * @example\n\t * ```ts\n\t * let isRafScheduled = false\n\t * const scheduledEffects: Array<() => void> = []\n\t * const scheduleEffect = (runEffect: () => void) => {\n\t * \tscheduledEffects.push(runEffect)\n\t * \tif (!isRafScheduled) {\n\t * \t\tisRafScheduled = true\n\t * \t\trequestAnimationFrame(() => {\n\t * \t\t\tisRafScheduled = false\n\t * \t\t\tscheduledEffects.forEach((runEffect) => runEffect())\n\t * \t\t\tscheduledEffects.length = 0\n\t * \t\t})\n\t * \t}\n\t * }\n\t * const stop = react('set page title', () => {\n\t * \tdocument.title = doc.title,\n\t * }, scheduleEffect)\n\t * ```\n\t *\n\t * @param execute - A function that will execute the effect.\n\t * @returns void\n\t */\n\t// eslint-disable-next-line tldraw/method-signature-style\n\tscheduleEffect?: (execute: () => void) => void\n}\n\nclass __EffectScheduler__<Result> implements EffectScheduler<Result> {\n\treadonly __isEffectScheduler = true as const\n\t/** @internal */\n\tprivate _isActivelyListening = false\n\t/**\n\t * Whether this scheduler is attached and actively listening to its parents.\n\t * @public\n\t */\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget isActivelyListening() {\n\t\treturn this._isActivelyListening\n\t}\n\t/** @internal */\n\tlastTraversedEpoch = GLOBAL_START_EPOCH\n\n\t/** @internal */\n\tprivate lastReactedEpoch = GLOBAL_START_EPOCH\n\n\t/** @internal */\n\tprivate _scheduleCount = 0\n\t/** @internal */\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null = null\n\n\t/**\n\t * The number of times this effect has been scheduled.\n\t * @public\n\t */\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget scheduleCount() {\n\t\treturn this._scheduleCount\n\t}\n\n\t/** @internal */\n\treadonly parentSet = new ArraySet<Signal<any, any>>()\n\t/** @internal */\n\treadonly parentEpochs: number[] = []\n\t/** @internal */\n\treadonly parents: Signal<any, any>[] = []\n\t/** @internal */\n\tprivate readonly _scheduleEffect?: (execute: () => void) => void\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tprivate readonly runEffect: (lastReactedEpoch: number) => Result,\n\t\toptions?: EffectSchedulerOptions\n\t) {\n\t\tthis._scheduleEffect = options?.scheduleEffect\n\t}\n\n\t/** @internal */\n\tmaybeScheduleEffect() {\n\t\t// bail out if we have been cancelled by another effect\n\t\tif (!this._isActivelyListening) return\n\t\t// bail out if no atoms have changed since the last time we ran this effect\n\t\tif (this.lastReactedEpoch === getGlobalEpoch()) return\n\n\t\t// bail out if we have parents and they have not changed since last time\n\t\tif (this.parents.length && !haveParentsChanged(this)) {\n\t\t\tthis.lastReactedEpoch = getGlobalEpoch()\n\t\t\treturn\n\t\t}\n\t\t// if we don't have parents it's probably the first time this is running.\n\t\tthis.scheduleEffect()\n\t}\n\n\t/** @internal */\n\tscheduleEffect() {\n\t\tthis._scheduleCount++\n\t\tif (this._scheduleEffect) {\n\t\t\t// if the effect should be deferred (e.g. until a react render), do so\n\t\t\tthis._scheduleEffect(this.maybeExecute)\n\t\t} else {\n\t\t\t// otherwise execute right now!\n\t\t\tthis.execute()\n\t\t}\n\t}\n\n\t/** @internal */\n\t// eslint-disable-next-line tldraw/prefer-class-methods\n\treadonly maybeExecute = () => {\n\t\t// bail out if we have been detached before this runs\n\t\tif (!this._isActivelyListening) return\n\t\tthis.execute()\n\t}\n\n\t/**\n\t * Makes this scheduler become 'actively listening' to its parents.\n\t * If it has been executed before it will immediately become eligible to receive 'maybeScheduleEffect' calls.\n\t * 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`.\n\t * @public\n\t */\n\tattach() {\n\t\tthis._isActivelyListening = true\n\t\tfor (let i = 0, n = this.parents.length; i < n; i++) {\n\t\t\tattach(this.parents[i], this)\n\t\t}\n\t}\n\n\t/**\n\t * Makes this scheduler stop 'actively listening' to its parents.\n\t * It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.\n\t * @public\n\t */\n\tdetach() {\n\t\tthis._isActivelyListening = false\n\t\tfor (let i = 0, n = this.parents.length; i < n; i++) {\n\t\t\tdetach(this.parents[i], this)\n\t\t}\n\t}\n\n\t/**\n\t * Executes the effect immediately and returns the result.\n\t * @returns The result of the effect.\n\t * @public\n\t */\n\texecute(): Result {\n\t\ttry {\n\t\t\tstartCapturingParents(this)\n\t\t\t// Important! We have to make a note of the current epoch before running the effect.\n\t\t\t// We allow atoms to be updated during effects, which increments the global epoch,\n\t\t\t// so if we were to wait until after the effect runs, the this.lastReactedEpoch value might get ahead of itself.\n\t\t\tconst currentEpoch = getGlobalEpoch()\n\t\t\tconst result = this.runEffect(this.lastReactedEpoch)\n\t\t\tthis.lastReactedEpoch = currentEpoch\n\t\t\treturn result\n\t\t} finally {\n\t\t\tstopCapturingParents()\n\t\t}\n\t}\n}\n\n/**\n * An EffectScheduler is responsible for executing side effects in response to changes in state.\n *\n * You probably don't need to use this directly unless you're integrating this library with a framework of some kind.\n *\n * Instead, use the {@link react} and {@link reactor} functions.\n *\n * @example\n * ```ts\n * const render = new EffectScheduler('render', drawToCanvas)\n *\n * render.attach()\n * render.execute()\n * ```\n *\n * @public\n */\nexport const EffectScheduler = singleton(\n\t'EffectScheduler',\n\t(): {\n\t\tnew <Result>(\n\t\t\tname: string,\n\t\t\trunEffect: (lastReactedEpoch: number) => Result,\n\t\t\toptions?: EffectSchedulerOptions\n\t\t): EffectScheduler<Result>\n\t} => __EffectScheduler__\n)\n/** @public */\nexport interface EffectScheduler<Result> {\n\t/**\n\t * Whether this scheduler is attached and actively listening to its parents.\n\t * @public\n\t */\n\treadonly isActivelyListening: boolean\n\n\t/** @internal */\n\treadonly lastTraversedEpoch: number\n\n\t/** @public */\n\treadonly name: string\n\n\t/** @internal */\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null\n\n\t/**\n\t * The number of times this effect has been scheduled.\n\t * @public\n\t */\n\treadonly scheduleCount: number\n\n\t/** @internal */\n\treadonly parentSet: ArraySet<Signal<any, any>>\n\n\t/** @internal */\n\treadonly parentEpochs: number[]\n\n\t/** @internal */\n\treadonly parents: Signal<any, any>[]\n\n\t/** @internal */\n\tmaybeScheduleEffect(): void\n\n\t/** @internal */\n\tscheduleEffect(): void\n\n\t/** @internal */\n\tmaybeExecute(): void\n\n\t/**\n\t * Makes this scheduler become 'actively listening' to its parents.\n\t * If it has been executed before it will immediately become eligible to receive 'maybeScheduleEffect' calls.\n\t * 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`.\n\t * @public\n\t */\n\tattach(): void\n\n\t/**\n\t * Makes this scheduler stop 'actively listening' to its parents.\n\t * It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.\n\t * @public\n\t */\n\tdetach(): void\n\n\t/**\n\t * Executes the effect immediately and returns the result.\n\t * @returns The result of the effect.\n\t * @public\n\t */\n\texecute(): Result\n}\n\n/**\n * Starts a new effect scheduler, scheduling the effect immediately.\n *\n * Returns a function that can be called to stop the scheduler.\n *\n * @example\n * ```ts\n * const color = atom('color', 'red')\n * const stop = react('set style', () => {\n * divElem.style.color = color.get()\n * })\n * color.set('blue')\n * // divElem.style.color === 'blue'\n * stop()\n * color.set('green')\n * // divElem.style.color === 'blue'\n * ```\n *\n *\n * Also useful in React applications for running effects outside of the render cycle.\n *\n * @example\n * ```ts\n * useEffect(() => react('set style', () => {\n * divRef.current.style.color = color.get()\n * }), [])\n * ```\n *\n * @public\n */\nexport function react(\n\tname: string,\n\tfn: (lastReactedEpoch: number) => any,\n\toptions?: EffectSchedulerOptions\n) {\n\tconst scheduler = new EffectScheduler(name, fn, options)\n\tscheduler.attach()\n\tscheduler.scheduleEffect()\n\treturn () => {\n\t\tscheduler.detach()\n\t}\n}\n\n/**\n * The reactor is a user-friendly interface for starting and stopping an `EffectScheduler`.\n *\n * Calling `.start()` will attach the scheduler and execute the effect immediately the first time it is called.\n *\n * 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.\n *\n * You can create a reactor with {@link reactor}.\n * @public\n */\nexport interface Reactor<T = unknown> {\n\t/**\n\t * The underlying effect scheduler.\n\t * @public\n\t */\n\tscheduler: EffectScheduler<T>\n\t/**\n\t * Start the scheduler. The first time this is called the effect will be scheduled immediately.\n\t *\n\t * 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.\n\t *\n\t * If you need to force re-execution of the effect, pass `{ force: true }`.\n\t * @public\n\t */\n\tstart(options?: { force?: boolean }): void\n\t/**\n\t * Stop the scheduler.\n\t * @public\n\t */\n\tstop(): void\n}\n\n/**\n * Creates a {@link Reactor}, which is a thin wrapper around an `EffectScheduler`.\n *\n * @public\n */\nexport function reactor<Result>(\n\tname: string,\n\tfn: (lastReactedEpoch: number) => Result,\n\toptions?: EffectSchedulerOptions\n): Reactor<Result> {\n\tconst scheduler = new EffectScheduler<Result>(name, fn, options)\n\treturn {\n\t\tscheduler,\n\t\tstart: (options?: { force?: boolean }) => {\n\t\t\tconst force = options?.force ?? false\n\t\t\tscheduler.attach()\n\t\t\tif (force) {\n\t\t\t\tscheduler.scheduleEffect()\n\t\t\t} else {\n\t\t\t\tscheduler.maybeScheduleEffect()\n\t\t\t}\n\t\t},\n\t\tstop: () => {\n\t\t\tscheduler.detach()\n\t\t},\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { ArraySet } from './ArraySet'\nimport { startCapturingParents, stopCapturingParents } from './capture'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { attach, detach, haveParentsChanged, singleton } from './helpers'\nimport { getGlobalEpoch } from './transactions'\nimport { Signal } from './types'\n\n/** @public */\nexport interface EffectSchedulerOptions {\n\t/**\n\t * scheduleEffect is a function that will be called when the effect is scheduled.\n\t *\n\t * It can be used to defer running effects until a later time, for example to batch them together with requestAnimationFrame.\n\t *\n\t *\n\t * @example\n\t * ```ts\n\t * let isRafScheduled = false\n\t * const scheduledEffects: Array<() => void> = []\n\t * const scheduleEffect = (runEffect: () => void) => {\n\t * \tscheduledEffects.push(runEffect)\n\t * \tif (!isRafScheduled) {\n\t * \t\tisRafScheduled = true\n\t * \t\trequestAnimationFrame(() => {\n\t * \t\t\tisRafScheduled = false\n\t * \t\t\tscheduledEffects.forEach((runEffect) => runEffect())\n\t * \t\t\tscheduledEffects.length = 0\n\t * \t\t})\n\t * \t}\n\t * }\n\t * const stop = react('set page title', () => {\n\t * \tdocument.title = doc.title\n\t * }, { scheduleEffect })\n\t * ```\n\t */\n\t// eslint-disable-next-line tldraw/method-signature-style\n\tscheduleEffect?: (execute: () => void) => void\n}\n\nclass __EffectScheduler__<Result> implements EffectScheduler<Result> {\n\treadonly __isEffectScheduler = true as const\n\t/** @internal */\n\tprivate _isActivelyListening = false\n\t/**\n\t * Whether this scheduler is attached and actively listening to its parents.\n\t * @public\n\t */\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget isActivelyListening() {\n\t\treturn this._isActivelyListening\n\t}\n\t/** @internal */\n\tlastTraversedEpoch = GLOBAL_START_EPOCH\n\n\t/** @internal */\n\tprivate lastReactedEpoch = GLOBAL_START_EPOCH\n\n\t/** @internal */\n\tprivate _scheduleCount = 0\n\t/** @internal */\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null = null\n\n\t/**\n\t * The number of times this effect has been scheduled.\n\t * @public\n\t */\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget scheduleCount() {\n\t\treturn this._scheduleCount\n\t}\n\n\t/** @internal */\n\treadonly parentSet = new ArraySet<Signal<any, any>>()\n\t/** @internal */\n\treadonly parentEpochs: number[] = []\n\t/** @internal */\n\treadonly parents: Signal<any, any>[] = []\n\t/** @internal */\n\tprivate readonly _scheduleEffect?: (execute: () => void) => void\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tprivate readonly runEffect: (lastReactedEpoch: number) => Result,\n\t\toptions?: EffectSchedulerOptions\n\t) {\n\t\tthis._scheduleEffect = options?.scheduleEffect\n\t}\n\n\t/** @internal */\n\tmaybeScheduleEffect() {\n\t\t// bail out if we have been cancelled by another effect\n\t\tif (!this._isActivelyListening) return\n\t\t// bail out if no atoms have changed since the last time we ran this effect\n\t\tif (this.lastReactedEpoch === getGlobalEpoch()) return\n\n\t\t// An effect that has run before (or captured parents before throwing) only needs to run\n\t\t// again if one of those parents changed; that includes an effect that captured no parents at\n\t\t// all. An effect that has never run always runs.\n\t\tif (\n\t\t\t(this.lastReactedEpoch !== GLOBAL_START_EPOCH || this.parents.length > 0) &&\n\t\t\t!haveParentsChanged(this)\n\t\t) {\n\t\t\tthis.lastReactedEpoch = getGlobalEpoch()\n\t\t\treturn\n\t\t}\n\t\tthis.scheduleEffect()\n\t}\n\n\t/** @internal */\n\tscheduleEffect() {\n\t\tthis._scheduleCount++\n\t\tif (this._scheduleEffect) {\n\t\t\t// if the effect should be deferred (e.g. until a react render), do so\n\t\t\tthis._scheduleEffect(this.maybeExecute)\n\t\t} else {\n\t\t\t// otherwise execute right now!\n\t\t\tthis.execute()\n\t\t}\n\t}\n\n\t/** @internal */\n\t// eslint-disable-next-line tldraw/prefer-class-methods\n\treadonly maybeExecute = () => {\n\t\t// bail out if we have been detached before this runs\n\t\tif (!this._isActivelyListening) return\n\t\tthis.execute()\n\t}\n\n\t/**\n\t * Makes this scheduler become 'actively listening' to its parents.\n\t * If it has been executed before it will immediately become eligible to receive 'maybeScheduleEffect' calls.\n\t * 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`.\n\t * @public\n\t */\n\tattach() {\n\t\tthis._isActivelyListening = true\n\t\tfor (let i = 0, n = this.parents.length; i < n; i++) {\n\t\t\tattach(this.parents[i], this)\n\t\t}\n\t}\n\n\t/**\n\t * Makes this scheduler stop 'actively listening' to its parents.\n\t * It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.\n\t * @public\n\t */\n\tdetach() {\n\t\tthis._isActivelyListening = false\n\t\tfor (let i = 0, n = this.parents.length; i < n; i++) {\n\t\t\tdetach(this.parents[i], this)\n\t\t}\n\t}\n\n\t/**\n\t * Executes the effect immediately and returns the result.\n\t * @returns The result of the effect.\n\t * @public\n\t */\n\texecute(): Result {\n\t\ttry {\n\t\t\tstartCapturingParents(this)\n\t\t\t// Important! We have to make a note of the current epoch before running the effect.\n\t\t\t// We allow atoms to be updated during effects, which increments the global epoch,\n\t\t\t// so if we were to wait until after the effect runs, the this.lastReactedEpoch value might get ahead of itself.\n\t\t\tconst currentEpoch = getGlobalEpoch()\n\t\t\tconst result = this.runEffect(this.lastReactedEpoch)\n\t\t\tthis.lastReactedEpoch = currentEpoch\n\t\t\treturn result\n\t\t} finally {\n\t\t\tstopCapturingParents()\n\t\t}\n\t}\n}\n\n/**\n * An EffectScheduler is responsible for executing side effects in response to changes in state.\n *\n * You probably don't need to use this directly unless you're integrating this library with a framework of some kind.\n *\n * Instead, use the {@link react} and {@link reactor} functions.\n *\n * @example\n * ```ts\n * const render = new EffectScheduler('render', drawToCanvas)\n *\n * render.attach()\n * render.execute()\n * ```\n *\n * @public\n */\nexport const EffectScheduler = singleton(\n\t'EffectScheduler',\n\t(): {\n\t\tnew <Result>(\n\t\t\tname: string,\n\t\t\trunEffect: (lastReactedEpoch: number) => Result,\n\t\t\toptions?: EffectSchedulerOptions\n\t\t): EffectScheduler<Result>\n\t} => __EffectScheduler__\n)\n/** @public */\nexport interface EffectScheduler<Result> {\n\t/**\n\t * Whether this scheduler is attached and actively listening to its parents.\n\t * @public\n\t */\n\treadonly isActivelyListening: boolean\n\n\t/** @internal */\n\treadonly lastTraversedEpoch: number\n\n\t/** @public */\n\treadonly name: string\n\n\t/** @internal */\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null\n\n\t/**\n\t * The number of times this effect has been scheduled.\n\t * @public\n\t */\n\treadonly scheduleCount: number\n\n\t/** @internal */\n\treadonly parentSet: ArraySet<Signal<any, any>>\n\n\t/** @internal */\n\treadonly parentEpochs: number[]\n\n\t/** @internal */\n\treadonly parents: Signal<any, any>[]\n\n\t/** @internal */\n\tmaybeScheduleEffect(): void\n\n\t/** @internal */\n\tscheduleEffect(): void\n\n\t/** @internal */\n\tmaybeExecute(): void\n\n\t/**\n\t * Makes this scheduler become 'actively listening' to its parents.\n\t * If it has been executed before it will immediately become eligible to receive 'maybeScheduleEffect' calls.\n\t * 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`.\n\t * @public\n\t */\n\tattach(): void\n\n\t/**\n\t * Makes this scheduler stop 'actively listening' to its parents.\n\t * It will no longer be eligible to receive 'maybeScheduleEffect' calls until `EffectScheduler.attach` is called again.\n\t * @public\n\t */\n\tdetach(): void\n\n\t/**\n\t * Executes the effect immediately and returns the result.\n\t * @returns The result of the effect.\n\t * @public\n\t */\n\texecute(): Result\n}\n\n/**\n * Starts a new effect scheduler, scheduling the effect immediately.\n *\n * Returns a function that can be called to stop the scheduler.\n *\n * @example\n * ```ts\n * const color = atom('color', 'red')\n * const stop = react('set style', () => {\n * divElem.style.color = color.get()\n * })\n * color.set('blue')\n * // divElem.style.color === 'blue'\n * stop()\n * color.set('green')\n * // divElem.style.color === 'blue'\n * ```\n *\n *\n * Also useful in React applications for running effects outside of the render cycle.\n *\n * @example\n * ```ts\n * useEffect(() => react('set style', () => {\n * divRef.current.style.color = color.get()\n * }), [])\n * ```\n *\n * @public\n */\nexport function react(\n\tname: string,\n\tfn: (lastReactedEpoch: number) => any,\n\toptions?: EffectSchedulerOptions\n) {\n\tconst scheduler = new EffectScheduler(name, fn, options)\n\tscheduler.attach()\n\tscheduler.scheduleEffect()\n\treturn () => {\n\t\tscheduler.detach()\n\t}\n}\n\n/**\n * The reactor is a user-friendly interface for starting and stopping an `EffectScheduler`.\n *\n * Calling `.start()` will attach the scheduler and execute the effect immediately the first time it is called.\n *\n * 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.\n *\n * You can create a reactor with {@link reactor}.\n * @public\n */\nexport interface Reactor<T = unknown> {\n\t/**\n\t * The underlying effect scheduler.\n\t * @public\n\t */\n\tscheduler: EffectScheduler<T>\n\t/**\n\t * Start the scheduler. The first time this is called the effect will be scheduled immediately.\n\t *\n\t * 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.\n\t *\n\t * If you need to force re-execution of the effect, pass `{ force: true }`.\n\t * @public\n\t */\n\tstart(options?: { force?: boolean }): void\n\t/**\n\t * Stop the scheduler.\n\t * @public\n\t */\n\tstop(): void\n}\n\n/**\n * Creates a {@link Reactor}, which is a thin wrapper around an `EffectScheduler`.\n *\n * @public\n */\nexport function reactor<Result>(\n\tname: string,\n\tfn: (lastReactedEpoch: number) => Result,\n\toptions?: EffectSchedulerOptions\n): Reactor<Result> {\n\tconst scheduler = new EffectScheduler<Result>(name, fn, options)\n\treturn {\n\t\tscheduler,\n\t\tstart: (options?: { force?: boolean }) => {\n\t\t\tconst force = options?.force ?? false\n\t\t\tscheduler.attach()\n\t\t\tif (force) {\n\t\t\t\tscheduler.scheduleEffect()\n\t\t\t} else {\n\t\t\t\tscheduler.maybeScheduleEffect()\n\t\t\t}\n\t\t},\n\t\tstop: () => {\n\t\t\tscheduler.detach()\n\t\t},\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["options"]
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { EMPTY_ARRAY } from "./helpers.mjs";
1
2
  import { RESET_VALUE } from "./types.mjs";
2
3
  class HistoryBuffer {
3
4
  /**
@@ -27,12 +28,14 @@ class HistoryBuffer {
27
28
  /**
28
29
  * Adds a diff entry to the history buffer, representing a change between two epochs.
29
30
  *
30
- * If the diff is undefined, the operation is ignored. If the diff is RESET_VALUE,
31
- * the entire buffer is cleared to indicate that historical tracking should restart.
31
+ * If the diff is RESET_VALUE, or undefined (meaning no diff is available), the entire buffer is
32
+ * cleared to indicate that historical tracking should restart. Silently skipping an entry would
33
+ * leave a gap, and a later `getChangesSince` from before the gap would return an incomplete
34
+ * diff list rather than RESET_VALUE.
32
35
  *
33
36
  * @param lastComputedEpoch - The epoch when the previous value was computed
34
37
  * @param currentEpoch - The epoch when the current value was computed
35
- * @param diff - The diff representing the change, or RESET_VALUE to clear history
38
+ * @param diff - The diff representing the change, or RESET_VALUE / undefined to clear history
36
39
  * @example
37
40
  * ```ts
38
41
  * const buffer = new HistoryBuffer<string>(5)
@@ -41,10 +44,7 @@ class HistoryBuffer {
41
44
  * ```
42
45
  */
43
46
  pushEntry(lastComputedEpoch, currentEpoch, diff) {
44
- if (diff === void 0) {
45
- return;
46
- }
47
- if (diff === RESET_VALUE) {
47
+ if (diff === RESET_VALUE || diff === void 0) {
48
48
  this.clear();
49
49
  return;
50
50
  }
@@ -97,7 +97,7 @@ class HistoryBuffer {
97
97
  }
98
98
  const [fromEpoch, toEpoch] = elem;
99
99
  if (i === 0 && sinceEpoch >= toEpoch) {
100
- return [];
100
+ return EMPTY_ARRAY;
101
101
  }
102
102
  if (fromEpoch <= sinceEpoch && sinceEpoch < toEpoch) {
103
103
  const len = i + 1;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/HistoryBuffer.ts"],
4
- "sourcesContent": ["import { RESET_VALUE } from './types'\n\n/**\n * A tuple representing a range of epochs and the associated diff.\n * Used internally by HistoryBuffer to store change information.\n *\n * @internal\n */\ntype RangeTuple<Diff> = [fromEpoch: number, toEpoch: number, diff: Diff]\n\n/**\n * A circular buffer that stores diffs between sequential values of an atom or computed signal.\n * This enables efficient change tracking and history retrieval for features like undo/redo.\n *\n * The buffer uses a wrap-around strategy to maintain a fixed-size history of the most recent\n * changes, automatically overwriting older entries when the capacity is exceeded.\n *\n * @example\n * ```ts\n * const buffer = new HistoryBuffer<string>(5)\n * buffer.pushEntry(0, 1, 'first change')\n * buffer.pushEntry(1, 2, 'second change')\n * const changes = buffer.getChangesSince(0) // ['first change', 'second change']\n * ```\n *\n * @internal\n */\nexport class HistoryBuffer<Diff> {\n\t/**\n\t * Current write position in the circular buffer.\n\t * @internal\n\t */\n\tprivate index = 0\n\n\t/**\n\t * Circular buffer storing range tuples. Uses undefined to represent empty slots.\n\t * @internal\n\t */\n\tbuffer: Array<RangeTuple<Diff> | undefined>\n\n\t/**\n\t * Creates a new HistoryBuffer with the specified capacity.\n\t *\n\t * capacity - Maximum number of diffs to store in the buffer\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<number>(10) // Store up to 10 diffs\n\t * ```\n\t */\n\tconstructor(private readonly capacity: number) {\n\t\tthis.buffer = new Array(capacity)\n\t}\n\n\t/**\n\t * Adds a diff entry to the history buffer, representing a change between two epochs.\n\t *\n\t * If the diff is undefined, the operation is ignored. If the diff is RESET_VALUE,\n\t * the entire buffer is cleared to indicate that historical tracking should restart.\n\t *\n\t * @param lastComputedEpoch - The epoch when the previous value was computed\n\t * @param currentEpoch - The epoch when the current value was computed\n\t * @param diff - The diff representing the change, or RESET_VALUE to clear history\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<string>(5)\n\t * buffer.pushEntry(0, 1, 'added text')\n\t * buffer.pushEntry(1, 2, RESET_VALUE) // Clears the buffer\n\t * ```\n\t */\n\tpushEntry(lastComputedEpoch: number, currentEpoch: number, diff: Diff | RESET_VALUE) {\n\t\tif (diff === undefined) {\n\t\t\treturn\n\t\t}\n\n\t\tif (diff === RESET_VALUE) {\n\t\t\tthis.clear()\n\t\t\treturn\n\t\t}\n\n\t\t// Add the diff to the buffer as a range tuple.\n\t\tthis.buffer[this.index] = [lastComputedEpoch, currentEpoch, diff]\n\n\t\t// Bump the index, wrapping around if necessary.\n\t\tthis.index = (this.index + 1) % this.capacity\n\t}\n\n\t/**\n\t * Clears all entries from the history buffer and resets the write position.\n\t * This is called when a RESET_VALUE diff is encountered.\n\t *\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<string>(5)\n\t * buffer.pushEntry(0, 1, 'change')\n\t * buffer.clear()\n\t * console.log(buffer.getChangesSince(0)) // RESET_VALUE\n\t * ```\n\t */\n\tclear() {\n\t\tthis.index = 0\n\t\tthis.buffer.fill(undefined)\n\t}\n\n\t/**\n\t * Retrieves all diffs that occurred since the specified epoch.\n\t *\n\t * The method searches backwards through the circular buffer to find changes\n\t * that occurred after the given epoch. If insufficient history is available\n\t * or the requested epoch is too old, returns RESET_VALUE indicating that\n\t * a complete state rebuild is required.\n\t *\n\t * @param sinceEpoch - The epoch from which to retrieve changes\n\t * @returns Array of diffs since the epoch, or RESET_VALUE if history is insufficient\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<string>(5)\n\t * buffer.pushEntry(0, 1, 'first')\n\t * buffer.pushEntry(1, 2, 'second')\n\t * const changes = buffer.getChangesSince(0) // ['first', 'second']\n\t * const recentChanges = buffer.getChangesSince(1) // ['second']\n\t * const tooOld = buffer.getChangesSince(-100) // RESET_VALUE\n\t * ```\n\t */\n\tgetChangesSince(sinceEpoch: number): RESET_VALUE | Diff[] {\n\t\tconst { index, capacity, buffer } = this\n\n\t\t// For each item in the buffer...\n\t\tfor (let i = 0; i < capacity; i++) {\n\t\t\tconst offset = (index - 1 + capacity - i) % capacity\n\n\t\t\tconst elem = buffer[offset]\n\n\t\t\t// If there's no element in the offset position, return the reset value\n\t\t\tif (!elem) {\n\t\t\t\treturn RESET_VALUE\n\t\t\t}\n\n\t\t\tconst [fromEpoch, toEpoch] = elem\n\n\t\t\t// If the first element is already too early, bail\n\t\t\tif (i === 0 && sinceEpoch >= toEpoch) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\t// If the element is since the given epoch, return an array with all diffs from this element and all following elements\n\t\t\tif (fromEpoch <= sinceEpoch && sinceEpoch < toEpoch) {\n\t\t\t\tconst len = i + 1\n\t\t\t\tconst result = new Array(len)\n\n\t\t\t\tfor (let j = 0; j < len; j++) {\n\t\t\t\t\tresult[j] = buffer[(offset + j) % capacity]![2]\n\t\t\t\t}\n\n\t\t\t\treturn result\n\t\t\t}\n\t\t}\n\n\t\t// If we haven't returned yet, return the reset value\n\t\treturn RESET_VALUE\n\t}\n}\n"],
5
- "mappings": "AAAA,SAAS,mBAAmB;AA2BrB,MAAM,cAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBhC,YAA6B,UAAkB;AAAlB;AAC5B,SAAK,SAAS,IAAI,MAAM,QAAQ;AAAA,EACjC;AAAA,EAF6B;AAAA;AAAA;AAAA;AAAA;AAAA,EAjBrB,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+BA,UAAU,mBAA2B,cAAsB,MAA0B;AACpF,QAAI,SAAS,QAAW;AACvB;AAAA,IACD;AAEA,QAAI,SAAS,aAAa;AACzB,WAAK,MAAM;AACX;AAAA,IACD;AAGA,SAAK,OAAO,KAAK,KAAK,IAAI,CAAC,mBAAmB,cAAc,IAAI;AAGhE,SAAK,SAAS,KAAK,QAAQ,KAAK,KAAK;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,QAAQ;AACP,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,MAAS;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,gBAAgB,YAA0C;AACzD,UAAM,EAAE,OAAO,UAAU,OAAO,IAAI;AAGpC,aAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AAClC,YAAM,UAAU,QAAQ,IAAI,WAAW,KAAK;AAE5C,YAAM,OAAO,OAAO,MAAM;AAG1B,UAAI,CAAC,MAAM;AACV,eAAO;AAAA,MACR;AAEA,YAAM,CAAC,WAAW,OAAO,IAAI;AAG7B,UAAI,MAAM,KAAK,cAAc,SAAS;AACrC,eAAO,CAAC;AAAA,MACT;AAGA,UAAI,aAAa,cAAc,aAAa,SAAS;AACpD,cAAM,MAAM,IAAI;AAChB,cAAM,SAAS,IAAI,MAAM,GAAG;AAE5B,iBAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC7B,iBAAO,CAAC,IAAI,QAAQ,SAAS,KAAK,QAAQ,EAAG,CAAC;AAAA,QAC/C;AAEA,eAAO;AAAA,MACR;AAAA,IACD;AAGA,WAAO;AAAA,EACR;AACD;",
4
+ "sourcesContent": ["import { EMPTY_ARRAY } from './helpers'\nimport { RESET_VALUE } from './types'\n\n/**\n * A tuple representing a range of epochs and the associated diff.\n * Used internally by HistoryBuffer to store change information.\n *\n * @internal\n */\ntype RangeTuple<Diff> = [fromEpoch: number, toEpoch: number, diff: Diff]\n\n/**\n * A circular buffer that stores diffs between sequential values of an atom or computed signal.\n * This enables efficient change tracking and history retrieval for features like undo/redo.\n *\n * The buffer uses a wrap-around strategy to maintain a fixed-size history of the most recent\n * changes, automatically overwriting older entries when the capacity is exceeded.\n *\n * Entries must be contiguous (`entry[k].toEpoch === entry[k+1].fromEpoch`); `getChangesSince`\n * relies on that to find the entry covering an epoch.\n *\n * @example\n * ```ts\n * const buffer = new HistoryBuffer<string>(5)\n * buffer.pushEntry(0, 1, 'first change')\n * buffer.pushEntry(1, 2, 'second change')\n * const changes = buffer.getChangesSince(0) // ['first change', 'second change']\n * ```\n *\n * @internal\n */\nexport class HistoryBuffer<Diff> {\n\t/**\n\t * Current write position in the circular buffer.\n\t * @internal\n\t */\n\tprivate index = 0\n\n\t/**\n\t * Circular buffer storing range tuples. Uses undefined to represent empty slots.\n\t * @internal\n\t */\n\tbuffer: Array<RangeTuple<Diff> | undefined>\n\n\t/**\n\t * Creates a new HistoryBuffer with the specified capacity.\n\t *\n\t * capacity - Maximum number of diffs to store in the buffer\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<number>(10) // Store up to 10 diffs\n\t * ```\n\t */\n\tconstructor(private readonly capacity: number) {\n\t\tthis.buffer = new Array(capacity)\n\t}\n\n\t/**\n\t * Adds a diff entry to the history buffer, representing a change between two epochs.\n\t *\n\t * If the diff is RESET_VALUE, or undefined (meaning no diff is available), the entire buffer is\n\t * cleared to indicate that historical tracking should restart. Silently skipping an entry would\n\t * leave a gap, and a later `getChangesSince` from before the gap would return an incomplete\n\t * diff list rather than RESET_VALUE.\n\t *\n\t * @param lastComputedEpoch - The epoch when the previous value was computed\n\t * @param currentEpoch - The epoch when the current value was computed\n\t * @param diff - The diff representing the change, or RESET_VALUE / undefined to clear history\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<string>(5)\n\t * buffer.pushEntry(0, 1, 'added text')\n\t * buffer.pushEntry(1, 2, RESET_VALUE) // Clears the buffer\n\t * ```\n\t */\n\tpushEntry(lastComputedEpoch: number, currentEpoch: number, diff: Diff | RESET_VALUE | undefined) {\n\t\tif (diff === RESET_VALUE || diff === undefined) {\n\t\t\tthis.clear()\n\t\t\treturn\n\t\t}\n\n\t\t// Add the diff to the buffer as a range tuple.\n\t\tthis.buffer[this.index] = [lastComputedEpoch, currentEpoch, diff]\n\n\t\t// Bump the index, wrapping around if necessary.\n\t\tthis.index = (this.index + 1) % this.capacity\n\t}\n\n\t/**\n\t * Clears all entries from the history buffer and resets the write position.\n\t * This is called when a RESET_VALUE diff is encountered.\n\t *\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<string>(5)\n\t * buffer.pushEntry(0, 1, 'change')\n\t * buffer.clear()\n\t * console.log(buffer.getChangesSince(0)) // RESET_VALUE\n\t * ```\n\t */\n\tclear() {\n\t\tthis.index = 0\n\t\tthis.buffer.fill(undefined)\n\t}\n\n\t/**\n\t * Retrieves all diffs that occurred since the specified epoch.\n\t *\n\t * The method searches backwards through the circular buffer to find changes\n\t * that occurred after the given epoch. If insufficient history is available\n\t * or the requested epoch is too old, returns RESET_VALUE indicating that\n\t * a complete state rebuild is required.\n\t *\n\t * @param sinceEpoch - The epoch from which to retrieve changes\n\t * @returns Array of diffs since the epoch, or RESET_VALUE if history is insufficient\n\t * @example\n\t * ```ts\n\t * const buffer = new HistoryBuffer<string>(5)\n\t * buffer.pushEntry(0, 1, 'first')\n\t * buffer.pushEntry(1, 2, 'second')\n\t * const changes = buffer.getChangesSince(0) // ['first', 'second']\n\t * const recentChanges = buffer.getChangesSince(1) // ['second']\n\t * const tooOld = buffer.getChangesSince(-100) // RESET_VALUE\n\t * ```\n\t */\n\tgetChangesSince(sinceEpoch: number): RESET_VALUE | Diff[] {\n\t\tconst { index, capacity, buffer } = this\n\n\t\t// For each item in the buffer...\n\t\tfor (let i = 0; i < capacity; i++) {\n\t\t\tconst offset = (index - 1 + capacity - i) % capacity\n\n\t\t\tconst elem = buffer[offset]\n\n\t\t\t// If there's no element in the offset position, return the reset value\n\t\t\tif (!elem) {\n\t\t\t\treturn RESET_VALUE\n\t\t\t}\n\n\t\t\tconst [fromEpoch, toEpoch] = elem\n\n\t\t\t// If the first element is already too early, bail\n\t\t\tif (i === 0 && sinceEpoch >= toEpoch) {\n\t\t\t\treturn EMPTY_ARRAY\n\t\t\t}\n\n\t\t\t// If the element is since the given epoch, return an array with all diffs from this element and all following elements\n\t\t\tif (fromEpoch <= sinceEpoch && sinceEpoch < toEpoch) {\n\t\t\t\tconst len = i + 1\n\t\t\t\tconst result = new Array(len)\n\n\t\t\t\tfor (let j = 0; j < len; j++) {\n\t\t\t\t\tresult[j] = buffer[(offset + j) % capacity]![2]\n\t\t\t\t}\n\n\t\t\t\treturn result\n\t\t\t}\n\t\t}\n\n\t\t// If we haven't returned yet, return the reset value\n\t\treturn RESET_VALUE\n\t}\n}\n"],
5
+ "mappings": "AAAA,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AA8BrB,MAAM,cAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBhC,YAA6B,UAAkB;AAAlB;AAC5B,SAAK,SAAS,IAAI,MAAM,QAAQ;AAAA,EACjC;AAAA,EAF6B;AAAA;AAAA;AAAA;AAAA;AAAA,EAjBrB,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiCA,UAAU,mBAA2B,cAAsB,MAAsC;AAChG,QAAI,SAAS,eAAe,SAAS,QAAW;AAC/C,WAAK,MAAM;AACX;AAAA,IACD;AAGA,SAAK,OAAO,KAAK,KAAK,IAAI,CAAC,mBAAmB,cAAc,IAAI;AAGhE,SAAK,SAAS,KAAK,QAAQ,KAAK,KAAK;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,QAAQ;AACP,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,MAAS;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,gBAAgB,YAA0C;AACzD,UAAM,EAAE,OAAO,UAAU,OAAO,IAAI;AAGpC,aAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AAClC,YAAM,UAAU,QAAQ,IAAI,WAAW,KAAK;AAE5C,YAAM,OAAO,OAAO,MAAM;AAG1B,UAAI,CAAC,MAAM;AACV,eAAO;AAAA,MACR;AAEA,YAAM,CAAC,WAAW,OAAO,IAAI;AAG7B,UAAI,MAAM,KAAK,cAAc,SAAS;AACrC,eAAO;AAAA,MACR;AAGA,UAAI,aAAa,cAAc,aAAa,SAAS;AACpD,cAAM,MAAM,IAAI;AAChB,cAAM,SAAS,IAAI,MAAM,GAAG;AAE5B,iBAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC7B,iBAAO,CAAC,IAAI,QAAQ,SAAS,KAAK,QAAQ,EAAG,CAAC;AAAA,QAC/C;AAEA,eAAO;AAAA,MACR;AAAA,IACD;AAGA,WAAO;AAAA,EACR;AACD;",
6
6
  "names": []
7
7
  }
@@ -59,11 +59,9 @@ function stopCapturingParents() {
59
59
  }
60
60
  function maybeCaptureParent(p) {
61
61
  if (inst.stack) {
62
- const wasCapturedAlready = inst.stack.child.parentSet.has(p);
63
- if (wasCapturedAlready) {
62
+ if (!inst.stack.child.parentSet.add(p)) {
64
63
  return;
65
64
  }
66
- inst.stack.child.parentSet.add(p);
67
65
  if (inst.stack.child.isActivelyListening) {
68
66
  attach(p, inst.stack.child);
69
67
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/capture.ts"],
4
- "sourcesContent": ["import { attach, detach, singleton } from './helpers'\nimport { isComputed } from './isComputed'\nimport type { Child, Signal } from './types'\n\nclass CaptureStackFrame {\n\toffset = 0\n\n\tmaybeRemoved?: Signal<any>[]\n\n\tconstructor(\n\t\tpublic readonly below: CaptureStackFrame | null,\n\t\tpublic readonly child: Child\n\t) {}\n}\n\nconst inst = singleton('capture', () => ({ stack: null as null | CaptureStackFrame }))\n\n/**\n * Executes the given function without capturing any parents in the current capture context.\n *\n * This is mainly useful if you want to run an effect only when certain signals change while also\n * dereferencing other signals which should not cause the effect to rerun on their own.\n *\n * @example\n * ```ts\n * const name = atom('name', 'Sam')\n * const time = atom('time', () => new Date().getTime())\n *\n * setInterval(() => {\n * time.set(new Date().getTime())\n * })\n *\n * react('log name changes', () => {\n * \t print(name.get(), 'was changed at', unsafe__withoutCapture(() => time.get()))\n * })\n *\n * ```\n *\n * @public\n */\nexport function unsafe__withoutCapture<T>(fn: () => T): T {\n\tconst oldStack = inst.stack\n\tinst.stack = null\n\ttry {\n\t\treturn fn()\n\t} finally {\n\t\tinst.stack = oldStack\n\t}\n}\n\n/**\n * Begins capturing parent signal dependencies for the given child signal.\n *\n * This function initiates a capture session where any signal accessed via `.get()`\n * will be automatically registered as a dependency of the child signal. It sets up\n * the capture stack frame and clears the existing parent set to prepare for fresh\n * dependency tracking.\n *\n * @param child - The child signal (computed or effect) that will capture dependencies\n *\n * @example\n * ```ts\n * const effect = createEffect('myEffect', () => { /* ... *\\/ })\n * startCapturingParents(effect)\n * // Now any signal.get() calls will be captured as dependencies\n * ```\n *\n * @internal\n */\nexport function startCapturingParents(child: Child) {\n\tinst.stack = new CaptureStackFrame(inst.stack, child)\n\tif (child.__debug_ancestor_epochs__) {\n\t\tconst previousAncestorEpochs = child.__debug_ancestor_epochs__\n\t\tchild.__debug_ancestor_epochs__ = null\n\t\tfor (const p of child.parents) {\n\t\t\tp.__unsafe__getWithoutCapture(true)\n\t\t}\n\t\tlogChangedAncestors(child, previousAncestorEpochs)\n\t}\n\tchild.parentSet.clear()\n}\n\n/**\n * Completes the parent dependency capture session and finalizes the dependency graph.\n *\n * This function cleans up the capture session by removing dependencies that are no\n * longer needed, detaching signals that should no longer be parents, and updating\n * the dependency arrays to reflect the current set of captured parents. It must be\n * called after `startCapturingParents` to complete the capture cycle.\n *\n * @example\n * ```ts\n * startCapturingParents(effect)\n * // ... signal.get() calls happen here ...\n * stopCapturingParents() // Finalizes the dependency graph\n * ```\n *\n * @internal\n */\nexport function stopCapturingParents() {\n\tconst frame = inst.stack!\n\tinst.stack = frame.below\n\n\tif (frame.offset < frame.child.parents.length) {\n\t\tfor (let i = frame.offset; i < frame.child.parents.length; i++) {\n\t\t\tconst maybeRemovedParent = frame.child.parents[i]\n\t\t\tif (!frame.child.parentSet.has(maybeRemovedParent)) {\n\t\t\t\tdetach(maybeRemovedParent, frame.child)\n\t\t\t}\n\t\t}\n\n\t\tframe.child.parents.length = frame.offset\n\t\tframe.child.parentEpochs.length = frame.offset\n\t}\n\n\tif (frame.maybeRemoved) {\n\t\tfor (let i = 0; i < frame.maybeRemoved.length; i++) {\n\t\t\tconst maybeRemovedParent = frame.maybeRemoved[i]\n\t\t\tif (!frame.child.parentSet.has(maybeRemovedParent)) {\n\t\t\t\tdetach(maybeRemovedParent, frame.child)\n\t\t\t}\n\t\t}\n\t}\n\n\tif (frame.child.__debug_ancestor_epochs__) {\n\t\tcaptureAncestorEpochs(frame.child, frame.child.__debug_ancestor_epochs__)\n\t}\n}\n\n/**\n * Conditionally captures a signal as a parent dependency during an active capture session.\n *\n * This function is called whenever a signal's `.get()` method is invoked during a\n * capture session. It checks if the signal should be added as a dependency and manages\n * the parent-child relationship in the reactive graph. The function handles deduplication,\n * attachment/detachment, and tracks changes in dependency order.\n *\n * Note: This must be called after the parent signal is up to date.\n *\n * @param p - The signal that might be captured as a parent dependency\n *\n * @example\n * ```ts\n * // This is called internally when you do:\n * const value = someAtom.get() // maybeCaptureParent(someAtom) is called\n * ```\n *\n * @internal\n */\nexport function maybeCaptureParent(p: Signal<any, any>) {\n\tif (inst.stack) {\n\t\tconst wasCapturedAlready = inst.stack.child.parentSet.has(p)\n\t\t// if the child didn't deref this parent last time it executed, then idx will be -1\n\t\t// if the child did deref this parent last time but in a different order relative to other parents, then idx will be greater than stack.offset\n\t\t// if the child did deref this parent last time in the same order, then idx will be the same as stack.offset\n\t\t// if the child did deref this parent already during this capture session then 0 <= idx < stack.offset\n\n\t\tif (wasCapturedAlready) {\n\t\t\treturn\n\t\t}\n\n\t\tinst.stack.child.parentSet.add(p)\n\t\tif (inst.stack.child.isActivelyListening) {\n\t\t\tattach(p, inst.stack.child)\n\t\t}\n\n\t\tif (inst.stack.offset < inst.stack.child.parents.length) {\n\t\t\tconst maybeRemovedParent = inst.stack.child.parents[inst.stack.offset]\n\t\t\tif (maybeRemovedParent !== p) {\n\t\t\t\tif (!inst.stack.maybeRemoved) {\n\t\t\t\t\tinst.stack.maybeRemoved = [maybeRemovedParent]\n\t\t\t\t} else {\n\t\t\t\t\tinst.stack.maybeRemoved.push(maybeRemovedParent)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tinst.stack.child.parents[inst.stack.offset] = p\n\t\tinst.stack.child.parentEpochs[inst.stack.offset] = p.lastChangedEpoch\n\t\tinst.stack.offset++\n\t}\n}\n\n/**\n * A debugging tool that tells you why a computed signal or effect is running.\n * Call in the body of a computed signal or effect function.\n *\n * @example\n * ```ts\n * const name = atom('name', 'Bob')\n * react('greeting', () => {\n * \twhyAmIRunning()\n *\tprint('Hello', name.get())\n * })\n *\n * name.set('Alice')\n *\n * // 'greeting' is running because:\n * // 'name' changed => 'Alice'\n * ```\n *\n * @public\n */\nexport function whyAmIRunning() {\n\tconst child = inst.stack?.child\n\tif (!child) {\n\t\tthrow new Error('whyAmIRunning() called outside of a reactive context')\n\t}\n\tchild.__debug_ancestor_epochs__ = new Map()\n}\n\nfunction captureAncestorEpochs(child: Child, ancestorEpochs: Map<Signal<any>, number>) {\n\tfor (let i = 0; i < child.parents.length; i++) {\n\t\tconst parent = child.parents[i]\n\t\tconst epoch = child.parentEpochs[i]\n\t\tancestorEpochs.set(parent, epoch)\n\t\tif (isComputed(parent)) {\n\t\t\tcaptureAncestorEpochs(parent as any, ancestorEpochs)\n\t\t}\n\t}\n\treturn ancestorEpochs\n}\n\ntype ChangeTree = { [signalName: string]: ChangeTree } | null\nfunction collectChangedAncestors(\n\tchild: Child,\n\tancestorEpochs: Map<Signal<any>, number>\n): NonNullable<ChangeTree> {\n\tconst changeTree: ChangeTree = {}\n\tfor (let i = 0; i < child.parents.length; i++) {\n\t\tconst parent = child.parents[i]\n\t\tif (!ancestorEpochs.has(parent)) {\n\t\t\tcontinue\n\t\t}\n\t\tconst prevEpoch = ancestorEpochs.get(parent)\n\t\tconst currentEpoch = parent.lastChangedEpoch\n\t\tif (currentEpoch !== prevEpoch) {\n\t\t\tif (isComputed(parent)) {\n\t\t\t\tchangeTree[parent.name] = collectChangedAncestors(parent as any, ancestorEpochs)\n\t\t\t} else {\n\t\t\t\tchangeTree[parent.name] = null\n\t\t\t}\n\t\t}\n\t}\n\treturn changeTree\n}\n\nfunction logChangedAncestors(child: Child, ancestorEpochs: Map<Signal<any>, number>) {\n\tconst changeTree = collectChangedAncestors(child, ancestorEpochs)\n\tif (Object.keys(changeTree).length === 0) {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(`Effect(${child.name}) was executed manually.`)\n\t\treturn\n\t}\n\n\tlet str = isComputed(child)\n\t\t? `Computed(${child.name}) is recomputing because:`\n\t\t: `Effect(${child.name}) is executing because:`\n\n\tfunction logParent(tree: NonNullable<ChangeTree>, indent: number) {\n\t\tconst indentStr = '\\n' + ' '.repeat(indent) + '\u21B3 '\n\t\tfor (const [name, val] of Object.entries(tree)) {\n\t\t\tif (val) {\n\t\t\t\tstr += `${indentStr}Computed(${name}) changed`\n\t\t\t\tlogParent(val, indent + 2)\n\t\t\t} else {\n\t\t\t\tstr += `${indentStr}Atom(${name}) changed`\n\t\t\t}\n\t\t}\n\t}\n\n\tlogParent(changeTree, 1)\n\n\t// eslint-disable-next-line no-console\n\tconsole.log(str)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { attach, detach, singleton } from './helpers'\nimport { isComputed } from './isComputed'\nimport type { Child, Signal } from './types'\n\nclass CaptureStackFrame {\n\toffset = 0\n\n\tmaybeRemoved?: Signal<any>[]\n\n\tconstructor(\n\t\tpublic readonly below: CaptureStackFrame | null,\n\t\tpublic readonly child: Child\n\t) {}\n}\n\nconst inst = singleton('capture', () => ({ stack: null as null | CaptureStackFrame }))\n\n/**\n * Executes the given function without capturing any parents in the current capture context.\n *\n * This is mainly useful if you want to run an effect only when certain signals change while also\n * dereferencing other signals which should not cause the effect to rerun on their own.\n *\n * @example\n * ```ts\n * const name = atom('name', 'Sam')\n * const time = atom('time', Date.now())\n *\n * setInterval(() => {\n * time.set(Date.now())\n * })\n *\n * react('log name changes', () => {\n * \t print(name.get(), 'was changed at', unsafe__withoutCapture(() => time.get()))\n * })\n *\n * ```\n *\n * @public\n */\nexport function unsafe__withoutCapture<T>(fn: () => T): T {\n\tconst oldStack = inst.stack\n\tinst.stack = null\n\ttry {\n\t\treturn fn()\n\t} finally {\n\t\tinst.stack = oldStack\n\t}\n}\n\n/**\n * Begins capturing parent signal dependencies for the given child signal.\n *\n * This function initiates a capture session where any signal accessed via `.get()`\n * will be automatically registered as a dependency of the child signal. It sets up\n * the capture stack frame and clears the existing parent set to prepare for fresh\n * dependency tracking.\n *\n * @param child - The child signal (computed or effect) that will capture dependencies\n *\n * @example\n * ```ts\n * const effect = createEffect('myEffect', () => { /* ... *\\/ })\n * startCapturingParents(effect)\n * // Now any signal.get() calls will be captured as dependencies\n * ```\n *\n * @internal\n */\nexport function startCapturingParents(child: Child) {\n\tinst.stack = new CaptureStackFrame(inst.stack, child)\n\tif (child.__debug_ancestor_epochs__) {\n\t\tconst previousAncestorEpochs = child.__debug_ancestor_epochs__\n\t\tchild.__debug_ancestor_epochs__ = null\n\t\tfor (const p of child.parents) {\n\t\t\tp.__unsafe__getWithoutCapture(true)\n\t\t}\n\t\tlogChangedAncestors(child, previousAncestorEpochs)\n\t}\n\tchild.parentSet.clear()\n}\n\n/**\n * Completes the parent dependency capture session and finalizes the dependency graph.\n *\n * This function cleans up the capture session by removing dependencies that are no\n * longer needed, detaching signals that should no longer be parents, and updating\n * the dependency arrays to reflect the current set of captured parents. It must be\n * called after `startCapturingParents` to complete the capture cycle.\n *\n * @example\n * ```ts\n * startCapturingParents(effect)\n * // ... signal.get() calls happen here ...\n * stopCapturingParents() // Finalizes the dependency graph\n * ```\n *\n * @internal\n */\nexport function stopCapturingParents() {\n\tconst frame = inst.stack!\n\tinst.stack = frame.below\n\n\tif (frame.offset < frame.child.parents.length) {\n\t\tfor (let i = frame.offset; i < frame.child.parents.length; i++) {\n\t\t\tconst maybeRemovedParent = frame.child.parents[i]\n\t\t\tif (!frame.child.parentSet.has(maybeRemovedParent)) {\n\t\t\t\tdetach(maybeRemovedParent, frame.child)\n\t\t\t}\n\t\t}\n\n\t\tframe.child.parents.length = frame.offset\n\t\tframe.child.parentEpochs.length = frame.offset\n\t}\n\n\tif (frame.maybeRemoved) {\n\t\tfor (let i = 0; i < frame.maybeRemoved.length; i++) {\n\t\t\tconst maybeRemovedParent = frame.maybeRemoved[i]\n\t\t\tif (!frame.child.parentSet.has(maybeRemovedParent)) {\n\t\t\t\tdetach(maybeRemovedParent, frame.child)\n\t\t\t}\n\t\t}\n\t}\n\n\tif (frame.child.__debug_ancestor_epochs__) {\n\t\tcaptureAncestorEpochs(frame.child, frame.child.__debug_ancestor_epochs__)\n\t}\n}\n\n/**\n * Conditionally captures a signal as a parent dependency during an active capture session.\n *\n * This function is called whenever a signal's `.get()` method is invoked during a\n * capture session. It checks if the signal should be added as a dependency and manages\n * the parent-child relationship in the reactive graph. The function handles deduplication,\n * attachment/detachment, and tracks changes in dependency order.\n *\n * Note: This must be called after the parent signal is up to date.\n *\n * @param p - The signal that might be captured as a parent dependency\n *\n * @example\n * ```ts\n * // This is called internally when you do:\n * const value = someAtom.get() // maybeCaptureParent(someAtom) is called\n * ```\n *\n * @internal\n */\nexport function maybeCaptureParent(p: Signal<any, any>) {\n\tif (inst.stack) {\n\t\t// `add` returns false when the parent was already captured this run. In array mode both\n\t\t// `has` and `add` scan with indexOf, so going straight to `add` halves the scans per\n\t\t// captured parent.\n\t\tif (!inst.stack.child.parentSet.add(p)) {\n\t\t\treturn\n\t\t}\n\n\t\tif (inst.stack.child.isActivelyListening) {\n\t\t\tattach(p, inst.stack.child)\n\t\t}\n\n\t\t// Parents are recorded in deref order. If the slot at this offset held a different parent\n\t\t// last run, that parent may have moved later in the order or been dropped; only\n\t\t// stopCapturingParents can tell, so remember it for then.\n\t\tif (inst.stack.offset < inst.stack.child.parents.length) {\n\t\t\tconst maybeRemovedParent = inst.stack.child.parents[inst.stack.offset]\n\t\t\tif (maybeRemovedParent !== p) {\n\t\t\t\tif (!inst.stack.maybeRemoved) {\n\t\t\t\t\tinst.stack.maybeRemoved = [maybeRemovedParent]\n\t\t\t\t} else {\n\t\t\t\t\tinst.stack.maybeRemoved.push(maybeRemovedParent)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tinst.stack.child.parents[inst.stack.offset] = p\n\t\tinst.stack.child.parentEpochs[inst.stack.offset] = p.lastChangedEpoch\n\t\tinst.stack.offset++\n\t}\n}\n\n/**\n * A debugging tool that tells you why a computed signal or effect is running.\n * Call in the body of a computed signal or effect function. Nothing is logged for the run that\n * calls it; from the next run on, each run logs the ancestors that changed.\n *\n * @example\n * ```ts\n * const name = atom('name', 'Bob')\n * react('greeting', () => {\n * \twhyAmIRunning()\n *\tprint('Hello', name.get())\n * })\n *\n * name.set('Alice')\n *\n * // Effect(greeting) is executing because:\n * // \u21B3 Atom(name) changed\n * ```\n *\n * @public\n */\nexport function whyAmIRunning() {\n\tconst child = inst.stack?.child\n\tif (!child) {\n\t\tthrow new Error('whyAmIRunning() called outside of a reactive context')\n\t}\n\tchild.__debug_ancestor_epochs__ = new Map()\n}\n\nfunction captureAncestorEpochs(child: Child, ancestorEpochs: Map<Signal<any>, number>) {\n\tfor (let i = 0; i < child.parents.length; i++) {\n\t\tconst parent = child.parents[i]\n\t\tconst epoch = child.parentEpochs[i]\n\t\tancestorEpochs.set(parent, epoch)\n\t\tif (isComputed(parent)) {\n\t\t\tcaptureAncestorEpochs(parent as any, ancestorEpochs)\n\t\t}\n\t}\n\treturn ancestorEpochs\n}\n\ntype ChangeTree = { [signalName: string]: ChangeTree } | null\nfunction collectChangedAncestors(\n\tchild: Child,\n\tancestorEpochs: Map<Signal<any>, number>\n): NonNullable<ChangeTree> {\n\tconst changeTree: ChangeTree = {}\n\tfor (let i = 0; i < child.parents.length; i++) {\n\t\tconst parent = child.parents[i]\n\t\tif (!ancestorEpochs.has(parent)) {\n\t\t\tcontinue\n\t\t}\n\t\tconst prevEpoch = ancestorEpochs.get(parent)\n\t\tconst currentEpoch = parent.lastChangedEpoch\n\t\tif (currentEpoch !== prevEpoch) {\n\t\t\tif (isComputed(parent)) {\n\t\t\t\tchangeTree[parent.name] = collectChangedAncestors(parent as any, ancestorEpochs)\n\t\t\t} else {\n\t\t\t\tchangeTree[parent.name] = null\n\t\t\t}\n\t\t}\n\t}\n\treturn changeTree\n}\n\nfunction logChangedAncestors(child: Child, ancestorEpochs: Map<Signal<any>, number>) {\n\tconst changeTree = collectChangedAncestors(child, ancestorEpochs)\n\tif (Object.keys(changeTree).length === 0) {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(`Effect(${child.name}) was executed manually.`)\n\t\treturn\n\t}\n\n\tlet str = isComputed(child)\n\t\t? `Computed(${child.name}) is recomputing because:`\n\t\t: `Effect(${child.name}) is executing because:`\n\n\tfunction logParent(tree: NonNullable<ChangeTree>, indent: number) {\n\t\tconst indentStr = '\\n' + ' '.repeat(indent) + '\u21B3 '\n\t\tfor (const [name, val] of Object.entries(tree)) {\n\t\t\tif (val) {\n\t\t\t\tstr += `${indentStr}Computed(${name}) changed`\n\t\t\t\tlogParent(val, indent + 2)\n\t\t\t} else {\n\t\t\t\tstr += `${indentStr}Atom(${name}) changed`\n\t\t\t}\n\t\t}\n\t}\n\n\tlogParent(changeTree, 1)\n\n\t// eslint-disable-next-line no-console\n\tconsole.log(str)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/constants.ts"],
4
- "sourcesContent": ["/**\n * The initial epoch value used to mark derivations (computed signals and effects) as dirty before their first computation.\n *\n * This constant ensures that all computed signals and effects start in a \"dirty\" state, guaranteeing they will\n * be computed/executed at least once when first accessed or started. The value -1 is used because:\n * - Global epoch starts at 0 (GLOBAL_START_EPOCH + 1)\n * - Any derived signal initialized with GLOBAL_START_EPOCH (-1) will be considered dirty when compared to any positive epoch\n * - This forces initial computation/execution without requiring special initialization logic\n *\n * Used by:\n * - Computed signals to track when they were last changed\n * - Effect schedulers to track when they were last executed\n * - Transaction system for initial global and reaction epoch values\n *\n * @example\n * ```ts\n * // In Computed class constructor\n * lastChangedEpoch = GLOBAL_START_EPOCH // -1, marking as dirty\n *\n * // When global epoch is 5, this computed will be dirty since -1 < 5\n * const needsComputation = this.lastChangedEpoch < globalEpoch\n * ```\n *\n * @public\n */\nexport const GLOBAL_START_EPOCH = -1\n"],
5
- "mappings": "AAyBO,MAAM,qBAAqB;",
4
+ "sourcesContent": ["/**\n * Sentinel below any real epoch. Derived signals and effects start with their epochs set to this\n * so they are dirty until their first run; real epochs start at `GLOBAL_START_EPOCH + 1`.\n *\n * @internal\n */\nexport const GLOBAL_START_EPOCH = -1\n"],
5
+ "mappings": "AAMO,MAAM,qBAAqB;",
6
6
  "names": []
7
7
  }
@@ -3,8 +3,9 @@ function isChild(x) {
3
3
  }
4
4
  function haveParentsChanged(child) {
5
5
  for (let i = 0, n = child.parents.length; i < n; i++) {
6
- child.parents[i].__unsafe__getWithoutCapture(true);
7
- if (child.parents[i].lastChangedEpoch !== child.parentEpochs[i]) {
6
+ const parent = child.parents[i];
7
+ parent.__unsafe__getWithoutCapture(true);
8
+ if (parent.lastChangedEpoch !== child.parentEpochs[i]) {
8
9
  return true;
9
10
  }
10
11
  }
@@ -41,20 +42,11 @@ function singleton(key, init) {
41
42
  return global[symbol];
42
43
  }
43
44
  const EMPTY_ARRAY = singleton("empty_array", () => Object.freeze([]));
44
- function hasReactors(signal) {
45
- for (const child of signal.children) {
46
- if (child.isActivelyListening) {
47
- return true;
48
- }
49
- }
50
- return false;
51
- }
52
45
  export {
53
46
  EMPTY_ARRAY,
54
47
  attach,
55
48
  detach,
56
49
  equals,
57
- hasReactors,
58
50
  haveParentsChanged,
59
51
  singleton
60
52
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/helpers.ts"],
4
- "sourcesContent": ["import { Child, Signal } from './types'\n\n/**\n * Get whether the given value is a child.\n *\n * @param x The value to check.\n * @returns True if the value is a child, false otherwise.\n * @internal\n */\nfunction isChild(x: any): x is Child {\n\treturn x && typeof x === 'object' && 'parents' in x\n}\n\n/**\n * Checks if any of a child's parent signals have changed by comparing their current epochs\n * with the child's cached view of those epochs.\n *\n * This function is used internally to determine if a computed signal or effect needs to\n * be re-evaluated because one of its dependencies has changed.\n *\n * @param child - The child (computed signal or effect) to check for parent changes\n * @returns `true` if any parent signal has changed since the child last observed it, `false` otherwise\n * @example\n * ```ts\n * const childSignal = computed('child', () => parentAtom.get())\n * // Check if the child needs to recompute\n * if (haveParentsChanged(childSignal)) {\n * // Recompute the child's value\n * }\n * ```\n * @internal\n */\nexport function haveParentsChanged(child: Child): boolean {\n\tfor (let i = 0, n = child.parents.length; i < n; i++) {\n\t\t// Get the parent's value without capturing it.\n\t\tchild.parents[i].__unsafe__getWithoutCapture(true)\n\n\t\t// If the parent's epoch does not match the child's view of the parent's epoch, then the parent has changed.\n\t\tif (child.parents[i].lastChangedEpoch !== child.parentEpochs[i]) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * Detaches a child signal from its parent signal, removing the parent-child relationship\n * in the reactive dependency graph. If the parent has no remaining children and is itself\n * a child, it will recursively detach from its own parents.\n *\n * This function is used internally to clean up the dependency graph when signals are no\n * longer needed or when dependencies change.\n *\n * @param parent - The parent signal to detach from\n * @param child - The child signal to detach\n * @example\n * ```ts\n * // When a computed signal's dependencies change\n * const oldParent = atom('old', 1)\n * const child = computed('child', () => oldParent.get())\n * // Later, detach the child from the old parent\n * detach(oldParent, child)\n * ```\n * @internal\n */\nexport function detach(parent: Signal<any>, child: Child) {\n\t// If the child is not attached to the parent, do nothing.\n\tif (!parent.children.remove(child)) {\n\t\treturn\n\t}\n\n\t// If the parent has no more children, then detach the parent from its parents.\n\tif (parent.children.isEmpty && isChild(parent)) {\n\t\tfor (let i = 0, n = parent.parents.length; i < n; i++) {\n\t\t\tdetach(parent.parents[i], parent)\n\t\t}\n\t}\n}\n\n/**\n * Attaches a child signal to its parent signal, establishing a parent-child relationship\n * in the reactive dependency graph. If the parent is itself a child, it will recursively\n * attach to its own parents to maintain the dependency chain.\n *\n * This function is used internally when dependencies are captured during computed signal\n * evaluation or effect execution.\n *\n * @param parent - The parent signal to attach to\n * @param child - The child signal to attach\n * @example\n * ```ts\n * // When a computed signal captures a new dependency\n * const parentAtom = atom('parent', 1)\n * const child = computed('child', () => parentAtom.get())\n * // Internally, attach is called to establish the dependency\n * attach(parentAtom, child)\n * ```\n * @internal\n */\nexport function attach(parent: Signal<any>, child: Child) {\n\t// If the child is already attached to the parent, do nothing.\n\tif (!parent.children.add(child)) {\n\t\treturn\n\t}\n\n\t// If the parent itself is a child, add the parent to the parent's parents.\n\tif (isChild(parent)) {\n\t\tfor (let i = 0, n = parent.parents.length; i < n; i++) {\n\t\t\tattach(parent.parents[i], parent)\n\t\t}\n\t}\n}\n\n/**\n * Checks if two values are equal using the equality semantics of @tldraw/state.\n *\n * This function performs equality checks in the following order:\n * 1. Reference equality (`===`)\n * 2. `Object.is()` equality (handles NaN and -0/+0 cases)\n * 3. Custom `.equals()` method when the left-hand value provides one\n *\n * This is used internally to determine if a signal's value has actually changed\n * when setting new values, preventing unnecessary updates and re-computations.\n *\n * @param a - The first value to compare\n * @param b - The second value to compare\n * @returns `true` if the values are considered equal, `false` otherwise\n * @example\n * ```ts\n * equals(1, 1) // true\n * equals(NaN, NaN) // true (unlike === which returns false)\n * equals({ equals: (other: any) => other.id === 1 }, { id: 1 }) // Uses custom equals method\n * ```\n * @internal\n */\nexport function equals(a: any, b: any): boolean {\n\tconst shallowEquals =\n\t\ta === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === 'function' && a.equals(b))\n\treturn shallowEquals\n}\n\n/**\n * A TypeScript utility function for exhaustiveness checking in switch statements and\n * conditional branches. This function should never be called at runtime\u2014it exists\n * purely for compile-time type checking and is `undefined` in emitted JavaScript.\n *\n * @param x - A value that should be of type `never`\n * @throws Always at runtime because the identifier is undefined\n * @example\n * ```ts\n * type Color = 'red' | 'blue'\n *\n * function handleColor(color: Color) {\n * switch (color) {\n * case 'red':\n * return 'Stop'\n * case 'blue':\n * return 'Go'\n * default:\n * return assertNever(color) // TypeScript error if not all cases handled\n * }\n * }\n * ```\n * @public\n */\nexport declare function assertNever(x: never): never\n\n/**\n * Creates or retrieves a singleton instance using a global symbol registry.\n * This ensures that the same instance is shared across all code that uses\n * the same key, even across different module boundaries.\n *\n * The singleton is stored on `globalThis` using a symbol created with\n * `Symbol.for()`, which ensures global uniqueness across realms.\n *\n * @param key - A unique string identifier for the singleton\n * @param init - A function that creates the initial value if it doesn't exist\n * @returns The singleton instance\n * @example\n * ```ts\n * // Create a singleton logger\n * const logger = singleton('logger', () => new Logger())\n *\n * // Elsewhere in the codebase, get the same logger instance\n * const sameLogger = singleton('logger', () => new Logger())\n * // logger === sameLogger\n * ```\n * @internal\n */\nexport function singleton<T>(key: string, init: () => T): T {\n\tconst symbol = Symbol.for(`com.tldraw.state/${key}`)\n\tconst global = globalThis as any\n\tglobal[symbol] ??= init()\n\treturn global[symbol]\n}\n\n/**\n * @public\n */\nexport const EMPTY_ARRAY: [] = singleton('empty_array', () => Object.freeze([]) as any)\n\n/**\n * Checks if a signal has any active reactors (effects or computed signals) that are\n * currently listening to it. This determines whether changes to the signal will\n * cause any side effects or recomputations to occur.\n *\n * A signal is considered to have active reactors if any of its child dependencies\n * are actively listening for changes.\n *\n * @param signal - The signal to check for active reactors\n * @returns `true` if the signal has active reactors, `false` otherwise\n * @example\n * ```ts\n * const count = atom('count', 0)\n *\n * console.log(hasReactors(count)) // false - no effects listening\n *\n * const stop = react('logger', () => console.log(count.get()))\n * console.log(hasReactors(count)) // true - effect is listening\n *\n * stop()\n * console.log(hasReactors(count)) // false - effect stopped\n * ```\n * @public\n */\nexport function hasReactors(signal: Signal<any>) {\n\tfor (const child of signal.children) {\n\t\tif (child.isActivelyListening) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"],
5
- "mappings": "AASA,SAAS,QAAQ,GAAoB;AACpC,SAAO,KAAK,OAAO,MAAM,YAAY,aAAa;AACnD;AAqBO,SAAS,mBAAmB,OAAuB;AACzD,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,QAAQ,IAAI,GAAG,KAAK;AAErD,UAAM,QAAQ,CAAC,EAAE,4BAA4B,IAAI;AAGjD,QAAI,MAAM,QAAQ,CAAC,EAAE,qBAAqB,MAAM,aAAa,CAAC,GAAG;AAChE,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO;AACR;AAsBO,SAAS,OAAO,QAAqB,OAAc;AAEzD,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,GAAG;AACnC;AAAA,EACD;AAGA,MAAI,OAAO,SAAS,WAAW,QAAQ,MAAM,GAAG;AAC/C,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IAAI,GAAG,KAAK;AACtD,aAAO,OAAO,QAAQ,CAAC,GAAG,MAAM;AAAA,IACjC;AAAA,EACD;AACD;AAsBO,SAAS,OAAO,QAAqB,OAAc;AAEzD,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,GAAG;AAChC;AAAA,EACD;AAGA,MAAI,QAAQ,MAAM,GAAG;AACpB,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IAAI,GAAG,KAAK;AACtD,aAAO,OAAO,QAAQ,CAAC,GAAG,MAAM;AAAA,IACjC;AAAA,EACD;AACD;AAwBO,SAAS,OAAO,GAAQ,GAAiB;AAC/C,QAAM,gBACL,MAAM,KAAK,OAAO,GAAG,GAAG,CAAC,KAAK,QAAQ,KAAK,KAAK,OAAO,EAAE,WAAW,cAAc,EAAE,OAAO,CAAC,CAAC;AAC9F,SAAO;AACR;AAkDO,SAAS,UAAa,KAAa,MAAkB;AAC3D,QAAM,SAAS,uBAAO,IAAI,oBAAoB,GAAG,EAAE;AACnD,QAAM,SAAS;AACf,SAAO,MAAM,MAAM,KAAK;AACxB,SAAO,OAAO,MAAM;AACrB;AAKO,MAAM,cAAkB,UAAU,eAAe,MAAM,OAAO,OAAO,CAAC,CAAC,CAAQ;AA0B/E,SAAS,YAAY,QAAqB;AAChD,aAAW,SAAS,OAAO,UAAU;AACpC,QAAI,MAAM,qBAAqB;AAC9B,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO;AACR;",
4
+ "sourcesContent": ["import { Child, Signal } from './types'\n\nfunction isChild(x: any): x is Child {\n\treturn x && typeof x === 'object' && 'parents' in x\n}\n\n/**\n * Whether any of the child's parents changed since the child last recorded their epochs. O(parents);\n * returns at the first changed parent, so parents after it are not brought up to date.\n *\n * @internal\n */\nexport function haveParentsChanged(child: Child): boolean {\n\tfor (let i = 0, n = child.parents.length; i < n; i++) {\n\t\tconst parent = child.parents[i]\n\t\t// Bring the parent up to date first: a computed parent's `lastChangedEpoch` only moves when\n\t\t// it is re-derived.\n\t\tparent.__unsafe__getWithoutCapture(true)\n\n\t\tif (parent.lastChangedEpoch !== child.parentEpochs[i]) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * Removes `child` from `parent.children`. A computed parent that loses its last child stops\n * listening itself, recursively, so a signal's `children` set is empty whenever nothing downstream\n * is actively listening.\n *\n * @internal\n */\nexport function detach(parent: Signal<any>, child: Child) {\n\tif (!parent.children.remove(child)) {\n\t\treturn\n\t}\n\n\tif (parent.children.isEmpty && isChild(parent)) {\n\t\tfor (let i = 0, n = parent.parents.length; i < n; i++) {\n\t\t\tdetach(parent.parents[i], parent)\n\t\t}\n\t}\n}\n\n/**\n * Adds `child` to `parent.children`. A computed parent that gains its first child starts\n * listening itself, recursively, so that changes to any ancestor are traversed down to the child.\n *\n * @internal\n */\nexport function attach(parent: Signal<any>, child: Child) {\n\tif (!parent.children.add(child)) {\n\t\treturn\n\t}\n\n\tif (isChild(parent)) {\n\t\tfor (let i = 0, n = parent.parents.length; i < n; i++) {\n\t\t\tattach(parent.parents[i], parent)\n\t\t}\n\t}\n}\n\n/**\n * The default equality used for change detection: `===`, then `Object.is` (so `NaN` equals\n * `NaN`; `0` and `-0` are already equal by `===`), then the old value's own `.equals(b)` method\n * if it has one. Only the old value's `equals` is consulted.\n *\n * @internal\n */\nexport function equals(a: any, b: any): boolean {\n\tconst shallowEquals =\n\t\ta === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === 'function' && a.equals(b))\n\treturn shallowEquals\n}\n\n/**\n * Creates or retrieves a singleton instance using a global symbol registry.\n * This ensures that the same instance is shared across all code that uses\n * the same key, even across different module boundaries.\n *\n * The singleton is stored on `globalThis` using a symbol created with\n * `Symbol.for()`, which ensures global uniqueness across realms.\n *\n * @param key - A unique string identifier for the singleton\n * @param init - A function that creates the initial value if it doesn't exist\n * @returns The singleton instance\n * @example\n * ```ts\n * // Create a singleton logger\n * const logger = singleton('logger', () => new Logger())\n *\n * // Elsewhere in the codebase, get the same logger instance\n * const sameLogger = singleton('logger', () => new Logger())\n * // logger === sameLogger\n * ```\n * @internal\n */\nexport function singleton<T>(key: string, init: () => T): T {\n\tconst symbol = Symbol.for(`com.tldraw.state/${key}`)\n\tconst global = globalThis as any\n\tglobal[symbol] ??= init()\n\treturn global[symbol]\n}\n\n/**\n * @public\n */\nexport const EMPTY_ARRAY: [] = singleton('empty_array', () => Object.freeze([]) as any)\n"],
5
+ "mappings": "AAEA,SAAS,QAAQ,GAAoB;AACpC,SAAO,KAAK,OAAO,MAAM,YAAY,aAAa;AACnD;AAQO,SAAS,mBAAmB,OAAuB;AACzD,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,QAAQ,IAAI,GAAG,KAAK;AACrD,UAAM,SAAS,MAAM,QAAQ,CAAC;AAG9B,WAAO,4BAA4B,IAAI;AAEvC,QAAI,OAAO,qBAAqB,MAAM,aAAa,CAAC,GAAG;AACtD,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO;AACR;AASO,SAAS,OAAO,QAAqB,OAAc;AACzD,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,GAAG;AACnC;AAAA,EACD;AAEA,MAAI,OAAO,SAAS,WAAW,QAAQ,MAAM,GAAG;AAC/C,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IAAI,GAAG,KAAK;AACtD,aAAO,OAAO,QAAQ,CAAC,GAAG,MAAM;AAAA,IACjC;AAAA,EACD;AACD;AAQO,SAAS,OAAO,QAAqB,OAAc;AACzD,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,GAAG;AAChC;AAAA,EACD;AAEA,MAAI,QAAQ,MAAM,GAAG;AACpB,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IAAI,GAAG,KAAK;AACtD,aAAO,OAAO,QAAQ,CAAC,GAAG,MAAM;AAAA,IACjC;AAAA,EACD;AACD;AASO,SAAS,OAAO,GAAQ,GAAiB;AAC/C,QAAM,gBACL,MAAM,KAAK,OAAO,GAAG,GAAG,CAAC,KAAK,QAAQ,KAAK,KAAK,OAAO,EAAE,WAAW,cAAc,EAAE,OAAO,CAAC,CAAC;AAC9F,SAAO;AACR;AAwBO,SAAS,UAAa,KAAa,MAAkB;AAC3D,QAAM,SAAS,uBAAO,IAAI,oBAAoB,GAAG,EAAE;AACnD,QAAM,SAAS;AACf,SAAO,MAAM,MAAM,KAAK;AACxB,SAAO,OAAO,MAAM;AACrB;AAKO,MAAM,cAAkB,UAAU,eAAe,MAAM,OAAO,OAAO,CAAC,CAAC,CAAQ;",
6
6
  "names": []
7
7
  }
@@ -27,10 +27,15 @@ function localStorageAtom(name, initialValue, options) {
27
27
  } catch {
28
28
  }
29
29
  };
30
- window.addEventListener("storage", handleStorageEvent);
30
+ const canListen = typeof window !== "undefined";
31
+ if (canListen) {
32
+ window.addEventListener("storage", handleStorageEvent);
33
+ }
31
34
  const cleanup = () => {
32
35
  reactCleanup();
33
- window.removeEventListener("storage", handleStorageEvent);
36
+ if (canListen) {
37
+ window.removeEventListener("storage", handleStorageEvent);
38
+ }
34
39
  };
35
40
  return [outAtom, cleanup];
36
41
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/localStorageAtom.ts"],
4
- "sourcesContent": ["import { deleteFromLocalStorage, getFromLocalStorage, setInLocalStorage } from '@tldraw/utils'\nimport { Atom, atom, AtomOptions } from './Atom'\nimport { react } from './EffectScheduler'\n\n/**\n * Creates a new {@link Atom} that persists its value to localStorage.\n *\n * The atom is automatically synced with localStorage - changes to the atom are saved to localStorage,\n * and the initial value is read from localStorage if it exists. Returns both the atom and a cleanup\n * function that should be called to stop syncing when the atom is no longer needed. If you need to delete\n * the atom, you should do it manually after all cleanup functions have been called.\n *\n * @example\n * ```ts\n * const [theme, cleanup] = localStorageAtom('theme', 'light')\n *\n * theme.get() // 'light' or value from localStorage if it exists\n *\n * theme.set('dark') // updates atom and saves to localStorage\n *\n * // When done:\n * cleanup() // stops syncing to localStorage\n * ```\n *\n * @param name - The localStorage key and atom name. This is used for both localStorage persistence\n * and debugging/profiling purposes.\n * @param initialValue - The initial value of the atom, used if no value exists in localStorage.\n * @param options - Optional atom configuration. See {@link AtomOptions}.\n * @returns A tuple containing the atom and a cleanup function to stop localStorage syncing.\n * @public\n */\nexport function localStorageAtom<Value, Diff = unknown>(\n\tname: string,\n\tinitialValue: Value,\n\toptions?: AtomOptions<Value, Diff>\n): [Atom<Value, Diff>, () => void] {\n\t// Try to restore the initial value from localStorage\n\tlet _initialValue = initialValue\n\n\ttry {\n\t\tconst value = getFromLocalStorage(name)\n\t\tif (value) {\n\t\t\t_initialValue = JSON.parse(value) as Value\n\t\t}\n\t} catch {\n\t\t// If parsing fails, the stored value is corrupted - delete it and use the provided initial value\n\t\tdeleteFromLocalStorage(name)\n\t}\n\n\t// Create the atom with the restored or initial value\n\tconst outAtom = atom(name, _initialValue, options)\n\n\t// Set up automatic syncing: whenever the atom changes, save it to localStorage\n\tconst reactCleanup = react(`save ${name} to localStorage`, () => {\n\t\tsetInLocalStorage(name, JSON.stringify(outAtom.get()))\n\t})\n\n\t// Set up cross-tab sync: listen for storage events from other tabs\n\tconst handleStorageEvent = (event: StorageEvent) => {\n\t\t// Only handle events for this specific key\n\t\tif (event.key !== name) return\n\n\t\t// If the value was deleted in another tab\n\t\tif (event.newValue === null) {\n\t\t\toutAtom.set(initialValue)\n\t\t\treturn\n\t\t}\n\n\t\t// If the value was changed in another tab, update the atom\n\t\ttry {\n\t\t\tconst newValue = JSON.parse(event.newValue) as Value\n\t\t\toutAtom.set(newValue)\n\t\t} catch {\n\t\t\t// If parsing fails, the stored value is corrupted; preserve the existing value\n\t\t}\n\t}\n\n\twindow.addEventListener('storage', handleStorageEvent)\n\n\t// Combined cleanup function\n\tconst cleanup = () => {\n\t\treactCleanup()\n\t\twindow.removeEventListener('storage', handleStorageEvent)\n\t}\n\n\treturn [outAtom, cleanup]\n}\n"],
5
- "mappings": "AAAA,SAAS,wBAAwB,qBAAqB,yBAAyB;AAC/E,SAAe,YAAyB;AACxC,SAAS,aAAa;AA6Bf,SAAS,iBACf,MACA,cACA,SACkC;AAElC,MAAI,gBAAgB;AAEpB,MAAI;AACH,UAAM,QAAQ,oBAAoB,IAAI;AACtC,QAAI,OAAO;AACV,sBAAgB,KAAK,MAAM,KAAK;AAAA,IACjC;AAAA,EACD,QAAQ;AAEP,2BAAuB,IAAI;AAAA,EAC5B;AAGA,QAAM,UAAU,KAAK,MAAM,eAAe,OAAO;AAGjD,QAAM,eAAe,MAAM,QAAQ,IAAI,oBAAoB,MAAM;AAChE,sBAAkB,MAAM,KAAK,UAAU,QAAQ,IAAI,CAAC,CAAC;AAAA,EACtD,CAAC;AAGD,QAAM,qBAAqB,CAAC,UAAwB;AAEnD,QAAI,MAAM,QAAQ,KAAM;AAGxB,QAAI,MAAM,aAAa,MAAM;AAC5B,cAAQ,IAAI,YAAY;AACxB;AAAA,IACD;AAGA,QAAI;AACH,YAAM,WAAW,KAAK,MAAM,MAAM,QAAQ;AAC1C,cAAQ,IAAI,QAAQ;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACD;AAEA,SAAO,iBAAiB,WAAW,kBAAkB;AAGrD,QAAM,UAAU,MAAM;AACrB,iBAAa;AACb,WAAO,oBAAoB,WAAW,kBAAkB;AAAA,EACzD;AAEA,SAAO,CAAC,SAAS,OAAO;AACzB;",
4
+ "sourcesContent": ["import { deleteFromLocalStorage, getFromLocalStorage, setInLocalStorage } from '@tldraw/utils'\nimport { Atom, atom, AtomOptions } from './Atom'\nimport { react } from './EffectScheduler'\n\n/**\n * Creates a new {@link Atom} that persists its value to localStorage.\n *\n * The atom is automatically synced with localStorage - changes to the atom are saved to localStorage,\n * and the initial value is read from localStorage if it exists. Returns both the atom and a cleanup\n * function that should be called to stop syncing when the atom is no longer needed. If you need to delete\n * the atom, you should do it manually after all cleanup functions have been called.\n *\n * @example\n * ```ts\n * const [theme, cleanup] = localStorageAtom('theme', 'light')\n *\n * theme.get() // 'light' or value from localStorage if it exists\n *\n * theme.set('dark') // updates atom and saves to localStorage\n *\n * // When done:\n * cleanup() // stops syncing to localStorage\n * ```\n *\n * @param name - The localStorage key and atom name. This is used for both localStorage persistence\n * and debugging/profiling purposes.\n * @param initialValue - The initial value of the atom, used if no value exists in localStorage.\n * @param options - Optional atom configuration. See {@link AtomOptions}.\n * @returns A tuple containing the atom and a cleanup function to stop localStorage syncing.\n * @public\n */\nexport function localStorageAtom<Value, Diff = unknown>(\n\tname: string,\n\tinitialValue: Value,\n\toptions?: AtomOptions<Value, Diff>\n): [Atom<Value, Diff>, () => void] {\n\t// Try to restore the initial value from localStorage\n\tlet _initialValue = initialValue\n\n\ttry {\n\t\tconst value = getFromLocalStorage(name)\n\t\tif (value) {\n\t\t\t_initialValue = JSON.parse(value) as Value\n\t\t}\n\t} catch {\n\t\t// If parsing fails, the stored value is corrupted - delete it and use the provided initial value\n\t\tdeleteFromLocalStorage(name)\n\t}\n\n\t// Create the atom with the restored or initial value\n\tconst outAtom = atom(name, _initialValue, options)\n\n\t// Set up automatic syncing: whenever the atom changes, save it to localStorage\n\tconst reactCleanup = react(`save ${name} to localStorage`, () => {\n\t\tsetInLocalStorage(name, JSON.stringify(outAtom.get()))\n\t})\n\n\t// Set up cross-tab sync: listen for storage events from other tabs\n\tconst handleStorageEvent = (event: StorageEvent) => {\n\t\t// Only handle events for this specific key\n\t\tif (event.key !== name) return\n\n\t\t// If the value was deleted in another tab\n\t\tif (event.newValue === null) {\n\t\t\toutAtom.set(initialValue)\n\t\t\treturn\n\t\t}\n\n\t\t// If the value was changed in another tab, update the atom\n\t\ttry {\n\t\t\tconst newValue = JSON.parse(event.newValue) as Value\n\t\t\toutAtom.set(newValue)\n\t\t} catch {\n\t\t\t// If parsing fails, the stored value is corrupted; preserve the existing value\n\t\t}\n\t}\n\n\t// The storage helpers above tolerate environments without localStorage (Node, SSR); do the\n\t// same here rather than throwing on `window`.\n\tconst canListen = typeof window !== 'undefined'\n\tif (canListen) {\n\t\twindow.addEventListener('storage', handleStorageEvent)\n\t}\n\n\t// Combined cleanup function\n\tconst cleanup = () => {\n\t\treactCleanup()\n\t\tif (canListen) {\n\t\t\twindow.removeEventListener('storage', handleStorageEvent)\n\t\t}\n\t}\n\n\treturn [outAtom, cleanup]\n}\n"],
5
+ "mappings": "AAAA,SAAS,wBAAwB,qBAAqB,yBAAyB;AAC/E,SAAe,YAAyB;AACxC,SAAS,aAAa;AA6Bf,SAAS,iBACf,MACA,cACA,SACkC;AAElC,MAAI,gBAAgB;AAEpB,MAAI;AACH,UAAM,QAAQ,oBAAoB,IAAI;AACtC,QAAI,OAAO;AACV,sBAAgB,KAAK,MAAM,KAAK;AAAA,IACjC;AAAA,EACD,QAAQ;AAEP,2BAAuB,IAAI;AAAA,EAC5B;AAGA,QAAM,UAAU,KAAK,MAAM,eAAe,OAAO;AAGjD,QAAM,eAAe,MAAM,QAAQ,IAAI,oBAAoB,MAAM;AAChE,sBAAkB,MAAM,KAAK,UAAU,QAAQ,IAAI,CAAC,CAAC;AAAA,EACtD,CAAC;AAGD,QAAM,qBAAqB,CAAC,UAAwB;AAEnD,QAAI,MAAM,QAAQ,KAAM;AAGxB,QAAI,MAAM,aAAa,MAAM;AAC5B,cAAQ,IAAI,YAAY;AACxB;AAAA,IACD;AAGA,QAAI;AACH,YAAM,WAAW,KAAK,MAAM,MAAM,QAAQ;AAC1C,cAAQ,IAAI,QAAQ;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACD;AAIA,QAAM,YAAY,OAAO,WAAW;AACpC,MAAI,WAAW;AACd,WAAO,iBAAiB,WAAW,kBAAkB;AAAA,EACtD;AAGA,QAAM,UAAU,MAAM;AACrB,iBAAa;AACb,QAAI,WAAW;AACd,aAAO,oBAAoB,WAAW,kBAAkB;AAAA,IACzD;AAAA,EACD;AAEA,SAAO,CAAC,SAAS,OAAO;AACzB;",
6
6
  "names": []
7
7
  }
@@ -9,20 +9,10 @@ class Transaction {
9
9
  isSync;
10
10
  asyncProcessCount = 0;
11
11
  initialAtomValues = /* @__PURE__ */ new Map();
12
- /**
13
- * Get whether this transaction is a root (no parents).
14
- *
15
- * @public
16
- */
17
12
  // eslint-disable-next-line tldraw/no-setter-getter
18
13
  get isRoot() {
19
14
  return this.parent === null;
20
15
  }
21
- /**
22
- * Commit the transaction's changes.
23
- *
24
- * @public
25
- */
26
16
  commit() {
27
17
  if (inst.globalIsReacting) {
28
18
  for (const atom of this.initialAtomValues.keys()) {
@@ -31,9 +21,14 @@ class Transaction {
31
21
  } else if (this.isRoot) {
32
22
  flushChanges(this.initialAtomValues.keys());
33
23
  } else {
24
+ const parentValues = this.parent.initialAtomValues;
25
+ if (parentValues.size === 0) {
26
+ this.parent.initialAtomValues = this.initialAtomValues;
27
+ return;
28
+ }
34
29
  this.initialAtomValues.forEach((value, atom) => {
35
- if (!this.parent.initialAtomValues.has(atom)) {
36
- this.parent.initialAtomValues.set(atom, value);
30
+ if (!parentValues.has(atom)) {
31
+ parentValues.set(atom, value);
37
32
  }
38
33
  });
39
34
  }
@@ -83,10 +78,6 @@ function traverseChild(child) {
83
78
  child.children.visit(traverseChild);
84
79
  }
85
80
  }
86
- function traverse(reactors, child) {
87
- traverseReactors = reactors;
88
- traverseChild(child);
89
- }
90
81
  function flushChanges(atoms) {
91
82
  if (inst.globalIsReacting) {
92
83
  throw new Error("flushChanges cannot be called during a reaction");
@@ -97,8 +88,9 @@ function flushChanges(atoms) {
97
88
  inst.globalIsReacting = true;
98
89
  inst.reactionEpoch = inst.globalEpoch;
99
90
  const reactors = /* @__PURE__ */ new Set();
91
+ traverseReactors = reactors;
100
92
  for (const atom of atoms) {
101
- atom.children.visit((child) => traverse(reactors, child));
93
+ atom.children.visit(traverseChild);
102
94
  }
103
95
  for (const r of reactors) {
104
96
  r.maybeScheduleEffect();
@@ -133,8 +125,8 @@ function atomDidChange(atom, previousValue) {
133
125
  }
134
126
  }
135
127
  function traverseAtomForCleanup(atom) {
136
- const rs = inst.cleanupReactors ??= /* @__PURE__ */ new Set();
137
- atom.children.visit((child) => traverse(rs, child));
128
+ traverseReactors = inst.cleanupReactors ??= /* @__PURE__ */ new Set();
129
+ atom.children.visit(traverseChild);
138
130
  }
139
131
  function advanceGlobalEpoch() {
140
132
  inst.globalEpoch++;