@tldraw/state 5.5.0-canary.f7ff7a688554 → 5.5.0-canary.fa4e171d600e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cjs/index.js CHANGED
@@ -61,7 +61,7 @@ if (actualApiVersion !== currentApiVersion) {
61
61
  }
62
62
  (0, import_utils.registerTldrawLibraryVersion)(
63
63
  "@tldraw/state",
64
- "5.5.0-canary.f7ff7a688554",
64
+ "5.5.0-canary.fa4e171d600e",
65
65
  "cjs"
66
66
  );
67
67
  //# sourceMappingURL=index.js.map
@@ -33,12 +33,11 @@ class __Atom__ {
33
33
  constructor(name, current, options) {
34
34
  this.name = name;
35
35
  this.current = current;
36
- this.isEqual = options?.isEqual ?? null;
37
- if (!options) return;
38
- if (options.historyLength) {
36
+ this.isEqual = options?.isEqual ?? import_helpers.equals;
37
+ if (options?.historyLength) {
39
38
  this.historyBuffer = new import_HistoryBuffer.HistoryBuffer(options.historyLength);
40
39
  }
41
- this.computeDiff = options.computeDiff;
40
+ this.computeDiff = options?.computeDiff;
42
41
  }
43
42
  name;
44
43
  current;
@@ -91,7 +90,7 @@ class __Atom__ {
91
90
  * ```
92
91
  */
93
92
  set(value, diff) {
94
- if (this.isEqual?.(this.current, value) ?? (0, import_helpers.equals)(this.current, value)) {
93
+ if (this.isEqual(this.current, value)) {
95
94
  return this.current;
96
95
  }
97
96
  let historyDiff;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/Atom.ts"],
4
- "sourcesContent": ["import { ArraySet } from './ArraySet'\nimport { maybeCaptureParent } from './capture'\nimport { EMPTY_ARRAY, equals, singleton } from './helpers'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { advanceGlobalEpoch, atomDidChange, getGlobalEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\n\n/**\n * The options to configure an atom, passed into the {@link atom} function.\n * @public\n */\nexport interface AtomOptions<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 passed to {@link Atom.set} or produced by {@link AtomOptions.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 atom's old and new values. If provided, it will not be used unless you also specify {@link AtomOptions.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 atom 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 * An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.\n *\n * Atoms are created using the {@link atom} function.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n *\n * print(name.get()) // 'John'\n * ```\n *\n * @public\n */\nexport interface Atom<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Sets the value of this atom to the given value. If the value is the same as the current value, this is a no-op.\n\t *\n\t * @param value - The new value to set.\n\t * @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}.\n\t */\n\tset(value: Value, diff?: Diff): Value\n\t/**\n\t * Updates the value of this atom using the given updater function. If the returned value is the same as the current value, this is a no-op.\n\t *\n\t * @param updater - A function that takes the current value and returns the new value.\n\t */\n\tupdate(updater: (value: Value) => Value): Value\n}\n\n/**\n * Internal implementation of the Atom interface. This class should not be used directly - use the {@link atom} function instead.\n *\n * @internal\n */\nclass __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tprivate current: Value,\n\t\toptions?: AtomOptions<Value, Diff>\n\t) {\n\t\tthis.isEqual = options?.isEqual ?? null\n\n\t\tif (!options) return\n\n\t\tif (options.historyLength) {\n\t\t\tthis.historyBuffer = new HistoryBuffer(options.historyLength)\n\t\t}\n\n\t\tthis.computeDiff = options.computeDiff\n\t}\n\n\t/** @internal */\n\treadonly isEqual: null | ((a: any, b: any) => boolean)\n\n\t/** @internal */\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\n\t/** @internal */\n\tlastChangedEpoch = getGlobalEpoch()\n\n\t/** @internal */\n\tchildren = new ArraySet<Child>()\n\n\t/** @internal */\n\thistoryBuffer?: HistoryBuffer<Diff>\n\n\t/**\n\t * Gets the current value without capturing it as a dependency in the current reactive context.\n\t * This is unsafe because it breaks the reactivity chain - use with caution.\n\t *\n\t * @param _ignoreErrors - Unused parameter for API compatibility\n\t * @returns The current value\n\t * @internal\n\t */\n\t__unsafe__getWithoutCapture(_ignoreErrors?: boolean): Value {\n\t\treturn this.current\n\t}\n\n\t/**\n\t * Gets the current value of this atom. When called within a computed signal or reaction,\n\t * this atom will be automatically captured as a dependency.\n\t *\n\t * @returns The current value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 5)\n\t * console.log(count.get()) // 5\n\t * ```\n\t */\n\tget() {\n\t\tmaybeCaptureParent(this)\n\t\treturn this.current\n\t}\n\n\t/**\n\t * Sets the value of this atom to the given value. If the value is the same as the current value, this is a no-op.\n\t *\n\t * @param value - The new value to set\n\t * @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}\n\t * @returns The new value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 0)\n\t * count.set(5) // count.get() is now 5\n\t * ```\n\t */\n\tset(value: Value, diff?: Diff): Value {\n\t\t// If the value has not changed, do nothing.\n\t\tif (this.isEqual?.(this.current, value) ?? equals(this.current, value)) {\n\t\t\treturn this.current\n\t\t}\n\n\t\t// `computeDiff` is user code: run it before ticking the epoch, so that any signal it reads is\n\t\t// checked against the epoch this write has not yet happened in. Reading a dependent computed\n\t\t// after the tick would stamp it as checked at the new epoch while the atom was still being\n\t\t// written, and it would then never see the change. Only `undefined` means \"no diff\n\t\t// supplied\"; `null` can be a legitimate diff.\n\t\tlet historyDiff: Diff | RESET_VALUE | undefined\n\t\tif (this.historyBuffer) {\n\t\t\thistoryDiff =\n\t\t\t\tdiff !== undefined\n\t\t\t\t\t? diff\n\t\t\t\t\t: this.computeDiff\n\t\t\t\t\t\t? this.computeDiff(this.current, value, this.lastChangedEpoch, getGlobalEpoch() + 1)\n\t\t\t\t\t\t: RESET_VALUE\n\t\t}\n\n\t\t// Tick forward the global epoch. This write belongs to that one epoch, so read it once and\n\t\t// use it everywhere below \u2014 otherwise a `computeDiff` that touches other atoms could leave\n\t\t// the history entry and `lastChangedEpoch` disagreeing.\n\t\tadvanceGlobalEpoch()\n\t\tconst epoch = getGlobalEpoch()\n\n\t\t// Add the diff to the history buffer.\n\t\tif (this.historyBuffer) {\n\t\t\tthis.historyBuffer.pushEntry(this.lastChangedEpoch, epoch, historyDiff)\n\t\t}\n\n\t\t// Update the atom's record of the epoch when last changed.\n\t\tthis.lastChangedEpoch = epoch\n\n\t\tconst oldValue = this.current\n\t\tthis.current = value\n\n\t\t// Notify all children that this atom has changed.\n\t\tatomDidChange(this as any, oldValue)\n\n\t\treturn value\n\t}\n\n\t/**\n\t * Updates the value of this atom using the given updater function. If the returned value is the same as the current value, this is a no-op.\n\t *\n\t * @param updater - A function that takes the current value and returns the new value\n\t * @returns The new value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 5)\n\t * count.update(n => n + 1) // count.get() is now 6\n\t * ```\n\t */\n\tupdate(updater: (value: Value) => Value): Value {\n\t\treturn this.set(updater(this.current))\n\t}\n\n\t/**\n\t * Gets all the diffs that have occurred since the given epoch. When called within a computed\n\t * signal or reaction, this atom will be automatically captured as a dependency.\n\t *\n\t * @param epoch - The epoch to get changes since\n\t * @returns An array of diffs, or RESET_VALUE if history is insufficient\n\t * @internal\n\t */\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[] {\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 Atom constructor. Used internally to create atom instances.\n * @internal\n */\nexport const _Atom = singleton('Atom', () => __Atom__)\n\n/**\n * Type alias for instances of the internal Atom class.\n * @internal\n */\nexport type _Atom = InstanceType<typeof _Atom>\n\n/**\n * Creates a new {@link Atom}.\n *\n * An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n *\n * name.get() // 'John'\n *\n * name.set('Jane')\n *\n * name.get() // 'Jane'\n * ```\n *\n * @public\n */\nexport function atom<Value, Diff = unknown>(\n\t/**\n\t * A name for the signal. This is used for debugging and profiling purposes, it does not need to be unique.\n\t */\n\tname: string,\n\t/**\n\t * The initial value of the signal.\n\t */\n\tinitialValue: Value,\n\t/**\n\t * The options to configure the atom. See {@link AtomOptions}.\n\t */\n\toptions?: AtomOptions<Value, Diff>\n): Atom<Value, Diff> {\n\treturn new _Atom(name, initialValue, options)\n}\n\n/**\n * Returns true if the given value is an {@link Atom}.\n *\n * @param value - The value to check\n * @returns True if the value is an Atom, false otherwise\n * @example\n * ```ts\n * const myAtom = atom('test', 42)\n * const notAtom = 'hello'\n *\n * console.log(isAtom(myAtom)) // true\n * console.log(isAtom(notAtom)) // false\n * ```\n * @public\n */\nexport function isAtom(value: unknown): value is Atom<unknown> {\n\treturn value instanceof _Atom\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAyB;AACzB,qBAAmC;AACnC,qBAA+C;AAC/C,2BAA8B;AAC9B,0BAAkE;AAClE,mBAAwD;AAmExD,MAAM,SAA6D;AAAA,EAClE,YACiB,MACR,SACR,SACC;AAHe;AACR;AAGR,SAAK,UAAU,SAAS,WAAW;AAEnC,QAAI,CAAC,QAAS;AAEd,QAAI,QAAQ,eAAe;AAC1B,WAAK,gBAAgB,IAAI,mCAAc,QAAQ,aAAa;AAAA,IAC7D;AAEA,SAAK,cAAc,QAAQ;AAAA,EAC5B;AAAA,EAbiB;AAAA,EACR;AAAA;AAAA,EAeA;AAAA;AAAA,EAGT;AAAA;AAAA,EAGA,uBAAmB,oCAAe;AAAA;AAAA,EAGlC,WAAW,IAAI,yBAAgB;AAAA;AAAA,EAG/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,4BAA4B,eAAgC;AAC3D,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM;AACL,2CAAmB,IAAI;AACvB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,IAAI,OAAc,MAAoB;AAErC,QAAI,KAAK,UAAU,KAAK,SAAS,KAAK,SAAK,uBAAO,KAAK,SAAS,KAAK,GAAG;AACvE,aAAO,KAAK;AAAA,IACb;AAOA,QAAI;AACJ,QAAI,KAAK,eAAe;AACvB,oBACC,SAAS,SACN,OACA,KAAK,cACJ,KAAK,YAAY,KAAK,SAAS,OAAO,KAAK,sBAAkB,oCAAe,IAAI,CAAC,IACjF;AAAA,IACN;AAKA,gDAAmB;AACnB,UAAM,YAAQ,oCAAe;AAG7B,QAAI,KAAK,eAAe;AACvB,WAAK,cAAc,UAAU,KAAK,kBAAkB,OAAO,WAAW;AAAA,IACvE;AAGA,SAAK,mBAAmB;AAExB,UAAM,WAAW,KAAK;AACtB,SAAK,UAAU;AAGf,2CAAc,MAAa,QAAQ;AAEnC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,OAAO,SAAyC;AAC/C,WAAO,KAAK,IAAI,QAAQ,KAAK,OAAO,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,aAAa,OAAqC;AACjD,2CAAmB,IAAI;AAEvB,QAAI,SAAS,KAAK,kBAAkB;AACnC,aAAO;AAAA,IACR;AAEA,WAAO,KAAK,eAAe,gBAAgB,KAAK,KAAK;AAAA,EACtD;AACD;AAMO,MAAM,YAAQ,0BAAU,QAAQ,MAAM,QAAQ;AA0B9C,SAAS,KAIf,MAIA,cAIA,SACoB;AACpB,SAAO,IAAI,MAAM,MAAM,cAAc,OAAO;AAC7C;AAiBO,SAAS,OAAO,OAAwC;AAC9D,SAAO,iBAAiB;AACzB;",
4
+ "sourcesContent": ["import { ArraySet } from './ArraySet'\nimport { maybeCaptureParent } from './capture'\nimport { EMPTY_ARRAY, equals, singleton } from './helpers'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { advanceGlobalEpoch, atomDidChange, getGlobalEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\n\n/**\n * The options to configure an atom, passed into the {@link atom} function.\n * @public\n */\nexport interface AtomOptions<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 passed to {@link Atom.set} or produced by {@link AtomOptions.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 atom's old and new values. If provided, it will not be used unless you also specify {@link AtomOptions.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 atom 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 * An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.\n *\n * Atoms are created using the {@link atom} function.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n *\n * print(name.get()) // 'John'\n * ```\n *\n * @public\n */\nexport interface Atom<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Sets the value of this atom to the given value. If the value is the same as the current value, this is a no-op.\n\t *\n\t * @param value - The new value to set.\n\t * @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}.\n\t */\n\tset(value: Value, diff?: Diff): Value\n\t/**\n\t * Updates the value of this atom using the given updater function. If the returned value is the same as the current value, this is a no-op.\n\t *\n\t * @param updater - A function that takes the current value and returns the new value.\n\t */\n\tupdate(updater: (value: Value) => Value): Value\n}\n\n/**\n * Internal implementation of the Atom interface. This class should not be used directly - use the {@link atom} function instead.\n *\n * @internal\n */\nclass __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tprivate current: Value,\n\t\toptions?: AtomOptions<Value, Diff>\n\t) {\n\t\tthis.isEqual = options?.isEqual ?? equals\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}\n\n\t/** @internal */\n\treadonly isEqual: (a: any, b: any) => boolean\n\n\t/** @internal */\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\n\t/** @internal */\n\tlastChangedEpoch = getGlobalEpoch()\n\n\t/** @internal */\n\tchildren = new ArraySet<Child>()\n\n\t/** @internal */\n\thistoryBuffer?: HistoryBuffer<Diff>\n\n\t/**\n\t * Gets the current value without capturing it as a dependency in the current reactive context.\n\t * This is unsafe because it breaks the reactivity chain - use with caution.\n\t *\n\t * @param _ignoreErrors - Unused parameter for API compatibility\n\t * @returns The current value\n\t * @internal\n\t */\n\t__unsafe__getWithoutCapture(_ignoreErrors?: boolean): Value {\n\t\treturn this.current\n\t}\n\n\t/**\n\t * Gets the current value of this atom. When called within a computed signal or reaction,\n\t * this atom will be automatically captured as a dependency.\n\t *\n\t * @returns The current value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 5)\n\t * console.log(count.get()) // 5\n\t * ```\n\t */\n\tget() {\n\t\tmaybeCaptureParent(this)\n\t\treturn this.current\n\t}\n\n\t/**\n\t * Sets the value of this atom to the given value. If the value is the same as the current value, this is a no-op.\n\t *\n\t * @param value - The new value to set\n\t * @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}\n\t * @returns The new value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 0)\n\t * count.set(5) // count.get() is now 5\n\t * ```\n\t */\n\tset(value: Value, diff?: Diff): Value {\n\t\t// If the value has not changed, do nothing.\n\t\tif (this.isEqual(this.current, value)) {\n\t\t\treturn this.current\n\t\t}\n\n\t\t// `computeDiff` is user code: run it before ticking the epoch, so that any signal it reads is\n\t\t// checked against the epoch this write has not yet happened in. Reading a dependent computed\n\t\t// after the tick would stamp it as checked at the new epoch while the atom was still being\n\t\t// written, and it would then never see the change. Only `undefined` means \"no diff\n\t\t// supplied\"; `null` can be a legitimate diff.\n\t\tlet historyDiff: Diff | RESET_VALUE | undefined\n\t\tif (this.historyBuffer) {\n\t\t\thistoryDiff =\n\t\t\t\tdiff !== undefined\n\t\t\t\t\t? diff\n\t\t\t\t\t: this.computeDiff\n\t\t\t\t\t\t? this.computeDiff(this.current, value, this.lastChangedEpoch, getGlobalEpoch() + 1)\n\t\t\t\t\t\t: RESET_VALUE\n\t\t}\n\n\t\t// Tick forward the global epoch. This write belongs to that one epoch, so read it once and\n\t\t// use it everywhere below \u2014 otherwise a `computeDiff` that touches other atoms could leave\n\t\t// the history entry and `lastChangedEpoch` disagreeing.\n\t\tadvanceGlobalEpoch()\n\t\tconst epoch = getGlobalEpoch()\n\n\t\t// Add the diff to the history buffer.\n\t\tif (this.historyBuffer) {\n\t\t\tthis.historyBuffer.pushEntry(this.lastChangedEpoch, epoch, historyDiff)\n\t\t}\n\n\t\t// Update the atom's record of the epoch when last changed.\n\t\tthis.lastChangedEpoch = epoch\n\n\t\tconst oldValue = this.current\n\t\tthis.current = value\n\n\t\t// Notify all children that this atom has changed.\n\t\tatomDidChange(this as any, oldValue)\n\n\t\treturn value\n\t}\n\n\t/**\n\t * Updates the value of this atom using the given updater function. If the returned value is the same as the current value, this is a no-op.\n\t *\n\t * @param updater - A function that takes the current value and returns the new value\n\t * @returns The new value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 5)\n\t * count.update(n => n + 1) // count.get() is now 6\n\t * ```\n\t */\n\tupdate(updater: (value: Value) => Value): Value {\n\t\treturn this.set(updater(this.current))\n\t}\n\n\t/**\n\t * Gets all the diffs that have occurred since the given epoch. When called within a computed\n\t * signal or reaction, this atom will be automatically captured as a dependency.\n\t *\n\t * @param epoch - The epoch to get changes since\n\t * @returns An array of diffs, or RESET_VALUE if history is insufficient\n\t * @internal\n\t */\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[] {\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 Atom constructor. Used internally to create atom instances.\n * @internal\n */\nexport const _Atom = singleton('Atom', () => __Atom__)\n\n/**\n * Type alias for instances of the internal Atom class.\n * @internal\n */\nexport type _Atom = InstanceType<typeof _Atom>\n\n/**\n * Creates a new {@link Atom}.\n *\n * An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n *\n * name.get() // 'John'\n *\n * name.set('Jane')\n *\n * name.get() // 'Jane'\n * ```\n *\n * @public\n */\nexport function atom<Value, Diff = unknown>(\n\t/**\n\t * A name for the signal. This is used for debugging and profiling purposes, it does not need to be unique.\n\t */\n\tname: string,\n\t/**\n\t * The initial value of the signal.\n\t */\n\tinitialValue: Value,\n\t/**\n\t * The options to configure the atom. See {@link AtomOptions}.\n\t */\n\toptions?: AtomOptions<Value, Diff>\n): Atom<Value, Diff> {\n\treturn new _Atom(name, initialValue, options)\n}\n\n/**\n * Returns true if the given value is an {@link Atom}.\n *\n * @param value - The value to check\n * @returns True if the value is an Atom, false otherwise\n * @example\n * ```ts\n * const myAtom = atom('test', 42)\n * const notAtom = 'hello'\n *\n * console.log(isAtom(myAtom)) // true\n * console.log(isAtom(notAtom)) // false\n * ```\n * @public\n */\nexport function isAtom(value: unknown): value is Atom<unknown> {\n\treturn value instanceof _Atom\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAyB;AACzB,qBAAmC;AACnC,qBAA+C;AAC/C,2BAA8B;AAC9B,0BAAkE;AAClE,mBAAwD;AAmExD,MAAM,SAA6D;AAAA,EAClE,YACiB,MACR,SACR,SACC;AAHe;AACR;AAGR,SAAK,UAAU,SAAS,WAAW;AACnC,QAAI,SAAS,eAAe;AAC3B,WAAK,gBAAgB,IAAI,mCAAc,QAAQ,aAAa;AAAA,IAC7D;AACA,SAAK,cAAc,SAAS;AAAA,EAC7B;AAAA,EATiB;AAAA,EACR;AAAA;AAAA,EAWA;AAAA;AAAA,EAGT;AAAA;AAAA,EAGA,uBAAmB,oCAAe;AAAA;AAAA,EAGlC,WAAW,IAAI,yBAAgB;AAAA;AAAA,EAG/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,4BAA4B,eAAgC;AAC3D,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM;AACL,2CAAmB,IAAI;AACvB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,IAAI,OAAc,MAAoB;AAErC,QAAI,KAAK,QAAQ,KAAK,SAAS,KAAK,GAAG;AACtC,aAAO,KAAK;AAAA,IACb;AAOA,QAAI;AACJ,QAAI,KAAK,eAAe;AACvB,oBACC,SAAS,SACN,OACA,KAAK,cACJ,KAAK,YAAY,KAAK,SAAS,OAAO,KAAK,sBAAkB,oCAAe,IAAI,CAAC,IACjF;AAAA,IACN;AAKA,gDAAmB;AACnB,UAAM,YAAQ,oCAAe;AAG7B,QAAI,KAAK,eAAe;AACvB,WAAK,cAAc,UAAU,KAAK,kBAAkB,OAAO,WAAW;AAAA,IACvE;AAGA,SAAK,mBAAmB;AAExB,UAAM,WAAW,KAAK;AACtB,SAAK,UAAU;AAGf,2CAAc,MAAa,QAAQ;AAEnC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,OAAO,SAAyC;AAC/C,WAAO,KAAK,IAAI,QAAQ,KAAK,OAAO,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,aAAa,OAAqC;AACjD,2CAAmB,IAAI;AAEvB,QAAI,SAAS,KAAK,kBAAkB;AACnC,aAAO;AAAA,IACR;AAEA,WAAO,KAAK,eAAe,gBAAgB,KAAK,KAAK;AAAA,EACtD;AACD;AAMO,MAAM,YAAQ,0BAAU,QAAQ,MAAM,QAAQ;AA0B9C,SAAS,KAIf,MAIA,cAIA,SACoB;AACpB,SAAO,IAAI,MAAM,MAAM,cAAc,OAAO;AAC7C;AAiBO,SAAS,OAAO,OAAwC;AAC9D,SAAO,iBAAiB;AACzB;",
6
6
  "names": []
7
7
  }
@@ -62,50 +62,52 @@ function startCapturingParents(child) {
62
62
  function stopCapturingParents() {
63
63
  const frame = inst.stack;
64
64
  inst.stack = frame.below;
65
- if (frame.offset < frame.child.parents.length) {
66
- for (let i = frame.offset; i < frame.child.parents.length; i++) {
67
- const maybeRemovedParent = frame.child.parents[i];
68
- if (!frame.child.parentSet.has(maybeRemovedParent)) {
69
- (0, import_helpers.detach)(maybeRemovedParent, frame.child);
65
+ const { child, offset, maybeRemoved } = frame;
66
+ if (offset < child.parents.length) {
67
+ for (let i = offset; i < child.parents.length; i++) {
68
+ const maybeRemovedParent = child.parents[i];
69
+ if (!child.parentSet.has(maybeRemovedParent)) {
70
+ (0, import_helpers.detach)(maybeRemovedParent, child);
70
71
  }
71
72
  }
72
- frame.child.parents.length = frame.offset;
73
- frame.child.parentEpochs.length = frame.offset;
73
+ child.parents.length = offset;
74
+ child.parentEpochs.length = offset;
74
75
  }
75
- if (frame.maybeRemoved) {
76
- for (let i = 0; i < frame.maybeRemoved.length; i++) {
77
- const maybeRemovedParent = frame.maybeRemoved[i];
78
- if (!frame.child.parentSet.has(maybeRemovedParent)) {
79
- (0, import_helpers.detach)(maybeRemovedParent, frame.child);
76
+ if (maybeRemoved) {
77
+ for (let i = 0; i < maybeRemoved.length; i++) {
78
+ const maybeRemovedParent = maybeRemoved[i];
79
+ if (!child.parentSet.has(maybeRemovedParent)) {
80
+ (0, import_helpers.detach)(maybeRemovedParent, child);
80
81
  }
81
82
  }
82
83
  }
83
- if (frame.child.__debug_ancestor_epochs__) {
84
- captureAncestorEpochs(frame.child, frame.child.__debug_ancestor_epochs__);
84
+ if (child.__debug_ancestor_epochs__) {
85
+ captureAncestorEpochs(child, child.__debug_ancestor_epochs__);
85
86
  }
86
87
  }
87
88
  function maybeCaptureParent(p) {
88
- if (inst.stack) {
89
- if (!inst.stack.child.parentSet.add(p)) {
90
- return;
91
- }
92
- if (inst.stack.child.isActivelyListening) {
93
- (0, import_helpers.attach)(p, inst.stack.child);
94
- }
95
- if (inst.stack.offset < inst.stack.child.parents.length) {
96
- const maybeRemovedParent = inst.stack.child.parents[inst.stack.offset];
97
- if (maybeRemovedParent !== p) {
98
- if (!inst.stack.maybeRemoved) {
99
- inst.stack.maybeRemoved = [maybeRemovedParent];
100
- } else {
101
- inst.stack.maybeRemoved.push(maybeRemovedParent);
102
- }
89
+ const stack = inst.stack;
90
+ if (!stack) return;
91
+ const { child } = stack;
92
+ if (!child.parentSet.add(p)) {
93
+ return;
94
+ }
95
+ if (child.isActivelyListening) {
96
+ (0, import_helpers.attach)(p, child);
97
+ }
98
+ if (stack.offset < child.parents.length) {
99
+ const maybeRemovedParent = child.parents[stack.offset];
100
+ if (maybeRemovedParent !== p) {
101
+ if (!stack.maybeRemoved) {
102
+ stack.maybeRemoved = [maybeRemovedParent];
103
+ } else {
104
+ stack.maybeRemoved.push(maybeRemovedParent);
103
105
  }
104
106
  }
105
- inst.stack.child.parents[inst.stack.offset] = p;
106
- inst.stack.child.parentEpochs[inst.stack.offset] = p.lastChangedEpoch;
107
- inst.stack.offset++;
108
107
  }
108
+ child.parents[stack.offset] = p;
109
+ child.parentEpochs[stack.offset] = p.lastChangedEpoch;
110
+ stack.offset++;
109
111
  }
110
112
  function whyAmIRunning() {
111
113
  const child = inst.stack?.child;
@@ -117,13 +119,11 @@ function whyAmIRunning() {
117
119
  function captureAncestorEpochs(child, ancestorEpochs) {
118
120
  for (let i = 0; i < child.parents.length; i++) {
119
121
  const parent = child.parents[i];
120
- const epoch = child.parentEpochs[i];
121
- ancestorEpochs.set(parent, epoch);
122
+ ancestorEpochs.set(parent, child.parentEpochs[i]);
122
123
  if ((0, import_isComputed.isComputed)(parent)) {
123
124
  captureAncestorEpochs(parent, ancestorEpochs);
124
125
  }
125
126
  }
126
- return ancestorEpochs;
127
127
  }
128
128
  function collectChangedAncestors(child, ancestorEpochs) {
129
129
  const changeTree = {};
@@ -132,9 +132,7 @@ function collectChangedAncestors(child, ancestorEpochs) {
132
132
  if (!ancestorEpochs.has(parent)) {
133
133
  continue;
134
134
  }
135
- const prevEpoch = ancestorEpochs.get(parent);
136
- const currentEpoch = parent.lastChangedEpoch;
137
- if (currentEpoch !== prevEpoch) {
135
+ if (parent.lastChangedEpoch !== ancestorEpochs.get(parent)) {
138
136
  if ((0, import_isComputed.isComputed)(parent)) {
139
137
  changeTree[parent.name] = collectChangedAncestors(parent, ancestorEpochs);
140
138
  } else {
@@ -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', 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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",
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\tconst { child, offset, maybeRemoved } = frame\n\n\tif (offset < child.parents.length) {\n\t\tfor (let i = offset; i < child.parents.length; i++) {\n\t\t\tconst maybeRemovedParent = child.parents[i]\n\t\t\tif (!child.parentSet.has(maybeRemovedParent)) {\n\t\t\t\tdetach(maybeRemovedParent, child)\n\t\t\t}\n\t\t}\n\n\t\tchild.parents.length = offset\n\t\tchild.parentEpochs.length = offset\n\t}\n\n\tif (maybeRemoved) {\n\t\tfor (let i = 0; i < maybeRemoved.length; i++) {\n\t\t\tconst maybeRemovedParent = maybeRemoved[i]\n\t\t\tif (!child.parentSet.has(maybeRemovedParent)) {\n\t\t\t\tdetach(maybeRemovedParent, child)\n\t\t\t}\n\t\t}\n\t}\n\n\tif (child.__debug_ancestor_epochs__) {\n\t\tcaptureAncestorEpochs(child, 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\tconst stack = inst.stack\n\tif (!stack) return\n\tconst { child } = stack\n\n\t// `add` returns false when the parent was already captured this run. In array mode both\n\t// `has` and `add` scan with indexOf, so going straight to `add` halves the scans per\n\t// captured parent.\n\tif (!child.parentSet.add(p)) {\n\t\treturn\n\t}\n\n\tif (child.isActivelyListening) {\n\t\tattach(p, child)\n\t}\n\n\t// Parents are recorded in deref order. If the slot at this offset held a different parent\n\t// last run, that parent may have moved later in the order or been dropped; only\n\t// stopCapturingParents can tell, so remember it for then.\n\tif (stack.offset < child.parents.length) {\n\t\tconst maybeRemovedParent = child.parents[stack.offset]\n\t\tif (maybeRemovedParent !== p) {\n\t\t\tif (!stack.maybeRemoved) {\n\t\t\t\tstack.maybeRemoved = [maybeRemovedParent]\n\t\t\t} else {\n\t\t\t\tstack.maybeRemoved.push(maybeRemovedParent)\n\t\t\t}\n\t\t}\n\t}\n\n\tchild.parents[stack.offset] = p\n\tchild.parentEpochs[stack.offset] = p.lastChangedEpoch\n\tstack.offset++\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\tancestorEpochs.set(parent, child.parentEpochs[i])\n\t\tif (isComputed(parent)) {\n\t\t\tcaptureAncestorEpochs(parent as any, ancestorEpochs)\n\t\t}\n\t}\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\tif (parent.lastChangedEpoch !== ancestorEpochs.get(parent)) {\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
  }
@@ -60,8 +60,7 @@ function attach(parent, child) {
60
60
  }
61
61
  }
62
62
  function equals(a, b) {
63
- const shallowEquals = a === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === "function" && a.equals(b));
64
- return shallowEquals;
63
+ return a === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === "function" && a.equals(b));
65
64
  }
66
65
  function singleton(key, init) {
67
66
  const symbol = /* @__PURE__ */ Symbol.for(`com.tldraw.state/${key}`);
@@ -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\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 * `parent` must be up to date when it is attached: `Computed` assumes an actively-listening\n * computed has been traversed for every ancestor change since it was last checked, which only\n * holds from the moment it started listening. Capture attaches a parent right after reading it;\n * `EffectScheduler.attach` refreshes its parents first.\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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAaO,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;",
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 * `parent` must be up to date when it is attached: `Computed` assumes an actively-listening\n * computed has been traversed for every ancestor change since it was last checked, which only\n * holds from the moment it started listening. Capture attaches a parent right after reading it;\n * `EffectScheduler.attach` refreshes its parents first.\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\treturn (\n\t\ta === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === 'function' && a.equals(b))\n\t)\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": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAaO,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,SACC,MAAM,KAAK,OAAO,GAAG,GAAG,CAAC,KAAK,QAAQ,KAAK,KAAK,OAAO,EAAE,WAAW,cAAc,EAAE,OAAO,CAAC,CAAC;AAE/F;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
  }
@@ -38,12 +38,8 @@ class Transaction {
38
38
  isSync;
39
39
  asyncProcessCount = 0;
40
40
  initialAtomValues = /* @__PURE__ */ new Map();
41
- // eslint-disable-next-line tldraw/no-setter-getter
42
- get isRoot() {
43
- return this.parent === null;
44
- }
45
41
  commit() {
46
- if (this.isRoot) {
42
+ if (this.parent === null) {
47
43
  flushChanges(this.initialAtomValues.keys());
48
44
  return;
49
45
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/transactions.ts"],
4
- "sourcesContent": ["import { _Atom } from './Atom'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { singleton } from './helpers'\nimport { Child, Signal } from './types'\n\ninterface Reactor {\n\tmaybeScheduleEffect(): void\n\tlastTraversedEpoch: number\n}\n\nclass Transaction {\n\tasyncProcessCount = 0\n\tconstructor(\n\t\tpublic readonly parent: Transaction | null,\n\t\tpublic readonly isSync: boolean\n\t) {}\n\n\tinitialAtomValues = new Map<_Atom, any>()\n\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget isRoot() {\n\t\treturn this.parent === null\n\t}\n\n\tcommit() {\n\t\tif (this.isRoot) {\n\t\t\t// For root transactions, flush changed atoms\n\t\t\tflushChanges(this.initialAtomValues.keys())\n\t\t\treturn\n\t\t}\n\t\t// For transactions with parents, add the transaction's initial values to the parent's, so\n\t\t// an outer rollback can still undo them (T7). This must come before any \"are we reacting?\" special case: a nested\n\t\t// transaction committed by an effect during the reaction phase used to skip this fold and\n\t\t// its changes survived the outer rollback.\n\t\t// A parent that has recorded nothing yet adopts the map outright: this transaction is\n\t\t// finished with it, and the common nested case is a single inner transaction doing all\n\t\t// the writes.\n\t\tconst parentValues = this.parent!.initialAtomValues\n\t\tif (parentValues.size === 0) {\n\t\t\tthis.parent!.initialAtomValues = this.initialAtomValues\n\t\t\treturn\n\t\t}\n\t\tthis.initialAtomValues.forEach((value, atom) => {\n\t\t\tif (!parentValues.has(atom)) {\n\t\t\t\tparentValues.set(atom, value)\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Abort the transaction. Restores every atom changed in this transaction to its initial\n\t * value, then commits. Must be called while this is still `inst.currentTransaction`, so that\n\t * the restoring sets are recorded against it instead of each flushing effects on their own.\n\t *\n\t * @public\n\t */\n\tabort() {\n\t\tinst.globalEpoch++\n\n\t\tthis.initialAtomValues.forEach((value, atom) => {\n\t\t\tatom.set(value)\n\t\t\tatom.historyBuffer?.clear()\n\t\t})\n\n\t\tthis.commit()\n\t}\n}\n\nconst inst = singleton('transactions', () => ({\n\t// The current epoch (global to all atoms).\n\tglobalEpoch: GLOBAL_START_EPOCH + 1,\n\t// Whether any transaction is reacting.\n\tglobalIsReacting: false,\n\tcurrentTransaction: null as Transaction | null,\n\n\tcleanupReactors: null as null | Set<Reactor>,\n}))\n\n/**\n * Gets the current global epoch, which is incremented every time any atom changes.\n * This is used to track changes across the entire reactive system.\n *\n * @returns The current global epoch number\n * @public\n */\nexport function getGlobalEpoch() {\n\treturn inst.globalEpoch\n}\n\n/**\n * Whether a transaction (sync or async) is currently open. While one is, atom changes are\n * recorded but their children are not traversed until it commits.\n *\n * @internal\n */\nexport function getIsInTransaction() {\n\treturn inst.currentTransaction !== null\n}\n\n// The set `traverseChild` collects reactors into. Module-level rather than a closure so that a\n// flush over thousands of atoms doesn't allocate a visitor per atom; traversal never runs user\n// code, so nothing can re-enter and swap it mid-walk.\nlet traverseReactors: Set<Reactor>\n\nfunction traverseChild(child: Child) {\n\tif (child.lastTraversedEpoch === inst.globalEpoch) {\n\t\treturn\n\t}\n\n\tchild.lastTraversedEpoch = inst.globalEpoch\n\n\tif ('__isEffectScheduler' in child) {\n\t\ttraverseReactors.add(child as unknown as Reactor)\n\t} else {\n\t\t;(child as any as Signal<any>).children.visit(traverseChild)\n\t}\n}\n\n/**\n * Collect all of the reactors that need to run for an atom and run them. During the reaction\n * phase the affected effects are instead queued for the cleanup pass, so a change made by an\n * effect never interrupts the pass that is running (P4).\n *\n * @param atoms - The atoms to flush changes for.\n */\nfunction flushChanges(atoms: Iterable<_Atom>) {\n\tif (inst.globalIsReacting) {\n\t\tfor (const atom of atoms) {\n\t\t\ttraverseAtomForCleanup(atom)\n\t\t}\n\t\treturn\n\t}\n\n\tconst outerTxn = inst.currentTransaction\n\ttry {\n\t\t// clear the transaction stack. Transactions started by effects must be roots: the committing\n\t\t// transaction (if any) has already handed its changes to this flush.\n\t\tinst.currentTransaction = null\n\t\tinst.globalIsReacting = true\n\n\t\tconst reactors = new Set<Reactor>()\n\t\ttraverseReactors = reactors\n\t\tfor (const atom of atoms) {\n\t\t\tatom.children.visit(traverseChild)\n\t\t}\n\n\t\t// Run each reactor.\n\t\tfor (const r of reactors) {\n\t\t\tr.maybeScheduleEffect()\n\t\t}\n\n\t\tlet updateDepth = 0\n\t\twhile (inst.cleanupReactors?.size) {\n\t\t\tif (updateDepth++ > 1000) {\n\t\t\t\tthrow new Error('Reaction update depth limit exceeded')\n\t\t\t}\n\t\t\tconst reactors = inst.cleanupReactors\n\t\t\tinst.cleanupReactors = null\n\t\t\tfor (const r of reactors) {\n\t\t\t\tr.maybeScheduleEffect()\n\t\t\t}\n\t\t}\n\t} finally {\n\t\tinst.cleanupReactors = null\n\t\tinst.globalIsReacting = false\n\t\tinst.currentTransaction = outerTxn\n\t\ttraverseReactors = undefined! // free memory\n\t}\n}\n\n/** @internal */\nexport function atomDidChange(atom: _Atom, previousValue: any) {\n\tif (inst.currentTransaction) {\n\t\t// If we are in a transaction, then all we have to do is preserve\n\t\t// the value of the atom at the start of the transaction in case\n\t\t// we need to roll back. Children are not traversed until the\n\t\t// transaction commits, which is why `Computed` must not trust its\n\t\t// traversal-based cache shortcut while a transaction is open.\n\t\tif (!inst.currentTransaction.initialAtomValues.has(atom)) {\n\t\t\tinst.currentTransaction.initialAtomValues.set(atom, previousValue)\n\t\t}\n\t} else {\n\t\t// If there is no transaction, flush the changes immediately.\n\t\tflushChanges([atom])\n\t}\n}\n\nfunction traverseAtomForCleanup(atom: _Atom) {\n\ttraverseReactors = inst.cleanupReactors ??= new Set()\n\tatom.children.visit(traverseChild)\n}\n\n/** @internal */\nexport function advanceGlobalEpoch() {\n\tinst.globalEpoch++\n}\n\n/**\n * Batches state updates, deferring side effects until after the transaction completes.\n * Unlike {@link transact}, this function always creates a new transaction, allowing for nested transactions.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction(() => {\n * firstName.set('Jane')\n * lastName.set('Smith')\n * })\n *\n * // Logs \"Hello, Jane Smith!\"\n * ```\n *\n * If the function throws, the transaction is aborted and any signals that were updated during the transaction revert to their state before the transaction began. An aborted transaction still flushes effects: effects whose parents went through a change-and-restore round trip are checked again and, if a parent's value differs from what they last saw (an atom they read directly always will), run once more with the restored values.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction(() => {\n * firstName.set('Jane')\n * throw new Error('oops')\n * })\n *\n * // firstName.get() === 'John'\n * // Logs \"Hello, John Doe!\" again: effects whose parents were changed and restored still run,\n * // and observe the restored values.\n * ```\n *\n * A `rollback` callback is passed into the function.\n * Calling this will prevent the transaction from committing and will revert any signals that were updated during the transaction to their state before the transaction began.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction((rollback) => {\n * firstName.set('Jane')\n * lastName.set('Smith')\n * rollback()\n * })\n *\n * // firstName.get() === 'John'\n * // lastName.get() === 'Doe'\n * // Logs \"Hello, John Doe!\" again, as above.\n * ```\n *\n * @param fn - The function to run in a transaction, called with a function to roll back the change.\n * @returns The return value of the function\n * @public\n */\nexport function transaction<T>(fn: (rollback: () => void) => T) {\n\tconst txn = new Transaction(inst.currentTransaction, true)\n\n\tinst.currentTransaction = txn\n\n\ttry {\n\t\tlet result = undefined as T | undefined\n\t\tlet rollback = false\n\n\t\ttry {\n\t\t\tresult = fn(() => (rollback = true))\n\t\t} catch (e) {\n\t\t\ttxn.abort()\n\t\t\tthrow e\n\t\t}\n\n\t\tif (inst.currentTransaction !== txn) {\n\t\t\tthrow new Error('Transaction boundaries overlap')\n\t\t}\n\n\t\tif (rollback) {\n\t\t\ttxn.abort()\n\t\t} else {\n\t\t\ttxn.commit()\n\t\t}\n\n\t\treturn result\n\t} finally {\n\t\tinst.currentTransaction = txn.parent\n\t}\n}\n\n/**\n * Like {@link transaction}, but does not create a new transaction if there is already one in progress.\n * This is the preferred way to batch state updates when you don't need the rollback functionality.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const doubled = atom('doubled', 0)\n *\n * react('update doubled', () => {\n * console.log(`Count: ${count.get()}, Doubled: ${doubled.get()}`)\n * })\n *\n * // This batches both updates into a single reaction\n * transact(() => {\n * count.set(5)\n * doubled.set(count.get() * 2)\n * })\n * // Logs: \"Count: 5, Doubled: 10\"\n * ```\n *\n * @param fn - The function to run in a transaction\n * @returns The return value of the function\n * @public\n */\nexport function transact<T>(fn: () => T): T {\n\tif (inst.currentTransaction) {\n\t\treturn fn()\n\t}\n\treturn transaction(fn)\n}\n\n/**\n * Defers the execution of asynchronous effects until they can be properly handled.\n * This function creates an asynchronous transaction context that batches state updates\n * across async operations while preventing conflicts with synchronous transactions.\n *\n * @example\n * ```ts\n * const data = atom('data', null)\n * const loading = atom('loading', false)\n *\n * await deferAsyncEffects(async () => {\n * loading.set(true)\n * const result = await fetch('/api/data')\n * const json = await result.json()\n * data.set(json)\n * loading.set(false)\n * })\n * ```\n *\n * @param fn - The async function to execute within the deferred context\n * @returns A promise that resolves to the return value of the function\n * @throws Will throw if called during a synchronous transaction\n * @internal\n */\nexport async function deferAsyncEffects<T>(fn: () => Promise<T>) {\n\t// Can't kick off async transactions during a sync transaction because\n\t// the async transaction won't finish until after the sync transaction\n\t// is done.\n\tif (inst.currentTransaction?.isSync) {\n\t\tthrow new Error('deferAsyncEffects cannot be called during a sync transaction')\n\t}\n\n\t// Can't kick off async transactions during a reaction phase at the moment,\n\t// because the transaction stack is cleared after the reaction phase.\n\t// So wait until the path ahead is clear\n\twhile (inst.globalIsReacting) {\n\t\tawait new Promise((r) => queueMicrotask(() => r(null)))\n\t}\n\n\tconst txn = inst.currentTransaction ?? new Transaction(null, false)\n\n\t// don't think this can happen, but just in case\n\tif (txn.isSync) throw new Error('deferAsyncEffects cannot be called during a sync transaction')\n\n\tinst.currentTransaction = txn\n\ttxn.asyncProcessCount++\n\n\tlet result = undefined as T | undefined\n\n\t// `undefined` means \"did not throw\"; a thrown `undefined` is stored as `null` so it still counts.\n\tlet error = undefined as any\n\ttry {\n\t\t// Run the function.\n\t\tresult = await fn()\n\t} catch (e) {\n\t\t// Abort the transaction if the function throws.\n\t\terror = e ?? null\n\t}\n\n\tif (--txn.asyncProcessCount > 0) {\n\t\t// Other processes still share this transaction; it settles when the last one finishes.\n\t\tif (typeof error !== 'undefined') {\n\t\t\t// If the rollback was triggered, abort the transaction.\n\t\t\tthrow error\n\t\t} else {\n\t\t\treturn result\n\t\t}\n\t}\n\n\t// Settle while this is still the current transaction (see `Transaction.abort`): clearing it\n\t// first made every restoring set inside `abort()` flush effects on its own, so effects\n\t// observed half-restored state.\n\ttry {\n\t\tif (typeof error !== 'undefined') {\n\t\t\t// If the rollback was triggered, abort the transaction.\n\t\t\ttxn.abort()\n\t\t\tthrow error\n\t\t} else {\n\t\t\ttxn.commit()\n\t\t\treturn result\n\t\t}\n\t} finally {\n\t\tinst.currentTransaction = null\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { _Atom } from './Atom'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { singleton } from './helpers'\nimport { Child, Signal } from './types'\n\ninterface Reactor {\n\tmaybeScheduleEffect(): void\n\tlastTraversedEpoch: number\n}\n\nclass Transaction {\n\tasyncProcessCount = 0\n\tconstructor(\n\t\tpublic readonly parent: Transaction | null,\n\t\tpublic readonly isSync: boolean\n\t) {}\n\n\tinitialAtomValues = new Map<_Atom, any>()\n\n\tcommit() {\n\t\tif (this.parent === null) {\n\t\t\t// For root transactions, flush changed atoms\n\t\t\tflushChanges(this.initialAtomValues.keys())\n\t\t\treturn\n\t\t}\n\t\t// For transactions with parents, add the transaction's initial values to the parent's, so\n\t\t// an outer rollback can still undo them (T7). This must come before any \"are we reacting?\" special case: a nested\n\t\t// transaction committed by an effect during the reaction phase used to skip this fold and\n\t\t// its changes survived the outer rollback.\n\t\t// A parent that has recorded nothing yet adopts the map outright: this transaction is\n\t\t// finished with it, and the common nested case is a single inner transaction doing all\n\t\t// the writes.\n\t\tconst parentValues = this.parent!.initialAtomValues\n\t\tif (parentValues.size === 0) {\n\t\t\tthis.parent!.initialAtomValues = this.initialAtomValues\n\t\t\treturn\n\t\t}\n\t\tthis.initialAtomValues.forEach((value, atom) => {\n\t\t\tif (!parentValues.has(atom)) {\n\t\t\t\tparentValues.set(atom, value)\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Abort the transaction. Restores every atom changed in this transaction to its initial\n\t * value, then commits. Must be called while this is still `inst.currentTransaction`, so that\n\t * the restoring sets are recorded against it instead of each flushing effects on their own.\n\t *\n\t * @public\n\t */\n\tabort() {\n\t\tinst.globalEpoch++\n\n\t\tthis.initialAtomValues.forEach((value, atom) => {\n\t\t\tatom.set(value)\n\t\t\tatom.historyBuffer?.clear()\n\t\t})\n\n\t\tthis.commit()\n\t}\n}\n\nconst inst = singleton('transactions', () => ({\n\t// The current epoch (global to all atoms).\n\tglobalEpoch: GLOBAL_START_EPOCH + 1,\n\t// Whether any transaction is reacting.\n\tglobalIsReacting: false,\n\tcurrentTransaction: null as Transaction | null,\n\n\tcleanupReactors: null as null | Set<Reactor>,\n}))\n\n/**\n * Gets the current global epoch, which is incremented every time any atom changes.\n * This is used to track changes across the entire reactive system.\n *\n * @returns The current global epoch number\n * @public\n */\nexport function getGlobalEpoch() {\n\treturn inst.globalEpoch\n}\n\n/**\n * Whether a transaction (sync or async) is currently open. While one is, atom changes are\n * recorded but their children are not traversed until it commits.\n *\n * @internal\n */\nexport function getIsInTransaction() {\n\treturn inst.currentTransaction !== null\n}\n\n// The set `traverseChild` collects reactors into. Module-level rather than a closure so that a\n// flush over thousands of atoms doesn't allocate a visitor per atom; traversal never runs user\n// code, so nothing can re-enter and swap it mid-walk.\nlet traverseReactors: Set<Reactor>\n\nfunction traverseChild(child: Child) {\n\tif (child.lastTraversedEpoch === inst.globalEpoch) {\n\t\treturn\n\t}\n\n\tchild.lastTraversedEpoch = inst.globalEpoch\n\n\tif ('__isEffectScheduler' in child) {\n\t\ttraverseReactors.add(child as unknown as Reactor)\n\t} else {\n\t\t;(child as any as Signal<any>).children.visit(traverseChild)\n\t}\n}\n\n/**\n * Collect all of the reactors that need to run for an atom and run them. During the reaction\n * phase the affected effects are instead queued for the cleanup pass, so a change made by an\n * effect never interrupts the pass that is running (P4).\n *\n * @param atoms - The atoms to flush changes for.\n */\nfunction flushChanges(atoms: Iterable<_Atom>) {\n\tif (inst.globalIsReacting) {\n\t\tfor (const atom of atoms) {\n\t\t\ttraverseAtomForCleanup(atom)\n\t\t}\n\t\treturn\n\t}\n\n\tconst outerTxn = inst.currentTransaction\n\ttry {\n\t\t// clear the transaction stack. Transactions started by effects must be roots: the committing\n\t\t// transaction (if any) has already handed its changes to this flush.\n\t\tinst.currentTransaction = null\n\t\tinst.globalIsReacting = true\n\n\t\tconst reactors = new Set<Reactor>()\n\t\ttraverseReactors = reactors\n\t\tfor (const atom of atoms) {\n\t\t\tatom.children.visit(traverseChild)\n\t\t}\n\n\t\t// Run each reactor.\n\t\tfor (const r of reactors) {\n\t\t\tr.maybeScheduleEffect()\n\t\t}\n\n\t\tlet updateDepth = 0\n\t\twhile (inst.cleanupReactors?.size) {\n\t\t\tif (updateDepth++ > 1000) {\n\t\t\t\tthrow new Error('Reaction update depth limit exceeded')\n\t\t\t}\n\t\t\tconst reactors = inst.cleanupReactors\n\t\t\tinst.cleanupReactors = null\n\t\t\tfor (const r of reactors) {\n\t\t\t\tr.maybeScheduleEffect()\n\t\t\t}\n\t\t}\n\t} finally {\n\t\tinst.cleanupReactors = null\n\t\tinst.globalIsReacting = false\n\t\tinst.currentTransaction = outerTxn\n\t\ttraverseReactors = undefined! // free memory\n\t}\n}\n\n/** @internal */\nexport function atomDidChange(atom: _Atom, previousValue: any) {\n\tif (inst.currentTransaction) {\n\t\t// If we are in a transaction, then all we have to do is preserve\n\t\t// the value of the atom at the start of the transaction in case\n\t\t// we need to roll back. Children are not traversed until the\n\t\t// transaction commits, which is why `Computed` must not trust its\n\t\t// traversal-based cache shortcut while a transaction is open.\n\t\tif (!inst.currentTransaction.initialAtomValues.has(atom)) {\n\t\t\tinst.currentTransaction.initialAtomValues.set(atom, previousValue)\n\t\t}\n\t} else {\n\t\t// If there is no transaction, flush the changes immediately.\n\t\tflushChanges([atom])\n\t}\n}\n\nfunction traverseAtomForCleanup(atom: _Atom) {\n\ttraverseReactors = inst.cleanupReactors ??= new Set()\n\tatom.children.visit(traverseChild)\n}\n\n/** @internal */\nexport function advanceGlobalEpoch() {\n\tinst.globalEpoch++\n}\n\n/**\n * Batches state updates, deferring side effects until after the transaction completes.\n * Unlike {@link transact}, this function always creates a new transaction, allowing for nested transactions.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction(() => {\n * firstName.set('Jane')\n * lastName.set('Smith')\n * })\n *\n * // Logs \"Hello, Jane Smith!\"\n * ```\n *\n * If the function throws, the transaction is aborted and any signals that were updated during the transaction revert to their state before the transaction began. An aborted transaction still flushes effects: effects whose parents went through a change-and-restore round trip are checked again and, if a parent's value differs from what they last saw (an atom they read directly always will), run once more with the restored values.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction(() => {\n * firstName.set('Jane')\n * throw new Error('oops')\n * })\n *\n * // firstName.get() === 'John'\n * // Logs \"Hello, John Doe!\" again: effects whose parents were changed and restored still run,\n * // and observe the restored values.\n * ```\n *\n * A `rollback` callback is passed into the function.\n * Calling this will prevent the transaction from committing and will revert any signals that were updated during the transaction to their state before the transaction began.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction((rollback) => {\n * firstName.set('Jane')\n * lastName.set('Smith')\n * rollback()\n * })\n *\n * // firstName.get() === 'John'\n * // lastName.get() === 'Doe'\n * // Logs \"Hello, John Doe!\" again, as above.\n * ```\n *\n * @param fn - The function to run in a transaction, called with a function to roll back the change.\n * @returns The return value of the function\n * @public\n */\nexport function transaction<T>(fn: (rollback: () => void) => T) {\n\tconst txn = new Transaction(inst.currentTransaction, true)\n\n\tinst.currentTransaction = txn\n\n\ttry {\n\t\tlet result = undefined as T | undefined\n\t\tlet rollback = false\n\n\t\ttry {\n\t\t\tresult = fn(() => (rollback = true))\n\t\t} catch (e) {\n\t\t\ttxn.abort()\n\t\t\tthrow e\n\t\t}\n\n\t\tif (inst.currentTransaction !== txn) {\n\t\t\tthrow new Error('Transaction boundaries overlap')\n\t\t}\n\n\t\tif (rollback) {\n\t\t\ttxn.abort()\n\t\t} else {\n\t\t\ttxn.commit()\n\t\t}\n\n\t\treturn result\n\t} finally {\n\t\tinst.currentTransaction = txn.parent\n\t}\n}\n\n/**\n * Like {@link transaction}, but does not create a new transaction if there is already one in progress.\n * This is the preferred way to batch state updates when you don't need the rollback functionality.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const doubled = atom('doubled', 0)\n *\n * react('update doubled', () => {\n * console.log(`Count: ${count.get()}, Doubled: ${doubled.get()}`)\n * })\n *\n * // This batches both updates into a single reaction\n * transact(() => {\n * count.set(5)\n * doubled.set(count.get() * 2)\n * })\n * // Logs: \"Count: 5, Doubled: 10\"\n * ```\n *\n * @param fn - The function to run in a transaction\n * @returns The return value of the function\n * @public\n */\nexport function transact<T>(fn: () => T): T {\n\tif (inst.currentTransaction) {\n\t\treturn fn()\n\t}\n\treturn transaction(fn)\n}\n\n/**\n * Defers the execution of asynchronous effects until they can be properly handled.\n * This function creates an asynchronous transaction context that batches state updates\n * across async operations while preventing conflicts with synchronous transactions.\n *\n * @example\n * ```ts\n * const data = atom('data', null)\n * const loading = atom('loading', false)\n *\n * await deferAsyncEffects(async () => {\n * loading.set(true)\n * const result = await fetch('/api/data')\n * const json = await result.json()\n * data.set(json)\n * loading.set(false)\n * })\n * ```\n *\n * @param fn - The async function to execute within the deferred context\n * @returns A promise that resolves to the return value of the function\n * @throws Will throw if called during a synchronous transaction\n * @internal\n */\nexport async function deferAsyncEffects<T>(fn: () => Promise<T>) {\n\t// Can't kick off async transactions during a sync transaction because\n\t// the async transaction won't finish until after the sync transaction\n\t// is done.\n\tif (inst.currentTransaction?.isSync) {\n\t\tthrow new Error('deferAsyncEffects cannot be called during a sync transaction')\n\t}\n\n\t// Can't kick off async transactions during a reaction phase at the moment,\n\t// because the transaction stack is cleared after the reaction phase.\n\t// So wait until the path ahead is clear\n\twhile (inst.globalIsReacting) {\n\t\tawait new Promise((r) => queueMicrotask(() => r(null)))\n\t}\n\n\tconst txn = inst.currentTransaction ?? new Transaction(null, false)\n\n\t// don't think this can happen, but just in case\n\tif (txn.isSync) throw new Error('deferAsyncEffects cannot be called during a sync transaction')\n\n\tinst.currentTransaction = txn\n\ttxn.asyncProcessCount++\n\n\tlet result = undefined as T | undefined\n\n\t// `undefined` means \"did not throw\"; a thrown `undefined` is stored as `null` so it still counts.\n\tlet error = undefined as any\n\ttry {\n\t\t// Run the function.\n\t\tresult = await fn()\n\t} catch (e) {\n\t\t// Abort the transaction if the function throws.\n\t\terror = e ?? null\n\t}\n\n\tif (--txn.asyncProcessCount > 0) {\n\t\t// Other processes still share this transaction; it settles when the last one finishes.\n\t\tif (typeof error !== 'undefined') {\n\t\t\t// If the rollback was triggered, abort the transaction.\n\t\t\tthrow error\n\t\t} else {\n\t\t\treturn result\n\t\t}\n\t}\n\n\t// Settle while this is still the current transaction (see `Transaction.abort`): clearing it\n\t// first made every restoring set inside `abort()` flush effects on its own, so effects\n\t// observed half-restored state.\n\ttry {\n\t\tif (typeof error !== 'undefined') {\n\t\t\t// If the rollback was triggered, abort the transaction.\n\t\t\ttxn.abort()\n\t\t\tthrow error\n\t\t} else {\n\t\t\ttxn.commit()\n\t\t\treturn result\n\t\t}\n\t} finally {\n\t\tinst.currentTransaction = null\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["reactors"]
7
7
  }
@@ -25,7 +25,7 @@ if (actualApiVersion !== currentApiVersion) {
25
25
  }
26
26
  registerTldrawLibraryVersion(
27
27
  "@tldraw/state",
28
- "5.5.0-canary.f7ff7a688554",
28
+ "5.5.0-canary.fa4e171d600e",
29
29
  "esm"
30
30
  );
31
31
  export {
@@ -8,12 +8,11 @@ class __Atom__ {
8
8
  constructor(name, current, options) {
9
9
  this.name = name;
10
10
  this.current = current;
11
- this.isEqual = options?.isEqual ?? null;
12
- if (!options) return;
13
- if (options.historyLength) {
11
+ this.isEqual = options?.isEqual ?? equals;
12
+ if (options?.historyLength) {
14
13
  this.historyBuffer = new HistoryBuffer(options.historyLength);
15
14
  }
16
- this.computeDiff = options.computeDiff;
15
+ this.computeDiff = options?.computeDiff;
17
16
  }
18
17
  name;
19
18
  current;
@@ -66,7 +65,7 @@ class __Atom__ {
66
65
  * ```
67
66
  */
68
67
  set(value, diff) {
69
- if (this.isEqual?.(this.current, value) ?? equals(this.current, value)) {
68
+ if (this.isEqual(this.current, value)) {
70
69
  return this.current;
71
70
  }
72
71
  let historyDiff;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/Atom.ts"],
4
- "sourcesContent": ["import { ArraySet } from './ArraySet'\nimport { maybeCaptureParent } from './capture'\nimport { EMPTY_ARRAY, equals, singleton } from './helpers'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { advanceGlobalEpoch, atomDidChange, getGlobalEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\n\n/**\n * The options to configure an atom, passed into the {@link atom} function.\n * @public\n */\nexport interface AtomOptions<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 passed to {@link Atom.set} or produced by {@link AtomOptions.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 atom's old and new values. If provided, it will not be used unless you also specify {@link AtomOptions.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 atom 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 * An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.\n *\n * Atoms are created using the {@link atom} function.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n *\n * print(name.get()) // 'John'\n * ```\n *\n * @public\n */\nexport interface Atom<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Sets the value of this atom to the given value. If the value is the same as the current value, this is a no-op.\n\t *\n\t * @param value - The new value to set.\n\t * @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}.\n\t */\n\tset(value: Value, diff?: Diff): Value\n\t/**\n\t * Updates the value of this atom using the given updater function. If the returned value is the same as the current value, this is a no-op.\n\t *\n\t * @param updater - A function that takes the current value and returns the new value.\n\t */\n\tupdate(updater: (value: Value) => Value): Value\n}\n\n/**\n * Internal implementation of the Atom interface. This class should not be used directly - use the {@link atom} function instead.\n *\n * @internal\n */\nclass __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tprivate current: Value,\n\t\toptions?: AtomOptions<Value, Diff>\n\t) {\n\t\tthis.isEqual = options?.isEqual ?? null\n\n\t\tif (!options) return\n\n\t\tif (options.historyLength) {\n\t\t\tthis.historyBuffer = new HistoryBuffer(options.historyLength)\n\t\t}\n\n\t\tthis.computeDiff = options.computeDiff\n\t}\n\n\t/** @internal */\n\treadonly isEqual: null | ((a: any, b: any) => boolean)\n\n\t/** @internal */\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\n\t/** @internal */\n\tlastChangedEpoch = getGlobalEpoch()\n\n\t/** @internal */\n\tchildren = new ArraySet<Child>()\n\n\t/** @internal */\n\thistoryBuffer?: HistoryBuffer<Diff>\n\n\t/**\n\t * Gets the current value without capturing it as a dependency in the current reactive context.\n\t * This is unsafe because it breaks the reactivity chain - use with caution.\n\t *\n\t * @param _ignoreErrors - Unused parameter for API compatibility\n\t * @returns The current value\n\t * @internal\n\t */\n\t__unsafe__getWithoutCapture(_ignoreErrors?: boolean): Value {\n\t\treturn this.current\n\t}\n\n\t/**\n\t * Gets the current value of this atom. When called within a computed signal or reaction,\n\t * this atom will be automatically captured as a dependency.\n\t *\n\t * @returns The current value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 5)\n\t * console.log(count.get()) // 5\n\t * ```\n\t */\n\tget() {\n\t\tmaybeCaptureParent(this)\n\t\treturn this.current\n\t}\n\n\t/**\n\t * Sets the value of this atom to the given value. If the value is the same as the current value, this is a no-op.\n\t *\n\t * @param value - The new value to set\n\t * @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}\n\t * @returns The new value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 0)\n\t * count.set(5) // count.get() is now 5\n\t * ```\n\t */\n\tset(value: Value, diff?: Diff): Value {\n\t\t// If the value has not changed, do nothing.\n\t\tif (this.isEqual?.(this.current, value) ?? equals(this.current, value)) {\n\t\t\treturn this.current\n\t\t}\n\n\t\t// `computeDiff` is user code: run it before ticking the epoch, so that any signal it reads is\n\t\t// checked against the epoch this write has not yet happened in. Reading a dependent computed\n\t\t// after the tick would stamp it as checked at the new epoch while the atom was still being\n\t\t// written, and it would then never see the change. Only `undefined` means \"no diff\n\t\t// supplied\"; `null` can be a legitimate diff.\n\t\tlet historyDiff: Diff | RESET_VALUE | undefined\n\t\tif (this.historyBuffer) {\n\t\t\thistoryDiff =\n\t\t\t\tdiff !== undefined\n\t\t\t\t\t? diff\n\t\t\t\t\t: this.computeDiff\n\t\t\t\t\t\t? this.computeDiff(this.current, value, this.lastChangedEpoch, getGlobalEpoch() + 1)\n\t\t\t\t\t\t: RESET_VALUE\n\t\t}\n\n\t\t// Tick forward the global epoch. This write belongs to that one epoch, so read it once and\n\t\t// use it everywhere below \u2014 otherwise a `computeDiff` that touches other atoms could leave\n\t\t// the history entry and `lastChangedEpoch` disagreeing.\n\t\tadvanceGlobalEpoch()\n\t\tconst epoch = getGlobalEpoch()\n\n\t\t// Add the diff to the history buffer.\n\t\tif (this.historyBuffer) {\n\t\t\tthis.historyBuffer.pushEntry(this.lastChangedEpoch, epoch, historyDiff)\n\t\t}\n\n\t\t// Update the atom's record of the epoch when last changed.\n\t\tthis.lastChangedEpoch = epoch\n\n\t\tconst oldValue = this.current\n\t\tthis.current = value\n\n\t\t// Notify all children that this atom has changed.\n\t\tatomDidChange(this as any, oldValue)\n\n\t\treturn value\n\t}\n\n\t/**\n\t * Updates the value of this atom using the given updater function. If the returned value is the same as the current value, this is a no-op.\n\t *\n\t * @param updater - A function that takes the current value and returns the new value\n\t * @returns The new value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 5)\n\t * count.update(n => n + 1) // count.get() is now 6\n\t * ```\n\t */\n\tupdate(updater: (value: Value) => Value): Value {\n\t\treturn this.set(updater(this.current))\n\t}\n\n\t/**\n\t * Gets all the diffs that have occurred since the given epoch. When called within a computed\n\t * signal or reaction, this atom will be automatically captured as a dependency.\n\t *\n\t * @param epoch - The epoch to get changes since\n\t * @returns An array of diffs, or RESET_VALUE if history is insufficient\n\t * @internal\n\t */\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[] {\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 Atom constructor. Used internally to create atom instances.\n * @internal\n */\nexport const _Atom = singleton('Atom', () => __Atom__)\n\n/**\n * Type alias for instances of the internal Atom class.\n * @internal\n */\nexport type _Atom = InstanceType<typeof _Atom>\n\n/**\n * Creates a new {@link Atom}.\n *\n * An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n *\n * name.get() // 'John'\n *\n * name.set('Jane')\n *\n * name.get() // 'Jane'\n * ```\n *\n * @public\n */\nexport function atom<Value, Diff = unknown>(\n\t/**\n\t * A name for the signal. This is used for debugging and profiling purposes, it does not need to be unique.\n\t */\n\tname: string,\n\t/**\n\t * The initial value of the signal.\n\t */\n\tinitialValue: Value,\n\t/**\n\t * The options to configure the atom. See {@link AtomOptions}.\n\t */\n\toptions?: AtomOptions<Value, Diff>\n): Atom<Value, Diff> {\n\treturn new _Atom(name, initialValue, options)\n}\n\n/**\n * Returns true if the given value is an {@link Atom}.\n *\n * @param value - The value to check\n * @returns True if the value is an Atom, false otherwise\n * @example\n * ```ts\n * const myAtom = atom('test', 42)\n * const notAtom = 'hello'\n *\n * console.log(isAtom(myAtom)) // true\n * console.log(isAtom(notAtom)) // false\n * ```\n * @public\n */\nexport function isAtom(value: unknown): value is Atom<unknown> {\n\treturn value instanceof _Atom\n}\n"],
5
- "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,0BAA0B;AACnC,SAAS,aAAa,QAAQ,iBAAiB;AAC/C,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB,eAAe,sBAAsB;AAClE,SAA6B,mBAA2B;AAmExD,MAAM,SAA6D;AAAA,EAClE,YACiB,MACR,SACR,SACC;AAHe;AACR;AAGR,SAAK,UAAU,SAAS,WAAW;AAEnC,QAAI,CAAC,QAAS;AAEd,QAAI,QAAQ,eAAe;AAC1B,WAAK,gBAAgB,IAAI,cAAc,QAAQ,aAAa;AAAA,IAC7D;AAEA,SAAK,cAAc,QAAQ;AAAA,EAC5B;AAAA,EAbiB;AAAA,EACR;AAAA;AAAA,EAeA;AAAA;AAAA,EAGT;AAAA;AAAA,EAGA,mBAAmB,eAAe;AAAA;AAAA,EAGlC,WAAW,IAAI,SAAgB;AAAA;AAAA,EAG/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,4BAA4B,eAAgC;AAC3D,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM;AACL,uBAAmB,IAAI;AACvB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,IAAI,OAAc,MAAoB;AAErC,QAAI,KAAK,UAAU,KAAK,SAAS,KAAK,KAAK,OAAO,KAAK,SAAS,KAAK,GAAG;AACvE,aAAO,KAAK;AAAA,IACb;AAOA,QAAI;AACJ,QAAI,KAAK,eAAe;AACvB,oBACC,SAAS,SACN,OACA,KAAK,cACJ,KAAK,YAAY,KAAK,SAAS,OAAO,KAAK,kBAAkB,eAAe,IAAI,CAAC,IACjF;AAAA,IACN;AAKA,uBAAmB;AACnB,UAAM,QAAQ,eAAe;AAG7B,QAAI,KAAK,eAAe;AACvB,WAAK,cAAc,UAAU,KAAK,kBAAkB,OAAO,WAAW;AAAA,IACvE;AAGA,SAAK,mBAAmB;AAExB,UAAM,WAAW,KAAK;AACtB,SAAK,UAAU;AAGf,kBAAc,MAAa,QAAQ;AAEnC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,OAAO,SAAyC;AAC/C,WAAO,KAAK,IAAI,QAAQ,KAAK,OAAO,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,aAAa,OAAqC;AACjD,uBAAmB,IAAI;AAEvB,QAAI,SAAS,KAAK,kBAAkB;AACnC,aAAO;AAAA,IACR;AAEA,WAAO,KAAK,eAAe,gBAAgB,KAAK,KAAK;AAAA,EACtD;AACD;AAMO,MAAM,QAAQ,UAAU,QAAQ,MAAM,QAAQ;AA0B9C,SAAS,KAIf,MAIA,cAIA,SACoB;AACpB,SAAO,IAAI,MAAM,MAAM,cAAc,OAAO;AAC7C;AAiBO,SAAS,OAAO,OAAwC;AAC9D,SAAO,iBAAiB;AACzB;",
4
+ "sourcesContent": ["import { ArraySet } from './ArraySet'\nimport { maybeCaptureParent } from './capture'\nimport { EMPTY_ARRAY, equals, singleton } from './helpers'\nimport { HistoryBuffer } from './HistoryBuffer'\nimport { advanceGlobalEpoch, atomDidChange, getGlobalEpoch } from './transactions'\nimport { Child, ComputeDiff, RESET_VALUE, Signal } from './types'\n\n/**\n * The options to configure an atom, passed into the {@link atom} function.\n * @public\n */\nexport interface AtomOptions<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 passed to {@link Atom.set} or produced by {@link AtomOptions.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 atom's old and new values. If provided, it will not be used unless you also specify {@link AtomOptions.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 atom 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 * An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.\n *\n * Atoms are created using the {@link atom} function.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n *\n * print(name.get()) // 'John'\n * ```\n *\n * @public\n */\nexport interface Atom<Value, Diff = unknown> extends Signal<Value, Diff> {\n\t/**\n\t * Sets the value of this atom to the given value. If the value is the same as the current value, this is a no-op.\n\t *\n\t * @param value - The new value to set.\n\t * @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}.\n\t */\n\tset(value: Value, diff?: Diff): Value\n\t/**\n\t * Updates the value of this atom using the given updater function. If the returned value is the same as the current value, this is a no-op.\n\t *\n\t * @param updater - A function that takes the current value and returns the new value.\n\t */\n\tupdate(updater: (value: Value) => Value): Value\n}\n\n/**\n * Internal implementation of the Atom interface. This class should not be used directly - use the {@link atom} function instead.\n *\n * @internal\n */\nclass __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tprivate current: Value,\n\t\toptions?: AtomOptions<Value, Diff>\n\t) {\n\t\tthis.isEqual = options?.isEqual ?? equals\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}\n\n\t/** @internal */\n\treadonly isEqual: (a: any, b: any) => boolean\n\n\t/** @internal */\n\tcomputeDiff?: ComputeDiff<Value, Diff>\n\n\t/** @internal */\n\tlastChangedEpoch = getGlobalEpoch()\n\n\t/** @internal */\n\tchildren = new ArraySet<Child>()\n\n\t/** @internal */\n\thistoryBuffer?: HistoryBuffer<Diff>\n\n\t/**\n\t * Gets the current value without capturing it as a dependency in the current reactive context.\n\t * This is unsafe because it breaks the reactivity chain - use with caution.\n\t *\n\t * @param _ignoreErrors - Unused parameter for API compatibility\n\t * @returns The current value\n\t * @internal\n\t */\n\t__unsafe__getWithoutCapture(_ignoreErrors?: boolean): Value {\n\t\treturn this.current\n\t}\n\n\t/**\n\t * Gets the current value of this atom. When called within a computed signal or reaction,\n\t * this atom will be automatically captured as a dependency.\n\t *\n\t * @returns The current value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 5)\n\t * console.log(count.get()) // 5\n\t * ```\n\t */\n\tget() {\n\t\tmaybeCaptureParent(this)\n\t\treturn this.current\n\t}\n\n\t/**\n\t * Sets the value of this atom to the given value. If the value is the same as the current value, this is a no-op.\n\t *\n\t * @param value - The new value to set\n\t * @param diff - The diff to use for the update. If not provided, the diff will be computed using {@link AtomOptions.computeDiff}\n\t * @returns The new value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 0)\n\t * count.set(5) // count.get() is now 5\n\t * ```\n\t */\n\tset(value: Value, diff?: Diff): Value {\n\t\t// If the value has not changed, do nothing.\n\t\tif (this.isEqual(this.current, value)) {\n\t\t\treturn this.current\n\t\t}\n\n\t\t// `computeDiff` is user code: run it before ticking the epoch, so that any signal it reads is\n\t\t// checked against the epoch this write has not yet happened in. Reading a dependent computed\n\t\t// after the tick would stamp it as checked at the new epoch while the atom was still being\n\t\t// written, and it would then never see the change. Only `undefined` means \"no diff\n\t\t// supplied\"; `null` can be a legitimate diff.\n\t\tlet historyDiff: Diff | RESET_VALUE | undefined\n\t\tif (this.historyBuffer) {\n\t\t\thistoryDiff =\n\t\t\t\tdiff !== undefined\n\t\t\t\t\t? diff\n\t\t\t\t\t: this.computeDiff\n\t\t\t\t\t\t? this.computeDiff(this.current, value, this.lastChangedEpoch, getGlobalEpoch() + 1)\n\t\t\t\t\t\t: RESET_VALUE\n\t\t}\n\n\t\t// Tick forward the global epoch. This write belongs to that one epoch, so read it once and\n\t\t// use it everywhere below \u2014 otherwise a `computeDiff` that touches other atoms could leave\n\t\t// the history entry and `lastChangedEpoch` disagreeing.\n\t\tadvanceGlobalEpoch()\n\t\tconst epoch = getGlobalEpoch()\n\n\t\t// Add the diff to the history buffer.\n\t\tif (this.historyBuffer) {\n\t\t\tthis.historyBuffer.pushEntry(this.lastChangedEpoch, epoch, historyDiff)\n\t\t}\n\n\t\t// Update the atom's record of the epoch when last changed.\n\t\tthis.lastChangedEpoch = epoch\n\n\t\tconst oldValue = this.current\n\t\tthis.current = value\n\n\t\t// Notify all children that this atom has changed.\n\t\tatomDidChange(this as any, oldValue)\n\n\t\treturn value\n\t}\n\n\t/**\n\t * Updates the value of this atom using the given updater function. If the returned value is the same as the current value, this is a no-op.\n\t *\n\t * @param updater - A function that takes the current value and returns the new value\n\t * @returns The new value\n\t * @example\n\t * ```ts\n\t * const count = atom('count', 5)\n\t * count.update(n => n + 1) // count.get() is now 6\n\t * ```\n\t */\n\tupdate(updater: (value: Value) => Value): Value {\n\t\treturn this.set(updater(this.current))\n\t}\n\n\t/**\n\t * Gets all the diffs that have occurred since the given epoch. When called within a computed\n\t * signal or reaction, this atom will be automatically captured as a dependency.\n\t *\n\t * @param epoch - The epoch to get changes since\n\t * @returns An array of diffs, or RESET_VALUE if history is insufficient\n\t * @internal\n\t */\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[] {\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 Atom constructor. Used internally to create atom instances.\n * @internal\n */\nexport const _Atom = singleton('Atom', () => __Atom__)\n\n/**\n * Type alias for instances of the internal Atom class.\n * @internal\n */\nexport type _Atom = InstanceType<typeof _Atom>\n\n/**\n * Creates a new {@link Atom}.\n *\n * An Atom is a signal that can be updated directly by calling {@link Atom.set} or {@link Atom.update}.\n *\n * @example\n * ```ts\n * const name = atom('name', 'John')\n *\n * name.get() // 'John'\n *\n * name.set('Jane')\n *\n * name.get() // 'Jane'\n * ```\n *\n * @public\n */\nexport function atom<Value, Diff = unknown>(\n\t/**\n\t * A name for the signal. This is used for debugging and profiling purposes, it does not need to be unique.\n\t */\n\tname: string,\n\t/**\n\t * The initial value of the signal.\n\t */\n\tinitialValue: Value,\n\t/**\n\t * The options to configure the atom. See {@link AtomOptions}.\n\t */\n\toptions?: AtomOptions<Value, Diff>\n): Atom<Value, Diff> {\n\treturn new _Atom(name, initialValue, options)\n}\n\n/**\n * Returns true if the given value is an {@link Atom}.\n *\n * @param value - The value to check\n * @returns True if the value is an Atom, false otherwise\n * @example\n * ```ts\n * const myAtom = atom('test', 42)\n * const notAtom = 'hello'\n *\n * console.log(isAtom(myAtom)) // true\n * console.log(isAtom(notAtom)) // false\n * ```\n * @public\n */\nexport function isAtom(value: unknown): value is Atom<unknown> {\n\treturn value instanceof _Atom\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,0BAA0B;AACnC,SAAS,aAAa,QAAQ,iBAAiB;AAC/C,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB,eAAe,sBAAsB;AAClE,SAA6B,mBAA2B;AAmExD,MAAM,SAA6D;AAAA,EAClE,YACiB,MACR,SACR,SACC;AAHe;AACR;AAGR,SAAK,UAAU,SAAS,WAAW;AACnC,QAAI,SAAS,eAAe;AAC3B,WAAK,gBAAgB,IAAI,cAAc,QAAQ,aAAa;AAAA,IAC7D;AACA,SAAK,cAAc,SAAS;AAAA,EAC7B;AAAA,EATiB;AAAA,EACR;AAAA;AAAA,EAWA;AAAA;AAAA,EAGT;AAAA;AAAA,EAGA,mBAAmB,eAAe;AAAA;AAAA,EAGlC,WAAW,IAAI,SAAgB;AAAA;AAAA,EAG/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,4BAA4B,eAAgC;AAC3D,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM;AACL,uBAAmB,IAAI;AACvB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,IAAI,OAAc,MAAoB;AAErC,QAAI,KAAK,QAAQ,KAAK,SAAS,KAAK,GAAG;AACtC,aAAO,KAAK;AAAA,IACb;AAOA,QAAI;AACJ,QAAI,KAAK,eAAe;AACvB,oBACC,SAAS,SACN,OACA,KAAK,cACJ,KAAK,YAAY,KAAK,SAAS,OAAO,KAAK,kBAAkB,eAAe,IAAI,CAAC,IACjF;AAAA,IACN;AAKA,uBAAmB;AACnB,UAAM,QAAQ,eAAe;AAG7B,QAAI,KAAK,eAAe;AACvB,WAAK,cAAc,UAAU,KAAK,kBAAkB,OAAO,WAAW;AAAA,IACvE;AAGA,SAAK,mBAAmB;AAExB,UAAM,WAAW,KAAK;AACtB,SAAK,UAAU;AAGf,kBAAc,MAAa,QAAQ;AAEnC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,OAAO,SAAyC;AAC/C,WAAO,KAAK,IAAI,QAAQ,KAAK,OAAO,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,aAAa,OAAqC;AACjD,uBAAmB,IAAI;AAEvB,QAAI,SAAS,KAAK,kBAAkB;AACnC,aAAO;AAAA,IACR;AAEA,WAAO,KAAK,eAAe,gBAAgB,KAAK,KAAK;AAAA,EACtD;AACD;AAMO,MAAM,QAAQ,UAAU,QAAQ,MAAM,QAAQ;AA0B9C,SAAS,KAIf,MAIA,cAIA,SACoB;AACpB,SAAO,IAAI,MAAM,MAAM,cAAc,OAAO;AAC7C;AAiBO,SAAS,OAAO,OAAwC;AAC9D,SAAO,iBAAiB;AACzB;",
6
6
  "names": []
7
7
  }
@@ -35,50 +35,52 @@ function startCapturingParents(child) {
35
35
  function stopCapturingParents() {
36
36
  const frame = inst.stack;
37
37
  inst.stack = frame.below;
38
- if (frame.offset < frame.child.parents.length) {
39
- for (let i = frame.offset; i < frame.child.parents.length; i++) {
40
- const maybeRemovedParent = frame.child.parents[i];
41
- if (!frame.child.parentSet.has(maybeRemovedParent)) {
42
- detach(maybeRemovedParent, frame.child);
38
+ const { child, offset, maybeRemoved } = frame;
39
+ if (offset < child.parents.length) {
40
+ for (let i = offset; i < child.parents.length; i++) {
41
+ const maybeRemovedParent = child.parents[i];
42
+ if (!child.parentSet.has(maybeRemovedParent)) {
43
+ detach(maybeRemovedParent, child);
43
44
  }
44
45
  }
45
- frame.child.parents.length = frame.offset;
46
- frame.child.parentEpochs.length = frame.offset;
46
+ child.parents.length = offset;
47
+ child.parentEpochs.length = offset;
47
48
  }
48
- if (frame.maybeRemoved) {
49
- for (let i = 0; i < frame.maybeRemoved.length; i++) {
50
- const maybeRemovedParent = frame.maybeRemoved[i];
51
- if (!frame.child.parentSet.has(maybeRemovedParent)) {
52
- detach(maybeRemovedParent, frame.child);
49
+ if (maybeRemoved) {
50
+ for (let i = 0; i < maybeRemoved.length; i++) {
51
+ const maybeRemovedParent = maybeRemoved[i];
52
+ if (!child.parentSet.has(maybeRemovedParent)) {
53
+ detach(maybeRemovedParent, child);
53
54
  }
54
55
  }
55
56
  }
56
- if (frame.child.__debug_ancestor_epochs__) {
57
- captureAncestorEpochs(frame.child, frame.child.__debug_ancestor_epochs__);
57
+ if (child.__debug_ancestor_epochs__) {
58
+ captureAncestorEpochs(child, child.__debug_ancestor_epochs__);
58
59
  }
59
60
  }
60
61
  function maybeCaptureParent(p) {
61
- if (inst.stack) {
62
- if (!inst.stack.child.parentSet.add(p)) {
63
- return;
64
- }
65
- if (inst.stack.child.isActivelyListening) {
66
- attach(p, inst.stack.child);
67
- }
68
- if (inst.stack.offset < inst.stack.child.parents.length) {
69
- const maybeRemovedParent = inst.stack.child.parents[inst.stack.offset];
70
- if (maybeRemovedParent !== p) {
71
- if (!inst.stack.maybeRemoved) {
72
- inst.stack.maybeRemoved = [maybeRemovedParent];
73
- } else {
74
- inst.stack.maybeRemoved.push(maybeRemovedParent);
75
- }
62
+ const stack = inst.stack;
63
+ if (!stack) return;
64
+ const { child } = stack;
65
+ if (!child.parentSet.add(p)) {
66
+ return;
67
+ }
68
+ if (child.isActivelyListening) {
69
+ attach(p, child);
70
+ }
71
+ if (stack.offset < child.parents.length) {
72
+ const maybeRemovedParent = child.parents[stack.offset];
73
+ if (maybeRemovedParent !== p) {
74
+ if (!stack.maybeRemoved) {
75
+ stack.maybeRemoved = [maybeRemovedParent];
76
+ } else {
77
+ stack.maybeRemoved.push(maybeRemovedParent);
76
78
  }
77
79
  }
78
- inst.stack.child.parents[inst.stack.offset] = p;
79
- inst.stack.child.parentEpochs[inst.stack.offset] = p.lastChangedEpoch;
80
- inst.stack.offset++;
81
80
  }
81
+ child.parents[stack.offset] = p;
82
+ child.parentEpochs[stack.offset] = p.lastChangedEpoch;
83
+ stack.offset++;
82
84
  }
83
85
  function whyAmIRunning() {
84
86
  const child = inst.stack?.child;
@@ -90,13 +92,11 @@ function whyAmIRunning() {
90
92
  function captureAncestorEpochs(child, ancestorEpochs) {
91
93
  for (let i = 0; i < child.parents.length; i++) {
92
94
  const parent = child.parents[i];
93
- const epoch = child.parentEpochs[i];
94
- ancestorEpochs.set(parent, epoch);
95
+ ancestorEpochs.set(parent, child.parentEpochs[i]);
95
96
  if (isComputed(parent)) {
96
97
  captureAncestorEpochs(parent, ancestorEpochs);
97
98
  }
98
99
  }
99
- return ancestorEpochs;
100
100
  }
101
101
  function collectChangedAncestors(child, ancestorEpochs) {
102
102
  const changeTree = {};
@@ -105,9 +105,7 @@ function collectChangedAncestors(child, ancestorEpochs) {
105
105
  if (!ancestorEpochs.has(parent)) {
106
106
  continue;
107
107
  }
108
- const prevEpoch = ancestorEpochs.get(parent);
109
- const currentEpoch = parent.lastChangedEpoch;
110
- if (currentEpoch !== prevEpoch) {
108
+ if (parent.lastChangedEpoch !== ancestorEpochs.get(parent)) {
111
109
  if (isComputed(parent)) {
112
110
  changeTree[parent.name] = collectChangedAncestors(parent, ancestorEpochs);
113
111
  } else {
@@ -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', 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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",
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\tconst { child, offset, maybeRemoved } = frame\n\n\tif (offset < child.parents.length) {\n\t\tfor (let i = offset; i < child.parents.length; i++) {\n\t\t\tconst maybeRemovedParent = child.parents[i]\n\t\t\tif (!child.parentSet.has(maybeRemovedParent)) {\n\t\t\t\tdetach(maybeRemovedParent, child)\n\t\t\t}\n\t\t}\n\n\t\tchild.parents.length = offset\n\t\tchild.parentEpochs.length = offset\n\t}\n\n\tif (maybeRemoved) {\n\t\tfor (let i = 0; i < maybeRemoved.length; i++) {\n\t\t\tconst maybeRemovedParent = maybeRemoved[i]\n\t\t\tif (!child.parentSet.has(maybeRemovedParent)) {\n\t\t\t\tdetach(maybeRemovedParent, child)\n\t\t\t}\n\t\t}\n\t}\n\n\tif (child.__debug_ancestor_epochs__) {\n\t\tcaptureAncestorEpochs(child, 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\tconst stack = inst.stack\n\tif (!stack) return\n\tconst { child } = stack\n\n\t// `add` returns false when the parent was already captured this run. In array mode both\n\t// `has` and `add` scan with indexOf, so going straight to `add` halves the scans per\n\t// captured parent.\n\tif (!child.parentSet.add(p)) {\n\t\treturn\n\t}\n\n\tif (child.isActivelyListening) {\n\t\tattach(p, child)\n\t}\n\n\t// Parents are recorded in deref order. If the slot at this offset held a different parent\n\t// last run, that parent may have moved later in the order or been dropped; only\n\t// stopCapturingParents can tell, so remember it for then.\n\tif (stack.offset < child.parents.length) {\n\t\tconst maybeRemovedParent = child.parents[stack.offset]\n\t\tif (maybeRemovedParent !== p) {\n\t\t\tif (!stack.maybeRemoved) {\n\t\t\t\tstack.maybeRemoved = [maybeRemovedParent]\n\t\t\t} else {\n\t\t\t\tstack.maybeRemoved.push(maybeRemovedParent)\n\t\t\t}\n\t\t}\n\t}\n\n\tchild.parents[stack.offset] = p\n\tchild.parentEpochs[stack.offset] = p.lastChangedEpoch\n\tstack.offset++\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\tancestorEpochs.set(parent, child.parentEpochs[i])\n\t\tif (isComputed(parent)) {\n\t\t\tcaptureAncestorEpochs(parent as any, ancestorEpochs)\n\t\t}\n\t}\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\tif (parent.lastChangedEpoch !== ancestorEpochs.get(parent)) {\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
  }
@@ -32,8 +32,7 @@ function attach(parent, child) {
32
32
  }
33
33
  }
34
34
  function equals(a, b) {
35
- const shallowEquals = a === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === "function" && a.equals(b));
36
- return shallowEquals;
35
+ return a === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === "function" && a.equals(b));
37
36
  }
38
37
  function singleton(key, init) {
39
38
  const symbol = /* @__PURE__ */ Symbol.for(`com.tldraw.state/${key}`);
@@ -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\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 * `parent` must be up to date when it is attached: `Computed` assumes an actively-listening\n * computed has been traversed for every ancestor change since it was last checked, which only\n * holds from the moment it started listening. Capture attaches a parent right after reading it;\n * `EffectScheduler.attach` refreshes its parents first.\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;AAaO,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;",
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 * `parent` must be up to date when it is attached: `Computed` assumes an actively-listening\n * computed has been traversed for every ancestor change since it was last checked, which only\n * holds from the moment it started listening. Capture attaches a parent right after reading it;\n * `EffectScheduler.attach` refreshes its parents first.\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\treturn (\n\t\ta === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === 'function' && a.equals(b))\n\t)\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;AAaO,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,SACC,MAAM,KAAK,OAAO,GAAG,GAAG,CAAC,KAAK,QAAQ,KAAK,KAAK,OAAO,EAAE,WAAW,cAAc,EAAE,OAAO,CAAC,CAAC;AAE/F;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
  }
@@ -9,12 +9,8 @@ class Transaction {
9
9
  isSync;
10
10
  asyncProcessCount = 0;
11
11
  initialAtomValues = /* @__PURE__ */ new Map();
12
- // eslint-disable-next-line tldraw/no-setter-getter
13
- get isRoot() {
14
- return this.parent === null;
15
- }
16
12
  commit() {
17
- if (this.isRoot) {
13
+ if (this.parent === null) {
18
14
  flushChanges(this.initialAtomValues.keys());
19
15
  return;
20
16
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/transactions.ts"],
4
- "sourcesContent": ["import { _Atom } from './Atom'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { singleton } from './helpers'\nimport { Child, Signal } from './types'\n\ninterface Reactor {\n\tmaybeScheduleEffect(): void\n\tlastTraversedEpoch: number\n}\n\nclass Transaction {\n\tasyncProcessCount = 0\n\tconstructor(\n\t\tpublic readonly parent: Transaction | null,\n\t\tpublic readonly isSync: boolean\n\t) {}\n\n\tinitialAtomValues = new Map<_Atom, any>()\n\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget isRoot() {\n\t\treturn this.parent === null\n\t}\n\n\tcommit() {\n\t\tif (this.isRoot) {\n\t\t\t// For root transactions, flush changed atoms\n\t\t\tflushChanges(this.initialAtomValues.keys())\n\t\t\treturn\n\t\t}\n\t\t// For transactions with parents, add the transaction's initial values to the parent's, so\n\t\t// an outer rollback can still undo them (T7). This must come before any \"are we reacting?\" special case: a nested\n\t\t// transaction committed by an effect during the reaction phase used to skip this fold and\n\t\t// its changes survived the outer rollback.\n\t\t// A parent that has recorded nothing yet adopts the map outright: this transaction is\n\t\t// finished with it, and the common nested case is a single inner transaction doing all\n\t\t// the writes.\n\t\tconst parentValues = this.parent!.initialAtomValues\n\t\tif (parentValues.size === 0) {\n\t\t\tthis.parent!.initialAtomValues = this.initialAtomValues\n\t\t\treturn\n\t\t}\n\t\tthis.initialAtomValues.forEach((value, atom) => {\n\t\t\tif (!parentValues.has(atom)) {\n\t\t\t\tparentValues.set(atom, value)\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Abort the transaction. Restores every atom changed in this transaction to its initial\n\t * value, then commits. Must be called while this is still `inst.currentTransaction`, so that\n\t * the restoring sets are recorded against it instead of each flushing effects on their own.\n\t *\n\t * @public\n\t */\n\tabort() {\n\t\tinst.globalEpoch++\n\n\t\tthis.initialAtomValues.forEach((value, atom) => {\n\t\t\tatom.set(value)\n\t\t\tatom.historyBuffer?.clear()\n\t\t})\n\n\t\tthis.commit()\n\t}\n}\n\nconst inst = singleton('transactions', () => ({\n\t// The current epoch (global to all atoms).\n\tglobalEpoch: GLOBAL_START_EPOCH + 1,\n\t// Whether any transaction is reacting.\n\tglobalIsReacting: false,\n\tcurrentTransaction: null as Transaction | null,\n\n\tcleanupReactors: null as null | Set<Reactor>,\n}))\n\n/**\n * Gets the current global epoch, which is incremented every time any atom changes.\n * This is used to track changes across the entire reactive system.\n *\n * @returns The current global epoch number\n * @public\n */\nexport function getGlobalEpoch() {\n\treturn inst.globalEpoch\n}\n\n/**\n * Whether a transaction (sync or async) is currently open. While one is, atom changes are\n * recorded but their children are not traversed until it commits.\n *\n * @internal\n */\nexport function getIsInTransaction() {\n\treturn inst.currentTransaction !== null\n}\n\n// The set `traverseChild` collects reactors into. Module-level rather than a closure so that a\n// flush over thousands of atoms doesn't allocate a visitor per atom; traversal never runs user\n// code, so nothing can re-enter and swap it mid-walk.\nlet traverseReactors: Set<Reactor>\n\nfunction traverseChild(child: Child) {\n\tif (child.lastTraversedEpoch === inst.globalEpoch) {\n\t\treturn\n\t}\n\n\tchild.lastTraversedEpoch = inst.globalEpoch\n\n\tif ('__isEffectScheduler' in child) {\n\t\ttraverseReactors.add(child as unknown as Reactor)\n\t} else {\n\t\t;(child as any as Signal<any>).children.visit(traverseChild)\n\t}\n}\n\n/**\n * Collect all of the reactors that need to run for an atom and run them. During the reaction\n * phase the affected effects are instead queued for the cleanup pass, so a change made by an\n * effect never interrupts the pass that is running (P4).\n *\n * @param atoms - The atoms to flush changes for.\n */\nfunction flushChanges(atoms: Iterable<_Atom>) {\n\tif (inst.globalIsReacting) {\n\t\tfor (const atom of atoms) {\n\t\t\ttraverseAtomForCleanup(atom)\n\t\t}\n\t\treturn\n\t}\n\n\tconst outerTxn = inst.currentTransaction\n\ttry {\n\t\t// clear the transaction stack. Transactions started by effects must be roots: the committing\n\t\t// transaction (if any) has already handed its changes to this flush.\n\t\tinst.currentTransaction = null\n\t\tinst.globalIsReacting = true\n\n\t\tconst reactors = new Set<Reactor>()\n\t\ttraverseReactors = reactors\n\t\tfor (const atom of atoms) {\n\t\t\tatom.children.visit(traverseChild)\n\t\t}\n\n\t\t// Run each reactor.\n\t\tfor (const r of reactors) {\n\t\t\tr.maybeScheduleEffect()\n\t\t}\n\n\t\tlet updateDepth = 0\n\t\twhile (inst.cleanupReactors?.size) {\n\t\t\tif (updateDepth++ > 1000) {\n\t\t\t\tthrow new Error('Reaction update depth limit exceeded')\n\t\t\t}\n\t\t\tconst reactors = inst.cleanupReactors\n\t\t\tinst.cleanupReactors = null\n\t\t\tfor (const r of reactors) {\n\t\t\t\tr.maybeScheduleEffect()\n\t\t\t}\n\t\t}\n\t} finally {\n\t\tinst.cleanupReactors = null\n\t\tinst.globalIsReacting = false\n\t\tinst.currentTransaction = outerTxn\n\t\ttraverseReactors = undefined! // free memory\n\t}\n}\n\n/** @internal */\nexport function atomDidChange(atom: _Atom, previousValue: any) {\n\tif (inst.currentTransaction) {\n\t\t// If we are in a transaction, then all we have to do is preserve\n\t\t// the value of the atom at the start of the transaction in case\n\t\t// we need to roll back. Children are not traversed until the\n\t\t// transaction commits, which is why `Computed` must not trust its\n\t\t// traversal-based cache shortcut while a transaction is open.\n\t\tif (!inst.currentTransaction.initialAtomValues.has(atom)) {\n\t\t\tinst.currentTransaction.initialAtomValues.set(atom, previousValue)\n\t\t}\n\t} else {\n\t\t// If there is no transaction, flush the changes immediately.\n\t\tflushChanges([atom])\n\t}\n}\n\nfunction traverseAtomForCleanup(atom: _Atom) {\n\ttraverseReactors = inst.cleanupReactors ??= new Set()\n\tatom.children.visit(traverseChild)\n}\n\n/** @internal */\nexport function advanceGlobalEpoch() {\n\tinst.globalEpoch++\n}\n\n/**\n * Batches state updates, deferring side effects until after the transaction completes.\n * Unlike {@link transact}, this function always creates a new transaction, allowing for nested transactions.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction(() => {\n * firstName.set('Jane')\n * lastName.set('Smith')\n * })\n *\n * // Logs \"Hello, Jane Smith!\"\n * ```\n *\n * If the function throws, the transaction is aborted and any signals that were updated during the transaction revert to their state before the transaction began. An aborted transaction still flushes effects: effects whose parents went through a change-and-restore round trip are checked again and, if a parent's value differs from what they last saw (an atom they read directly always will), run once more with the restored values.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction(() => {\n * firstName.set('Jane')\n * throw new Error('oops')\n * })\n *\n * // firstName.get() === 'John'\n * // Logs \"Hello, John Doe!\" again: effects whose parents were changed and restored still run,\n * // and observe the restored values.\n * ```\n *\n * A `rollback` callback is passed into the function.\n * Calling this will prevent the transaction from committing and will revert any signals that were updated during the transaction to their state before the transaction began.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction((rollback) => {\n * firstName.set('Jane')\n * lastName.set('Smith')\n * rollback()\n * })\n *\n * // firstName.get() === 'John'\n * // lastName.get() === 'Doe'\n * // Logs \"Hello, John Doe!\" again, as above.\n * ```\n *\n * @param fn - The function to run in a transaction, called with a function to roll back the change.\n * @returns The return value of the function\n * @public\n */\nexport function transaction<T>(fn: (rollback: () => void) => T) {\n\tconst txn = new Transaction(inst.currentTransaction, true)\n\n\tinst.currentTransaction = txn\n\n\ttry {\n\t\tlet result = undefined as T | undefined\n\t\tlet rollback = false\n\n\t\ttry {\n\t\t\tresult = fn(() => (rollback = true))\n\t\t} catch (e) {\n\t\t\ttxn.abort()\n\t\t\tthrow e\n\t\t}\n\n\t\tif (inst.currentTransaction !== txn) {\n\t\t\tthrow new Error('Transaction boundaries overlap')\n\t\t}\n\n\t\tif (rollback) {\n\t\t\ttxn.abort()\n\t\t} else {\n\t\t\ttxn.commit()\n\t\t}\n\n\t\treturn result\n\t} finally {\n\t\tinst.currentTransaction = txn.parent\n\t}\n}\n\n/**\n * Like {@link transaction}, but does not create a new transaction if there is already one in progress.\n * This is the preferred way to batch state updates when you don't need the rollback functionality.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const doubled = atom('doubled', 0)\n *\n * react('update doubled', () => {\n * console.log(`Count: ${count.get()}, Doubled: ${doubled.get()}`)\n * })\n *\n * // This batches both updates into a single reaction\n * transact(() => {\n * count.set(5)\n * doubled.set(count.get() * 2)\n * })\n * // Logs: \"Count: 5, Doubled: 10\"\n * ```\n *\n * @param fn - The function to run in a transaction\n * @returns The return value of the function\n * @public\n */\nexport function transact<T>(fn: () => T): T {\n\tif (inst.currentTransaction) {\n\t\treturn fn()\n\t}\n\treturn transaction(fn)\n}\n\n/**\n * Defers the execution of asynchronous effects until they can be properly handled.\n * This function creates an asynchronous transaction context that batches state updates\n * across async operations while preventing conflicts with synchronous transactions.\n *\n * @example\n * ```ts\n * const data = atom('data', null)\n * const loading = atom('loading', false)\n *\n * await deferAsyncEffects(async () => {\n * loading.set(true)\n * const result = await fetch('/api/data')\n * const json = await result.json()\n * data.set(json)\n * loading.set(false)\n * })\n * ```\n *\n * @param fn - The async function to execute within the deferred context\n * @returns A promise that resolves to the return value of the function\n * @throws Will throw if called during a synchronous transaction\n * @internal\n */\nexport async function deferAsyncEffects<T>(fn: () => Promise<T>) {\n\t// Can't kick off async transactions during a sync transaction because\n\t// the async transaction won't finish until after the sync transaction\n\t// is done.\n\tif (inst.currentTransaction?.isSync) {\n\t\tthrow new Error('deferAsyncEffects cannot be called during a sync transaction')\n\t}\n\n\t// Can't kick off async transactions during a reaction phase at the moment,\n\t// because the transaction stack is cleared after the reaction phase.\n\t// So wait until the path ahead is clear\n\twhile (inst.globalIsReacting) {\n\t\tawait new Promise((r) => queueMicrotask(() => r(null)))\n\t}\n\n\tconst txn = inst.currentTransaction ?? new Transaction(null, false)\n\n\t// don't think this can happen, but just in case\n\tif (txn.isSync) throw new Error('deferAsyncEffects cannot be called during a sync transaction')\n\n\tinst.currentTransaction = txn\n\ttxn.asyncProcessCount++\n\n\tlet result = undefined as T | undefined\n\n\t// `undefined` means \"did not throw\"; a thrown `undefined` is stored as `null` so it still counts.\n\tlet error = undefined as any\n\ttry {\n\t\t// Run the function.\n\t\tresult = await fn()\n\t} catch (e) {\n\t\t// Abort the transaction if the function throws.\n\t\terror = e ?? null\n\t}\n\n\tif (--txn.asyncProcessCount > 0) {\n\t\t// Other processes still share this transaction; it settles when the last one finishes.\n\t\tif (typeof error !== 'undefined') {\n\t\t\t// If the rollback was triggered, abort the transaction.\n\t\t\tthrow error\n\t\t} else {\n\t\t\treturn result\n\t\t}\n\t}\n\n\t// Settle while this is still the current transaction (see `Transaction.abort`): clearing it\n\t// first made every restoring set inside `abort()` flush effects on its own, so effects\n\t// observed half-restored state.\n\ttry {\n\t\tif (typeof error !== 'undefined') {\n\t\t\t// If the rollback was triggered, abort the transaction.\n\t\t\ttxn.abort()\n\t\t\tthrow error\n\t\t} else {\n\t\t\ttxn.commit()\n\t\t\treturn result\n\t\t}\n\t} finally {\n\t\tinst.currentTransaction = null\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { _Atom } from './Atom'\nimport { GLOBAL_START_EPOCH } from './constants'\nimport { singleton } from './helpers'\nimport { Child, Signal } from './types'\n\ninterface Reactor {\n\tmaybeScheduleEffect(): void\n\tlastTraversedEpoch: number\n}\n\nclass Transaction {\n\tasyncProcessCount = 0\n\tconstructor(\n\t\tpublic readonly parent: Transaction | null,\n\t\tpublic readonly isSync: boolean\n\t) {}\n\n\tinitialAtomValues = new Map<_Atom, any>()\n\n\tcommit() {\n\t\tif (this.parent === null) {\n\t\t\t// For root transactions, flush changed atoms\n\t\t\tflushChanges(this.initialAtomValues.keys())\n\t\t\treturn\n\t\t}\n\t\t// For transactions with parents, add the transaction's initial values to the parent's, so\n\t\t// an outer rollback can still undo them (T7). This must come before any \"are we reacting?\" special case: a nested\n\t\t// transaction committed by an effect during the reaction phase used to skip this fold and\n\t\t// its changes survived the outer rollback.\n\t\t// A parent that has recorded nothing yet adopts the map outright: this transaction is\n\t\t// finished with it, and the common nested case is a single inner transaction doing all\n\t\t// the writes.\n\t\tconst parentValues = this.parent!.initialAtomValues\n\t\tif (parentValues.size === 0) {\n\t\t\tthis.parent!.initialAtomValues = this.initialAtomValues\n\t\t\treturn\n\t\t}\n\t\tthis.initialAtomValues.forEach((value, atom) => {\n\t\t\tif (!parentValues.has(atom)) {\n\t\t\t\tparentValues.set(atom, value)\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Abort the transaction. Restores every atom changed in this transaction to its initial\n\t * value, then commits. Must be called while this is still `inst.currentTransaction`, so that\n\t * the restoring sets are recorded against it instead of each flushing effects on their own.\n\t *\n\t * @public\n\t */\n\tabort() {\n\t\tinst.globalEpoch++\n\n\t\tthis.initialAtomValues.forEach((value, atom) => {\n\t\t\tatom.set(value)\n\t\t\tatom.historyBuffer?.clear()\n\t\t})\n\n\t\tthis.commit()\n\t}\n}\n\nconst inst = singleton('transactions', () => ({\n\t// The current epoch (global to all atoms).\n\tglobalEpoch: GLOBAL_START_EPOCH + 1,\n\t// Whether any transaction is reacting.\n\tglobalIsReacting: false,\n\tcurrentTransaction: null as Transaction | null,\n\n\tcleanupReactors: null as null | Set<Reactor>,\n}))\n\n/**\n * Gets the current global epoch, which is incremented every time any atom changes.\n * This is used to track changes across the entire reactive system.\n *\n * @returns The current global epoch number\n * @public\n */\nexport function getGlobalEpoch() {\n\treturn inst.globalEpoch\n}\n\n/**\n * Whether a transaction (sync or async) is currently open. While one is, atom changes are\n * recorded but their children are not traversed until it commits.\n *\n * @internal\n */\nexport function getIsInTransaction() {\n\treturn inst.currentTransaction !== null\n}\n\n// The set `traverseChild` collects reactors into. Module-level rather than a closure so that a\n// flush over thousands of atoms doesn't allocate a visitor per atom; traversal never runs user\n// code, so nothing can re-enter and swap it mid-walk.\nlet traverseReactors: Set<Reactor>\n\nfunction traverseChild(child: Child) {\n\tif (child.lastTraversedEpoch === inst.globalEpoch) {\n\t\treturn\n\t}\n\n\tchild.lastTraversedEpoch = inst.globalEpoch\n\n\tif ('__isEffectScheduler' in child) {\n\t\ttraverseReactors.add(child as unknown as Reactor)\n\t} else {\n\t\t;(child as any as Signal<any>).children.visit(traverseChild)\n\t}\n}\n\n/**\n * Collect all of the reactors that need to run for an atom and run them. During the reaction\n * phase the affected effects are instead queued for the cleanup pass, so a change made by an\n * effect never interrupts the pass that is running (P4).\n *\n * @param atoms - The atoms to flush changes for.\n */\nfunction flushChanges(atoms: Iterable<_Atom>) {\n\tif (inst.globalIsReacting) {\n\t\tfor (const atom of atoms) {\n\t\t\ttraverseAtomForCleanup(atom)\n\t\t}\n\t\treturn\n\t}\n\n\tconst outerTxn = inst.currentTransaction\n\ttry {\n\t\t// clear the transaction stack. Transactions started by effects must be roots: the committing\n\t\t// transaction (if any) has already handed its changes to this flush.\n\t\tinst.currentTransaction = null\n\t\tinst.globalIsReacting = true\n\n\t\tconst reactors = new Set<Reactor>()\n\t\ttraverseReactors = reactors\n\t\tfor (const atom of atoms) {\n\t\t\tatom.children.visit(traverseChild)\n\t\t}\n\n\t\t// Run each reactor.\n\t\tfor (const r of reactors) {\n\t\t\tr.maybeScheduleEffect()\n\t\t}\n\n\t\tlet updateDepth = 0\n\t\twhile (inst.cleanupReactors?.size) {\n\t\t\tif (updateDepth++ > 1000) {\n\t\t\t\tthrow new Error('Reaction update depth limit exceeded')\n\t\t\t}\n\t\t\tconst reactors = inst.cleanupReactors\n\t\t\tinst.cleanupReactors = null\n\t\t\tfor (const r of reactors) {\n\t\t\t\tr.maybeScheduleEffect()\n\t\t\t}\n\t\t}\n\t} finally {\n\t\tinst.cleanupReactors = null\n\t\tinst.globalIsReacting = false\n\t\tinst.currentTransaction = outerTxn\n\t\ttraverseReactors = undefined! // free memory\n\t}\n}\n\n/** @internal */\nexport function atomDidChange(atom: _Atom, previousValue: any) {\n\tif (inst.currentTransaction) {\n\t\t// If we are in a transaction, then all we have to do is preserve\n\t\t// the value of the atom at the start of the transaction in case\n\t\t// we need to roll back. Children are not traversed until the\n\t\t// transaction commits, which is why `Computed` must not trust its\n\t\t// traversal-based cache shortcut while a transaction is open.\n\t\tif (!inst.currentTransaction.initialAtomValues.has(atom)) {\n\t\t\tinst.currentTransaction.initialAtomValues.set(atom, previousValue)\n\t\t}\n\t} else {\n\t\t// If there is no transaction, flush the changes immediately.\n\t\tflushChanges([atom])\n\t}\n}\n\nfunction traverseAtomForCleanup(atom: _Atom) {\n\ttraverseReactors = inst.cleanupReactors ??= new Set()\n\tatom.children.visit(traverseChild)\n}\n\n/** @internal */\nexport function advanceGlobalEpoch() {\n\tinst.globalEpoch++\n}\n\n/**\n * Batches state updates, deferring side effects until after the transaction completes.\n * Unlike {@link transact}, this function always creates a new transaction, allowing for nested transactions.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction(() => {\n * firstName.set('Jane')\n * lastName.set('Smith')\n * })\n *\n * // Logs \"Hello, Jane Smith!\"\n * ```\n *\n * If the function throws, the transaction is aborted and any signals that were updated during the transaction revert to their state before the transaction began. An aborted transaction still flushes effects: effects whose parents went through a change-and-restore round trip are checked again and, if a parent's value differs from what they last saw (an atom they read directly always will), run once more with the restored values.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction(() => {\n * firstName.set('Jane')\n * throw new Error('oops')\n * })\n *\n * // firstName.get() === 'John'\n * // Logs \"Hello, John Doe!\" again: effects whose parents were changed and restored still run,\n * // and observe the restored values.\n * ```\n *\n * A `rollback` callback is passed into the function.\n * Calling this will prevent the transaction from committing and will revert any signals that were updated during the transaction to their state before the transaction began.\n *\n * @example\n * ```ts\n * const firstName = atom('firstName', 'John')\n * const lastName = atom('lastName', 'Doe')\n *\n * react('greet', () => {\n * console.log(`Hello, ${firstName.get()} ${lastName.get()}!`)\n * })\n *\n * // Logs \"Hello, John Doe!\"\n *\n * transaction((rollback) => {\n * firstName.set('Jane')\n * lastName.set('Smith')\n * rollback()\n * })\n *\n * // firstName.get() === 'John'\n * // lastName.get() === 'Doe'\n * // Logs \"Hello, John Doe!\" again, as above.\n * ```\n *\n * @param fn - The function to run in a transaction, called with a function to roll back the change.\n * @returns The return value of the function\n * @public\n */\nexport function transaction<T>(fn: (rollback: () => void) => T) {\n\tconst txn = new Transaction(inst.currentTransaction, true)\n\n\tinst.currentTransaction = txn\n\n\ttry {\n\t\tlet result = undefined as T | undefined\n\t\tlet rollback = false\n\n\t\ttry {\n\t\t\tresult = fn(() => (rollback = true))\n\t\t} catch (e) {\n\t\t\ttxn.abort()\n\t\t\tthrow e\n\t\t}\n\n\t\tif (inst.currentTransaction !== txn) {\n\t\t\tthrow new Error('Transaction boundaries overlap')\n\t\t}\n\n\t\tif (rollback) {\n\t\t\ttxn.abort()\n\t\t} else {\n\t\t\ttxn.commit()\n\t\t}\n\n\t\treturn result\n\t} finally {\n\t\tinst.currentTransaction = txn.parent\n\t}\n}\n\n/**\n * Like {@link transaction}, but does not create a new transaction if there is already one in progress.\n * This is the preferred way to batch state updates when you don't need the rollback functionality.\n *\n * @example\n * ```ts\n * const count = atom('count', 0)\n * const doubled = atom('doubled', 0)\n *\n * react('update doubled', () => {\n * console.log(`Count: ${count.get()}, Doubled: ${doubled.get()}`)\n * })\n *\n * // This batches both updates into a single reaction\n * transact(() => {\n * count.set(5)\n * doubled.set(count.get() * 2)\n * })\n * // Logs: \"Count: 5, Doubled: 10\"\n * ```\n *\n * @param fn - The function to run in a transaction\n * @returns The return value of the function\n * @public\n */\nexport function transact<T>(fn: () => T): T {\n\tif (inst.currentTransaction) {\n\t\treturn fn()\n\t}\n\treturn transaction(fn)\n}\n\n/**\n * Defers the execution of asynchronous effects until they can be properly handled.\n * This function creates an asynchronous transaction context that batches state updates\n * across async operations while preventing conflicts with synchronous transactions.\n *\n * @example\n * ```ts\n * const data = atom('data', null)\n * const loading = atom('loading', false)\n *\n * await deferAsyncEffects(async () => {\n * loading.set(true)\n * const result = await fetch('/api/data')\n * const json = await result.json()\n * data.set(json)\n * loading.set(false)\n * })\n * ```\n *\n * @param fn - The async function to execute within the deferred context\n * @returns A promise that resolves to the return value of the function\n * @throws Will throw if called during a synchronous transaction\n * @internal\n */\nexport async function deferAsyncEffects<T>(fn: () => Promise<T>) {\n\t// Can't kick off async transactions during a sync transaction because\n\t// the async transaction won't finish until after the sync transaction\n\t// is done.\n\tif (inst.currentTransaction?.isSync) {\n\t\tthrow new Error('deferAsyncEffects cannot be called during a sync transaction')\n\t}\n\n\t// Can't kick off async transactions during a reaction phase at the moment,\n\t// because the transaction stack is cleared after the reaction phase.\n\t// So wait until the path ahead is clear\n\twhile (inst.globalIsReacting) {\n\t\tawait new Promise((r) => queueMicrotask(() => r(null)))\n\t}\n\n\tconst txn = inst.currentTransaction ?? new Transaction(null, false)\n\n\t// don't think this can happen, but just in case\n\tif (txn.isSync) throw new Error('deferAsyncEffects cannot be called during a sync transaction')\n\n\tinst.currentTransaction = txn\n\ttxn.asyncProcessCount++\n\n\tlet result = undefined as T | undefined\n\n\t// `undefined` means \"did not throw\"; a thrown `undefined` is stored as `null` so it still counts.\n\tlet error = undefined as any\n\ttry {\n\t\t// Run the function.\n\t\tresult = await fn()\n\t} catch (e) {\n\t\t// Abort the transaction if the function throws.\n\t\terror = e ?? null\n\t}\n\n\tif (--txn.asyncProcessCount > 0) {\n\t\t// Other processes still share this transaction; it settles when the last one finishes.\n\t\tif (typeof error !== 'undefined') {\n\t\t\t// If the rollback was triggered, abort the transaction.\n\t\t\tthrow error\n\t\t} else {\n\t\t\treturn result\n\t\t}\n\t}\n\n\t// Settle while this is still the current transaction (see `Transaction.abort`): clearing it\n\t// first made every restoring set inside `abort()` flush effects on its own, so effects\n\t// observed half-restored state.\n\ttry {\n\t\tif (typeof error !== 'undefined') {\n\t\t\t// If the rollback was triggered, abort the transaction.\n\t\t\ttxn.abort()\n\t\t\tthrow error\n\t\t} else {\n\t\t\ttxn.commit()\n\t\t\treturn result\n\t\t}\n\t} finally {\n\t\tinst.currentTransaction = null\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["reactors"]
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/state",
3
3
  "description": "tldraw infinite canvas SDK (state).",
4
- "version": "5.5.0-canary.f7ff7a688554",
4
+ "version": "5.5.0-canary.fa4e171d600e",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -52,10 +52,10 @@
52
52
  "devDependencies": {
53
53
  "@types/lodash": "^4.17.14",
54
54
  "lodash": "^4.17.21",
55
- "vitest": "^4.1.7"
55
+ "vitest": "^5.0.0"
56
56
  },
57
57
  "dependencies": {
58
- "@tldraw/utils": "5.5.0-canary.f7ff7a688554"
58
+ "@tldraw/utils": "5.5.0-canary.fa4e171d600e"
59
59
  },
60
60
  "typedoc": {
61
61
  "readmeFile": "none",
package/src/lib/Atom.ts CHANGED
@@ -76,19 +76,15 @@ class __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {
76
76
  private current: Value,
77
77
  options?: AtomOptions<Value, Diff>
78
78
  ) {
79
- this.isEqual = options?.isEqual ?? null
80
-
81
- if (!options) return
82
-
83
- if (options.historyLength) {
79
+ this.isEqual = options?.isEqual ?? equals
80
+ if (options?.historyLength) {
84
81
  this.historyBuffer = new HistoryBuffer(options.historyLength)
85
82
  }
86
-
87
- this.computeDiff = options.computeDiff
83
+ this.computeDiff = options?.computeDiff
88
84
  }
89
85
 
90
86
  /** @internal */
91
- readonly isEqual: null | ((a: any, b: any) => boolean)
87
+ readonly isEqual: (a: any, b: any) => boolean
92
88
 
93
89
  /** @internal */
94
90
  computeDiff?: ComputeDiff<Value, Diff>
@@ -144,7 +140,7 @@ class __Atom__<Value, Diff = unknown> implements Atom<Value, Diff> {
144
140
  */
145
141
  set(value: Value, diff?: Diff): Value {
146
142
  // If the value has not changed, do nothing.
147
- if (this.isEqual?.(this.current, value) ?? equals(this.current, value)) {
143
+ if (this.isEqual(this.current, value)) {
148
144
  return this.current
149
145
  }
150
146
 
@@ -100,30 +100,31 @@ export function startCapturingParents(child: Child) {
100
100
  export function stopCapturingParents() {
101
101
  const frame = inst.stack!
102
102
  inst.stack = frame.below
103
+ const { child, offset, maybeRemoved } = frame
103
104
 
104
- if (frame.offset < frame.child.parents.length) {
105
- for (let i = frame.offset; i < frame.child.parents.length; i++) {
106
- const maybeRemovedParent = frame.child.parents[i]
107
- if (!frame.child.parentSet.has(maybeRemovedParent)) {
108
- detach(maybeRemovedParent, frame.child)
105
+ if (offset < child.parents.length) {
106
+ for (let i = offset; i < child.parents.length; i++) {
107
+ const maybeRemovedParent = child.parents[i]
108
+ if (!child.parentSet.has(maybeRemovedParent)) {
109
+ detach(maybeRemovedParent, child)
109
110
  }
110
111
  }
111
112
 
112
- frame.child.parents.length = frame.offset
113
- frame.child.parentEpochs.length = frame.offset
113
+ child.parents.length = offset
114
+ child.parentEpochs.length = offset
114
115
  }
115
116
 
116
- if (frame.maybeRemoved) {
117
- for (let i = 0; i < frame.maybeRemoved.length; i++) {
118
- const maybeRemovedParent = frame.maybeRemoved[i]
119
- if (!frame.child.parentSet.has(maybeRemovedParent)) {
120
- detach(maybeRemovedParent, frame.child)
117
+ if (maybeRemoved) {
118
+ for (let i = 0; i < maybeRemoved.length; i++) {
119
+ const maybeRemovedParent = maybeRemoved[i]
120
+ if (!child.parentSet.has(maybeRemovedParent)) {
121
+ detach(maybeRemovedParent, child)
121
122
  }
122
123
  }
123
124
  }
124
125
 
125
- if (frame.child.__debug_ancestor_epochs__) {
126
- captureAncestorEpochs(frame.child, frame.child.__debug_ancestor_epochs__)
126
+ if (child.__debug_ancestor_epochs__) {
127
+ captureAncestorEpochs(child, child.__debug_ancestor_epochs__)
127
128
  }
128
129
  }
129
130
 
@@ -148,36 +149,38 @@ export function stopCapturingParents() {
148
149
  * @internal
149
150
  */
150
151
  export function maybeCaptureParent(p: Signal<any, any>) {
151
- if (inst.stack) {
152
- // `add` returns false when the parent was already captured this run. In array mode both
153
- // `has` and `add` scan with indexOf, so going straight to `add` halves the scans per
154
- // captured parent.
155
- if (!inst.stack.child.parentSet.add(p)) {
156
- return
157
- }
152
+ const stack = inst.stack
153
+ if (!stack) return
154
+ const { child } = stack
158
155
 
159
- if (inst.stack.child.isActivelyListening) {
160
- attach(p, inst.stack.child)
161
- }
156
+ // `add` returns false when the parent was already captured this run. In array mode both
157
+ // `has` and `add` scan with indexOf, so going straight to `add` halves the scans per
158
+ // captured parent.
159
+ if (!child.parentSet.add(p)) {
160
+ return
161
+ }
162
162
 
163
- // Parents are recorded in deref order. If the slot at this offset held a different parent
164
- // last run, that parent may have moved later in the order or been dropped; only
165
- // stopCapturingParents can tell, so remember it for then.
166
- if (inst.stack.offset < inst.stack.child.parents.length) {
167
- const maybeRemovedParent = inst.stack.child.parents[inst.stack.offset]
168
- if (maybeRemovedParent !== p) {
169
- if (!inst.stack.maybeRemoved) {
170
- inst.stack.maybeRemoved = [maybeRemovedParent]
171
- } else {
172
- inst.stack.maybeRemoved.push(maybeRemovedParent)
173
- }
163
+ if (child.isActivelyListening) {
164
+ attach(p, child)
165
+ }
166
+
167
+ // Parents are recorded in deref order. If the slot at this offset held a different parent
168
+ // last run, that parent may have moved later in the order or been dropped; only
169
+ // stopCapturingParents can tell, so remember it for then.
170
+ if (stack.offset < child.parents.length) {
171
+ const maybeRemovedParent = child.parents[stack.offset]
172
+ if (maybeRemovedParent !== p) {
173
+ if (!stack.maybeRemoved) {
174
+ stack.maybeRemoved = [maybeRemovedParent]
175
+ } else {
176
+ stack.maybeRemoved.push(maybeRemovedParent)
174
177
  }
175
178
  }
176
-
177
- inst.stack.child.parents[inst.stack.offset] = p
178
- inst.stack.child.parentEpochs[inst.stack.offset] = p.lastChangedEpoch
179
- inst.stack.offset++
180
179
  }
180
+
181
+ child.parents[stack.offset] = p
182
+ child.parentEpochs[stack.offset] = p.lastChangedEpoch
183
+ stack.offset++
181
184
  }
182
185
 
183
186
  /**
@@ -212,13 +215,11 @@ export function whyAmIRunning() {
212
215
  function captureAncestorEpochs(child: Child, ancestorEpochs: Map<Signal<any>, number>) {
213
216
  for (let i = 0; i < child.parents.length; i++) {
214
217
  const parent = child.parents[i]
215
- const epoch = child.parentEpochs[i]
216
- ancestorEpochs.set(parent, epoch)
218
+ ancestorEpochs.set(parent, child.parentEpochs[i])
217
219
  if (isComputed(parent)) {
218
220
  captureAncestorEpochs(parent as any, ancestorEpochs)
219
221
  }
220
222
  }
221
- return ancestorEpochs
222
223
  }
223
224
 
224
225
  type ChangeTree = { [signalName: string]: ChangeTree } | null
@@ -232,9 +233,7 @@ function collectChangedAncestors(
232
233
  if (!ancestorEpochs.has(parent)) {
233
234
  continue
234
235
  }
235
- const prevEpoch = ancestorEpochs.get(parent)
236
- const currentEpoch = parent.lastChangedEpoch
237
- if (currentEpoch !== prevEpoch) {
236
+ if (parent.lastChangedEpoch !== ancestorEpochs.get(parent)) {
238
237
  if (isComputed(parent)) {
239
238
  changeTree[parent.name] = collectChangedAncestors(parent as any, ancestorEpochs)
240
239
  } else {
@@ -75,9 +75,9 @@ export function attach(parent: Signal<any>, child: Child) {
75
75
  * @internal
76
76
  */
77
77
  export function equals(a: any, b: any): boolean {
78
- const shallowEquals =
78
+ return (
79
79
  a === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === 'function' && a.equals(b))
80
- return shallowEquals
80
+ )
81
81
  }
82
82
 
83
83
  /**
@@ -17,13 +17,8 @@ class Transaction {
17
17
 
18
18
  initialAtomValues = new Map<_Atom, any>()
19
19
 
20
- // eslint-disable-next-line tldraw/no-setter-getter
21
- get isRoot() {
22
- return this.parent === null
23
- }
24
-
25
20
  commit() {
26
- if (this.isRoot) {
21
+ if (this.parent === null) {
27
22
  // For root transactions, flush changed atoms
28
23
  flushChanges(this.initialAtomValues.keys())
29
24
  return