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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/DOCS.md +64 -63
  2. package/README.md +35 -36
  3. package/dist-cjs/index.d.ts +26 -27
  4. package/dist-cjs/index.js +1 -1
  5. package/dist-cjs/lib/ArraySet.js +47 -144
  6. package/dist-cjs/lib/ArraySet.js.map +2 -2
  7. package/dist-cjs/lib/Atom.js +12 -26
  8. package/dist-cjs/lib/Atom.js.map +2 -2
  9. package/dist-cjs/lib/Computed.js +36 -64
  10. package/dist-cjs/lib/Computed.js.map +2 -2
  11. package/dist-cjs/lib/EffectScheduler.js +1 -1
  12. package/dist-cjs/lib/EffectScheduler.js.map +2 -2
  13. package/dist-cjs/lib/HistoryBuffer.js +8 -8
  14. package/dist-cjs/lib/HistoryBuffer.js.map +2 -2
  15. package/dist-cjs/lib/capture.js +1 -3
  16. package/dist-cjs/lib/capture.js.map +2 -2
  17. package/dist-cjs/lib/constants.js.map +2 -2
  18. package/dist-cjs/lib/helpers.js +3 -11
  19. package/dist-cjs/lib/helpers.js.map +2 -2
  20. package/dist-cjs/lib/localStorageAtom.js +7 -2
  21. package/dist-cjs/lib/localStorageAtom.js.map +2 -2
  22. package/dist-cjs/lib/transactions.js +11 -19
  23. package/dist-cjs/lib/transactions.js.map +2 -2
  24. package/dist-cjs/lib/types.js.map +1 -1
  25. package/dist-cjs/lib/warnings.js +2 -4
  26. package/dist-cjs/lib/warnings.js.map +2 -2
  27. package/dist-esm/index.d.mts +26 -27
  28. package/dist-esm/index.mjs +1 -1
  29. package/dist-esm/lib/ArraySet.mjs +47 -144
  30. package/dist-esm/lib/ArraySet.mjs.map +2 -2
  31. package/dist-esm/lib/Atom.mjs +12 -26
  32. package/dist-esm/lib/Atom.mjs.map +2 -2
  33. package/dist-esm/lib/Computed.mjs +36 -64
  34. package/dist-esm/lib/Computed.mjs.map +2 -2
  35. package/dist-esm/lib/EffectScheduler.mjs +1 -1
  36. package/dist-esm/lib/EffectScheduler.mjs.map +2 -2
  37. package/dist-esm/lib/HistoryBuffer.mjs +8 -8
  38. package/dist-esm/lib/HistoryBuffer.mjs.map +2 -2
  39. package/dist-esm/lib/capture.mjs +1 -3
  40. package/dist-esm/lib/capture.mjs.map +2 -2
  41. package/dist-esm/lib/constants.mjs.map +2 -2
  42. package/dist-esm/lib/helpers.mjs +3 -11
  43. package/dist-esm/lib/helpers.mjs.map +2 -2
  44. package/dist-esm/lib/localStorageAtom.mjs +7 -2
  45. package/dist-esm/lib/localStorageAtom.mjs.map +2 -2
  46. package/dist-esm/lib/transactions.mjs +11 -19
  47. package/dist-esm/lib/transactions.mjs.map +2 -2
  48. package/dist-esm/lib/types.mjs.map +1 -1
  49. package/dist-esm/lib/warnings.mjs +2 -4
  50. package/dist-esm/lib/warnings.mjs.map +2 -2
  51. package/package.json +2 -2
  52. package/src/lib/ArraySet.ts +68 -176
  53. package/src/lib/Atom.ts +27 -31
  54. package/src/lib/Computed.ts +68 -96
  55. package/src/lib/EffectScheduler.ts +9 -8
  56. package/src/lib/HistoryBuffer.ts +12 -10
  57. package/src/lib/__tests__/ArraySet.test.ts +39 -13
  58. package/src/lib/__tests__/EffectScheduler.test.ts +18 -0
  59. package/src/lib/__tests__/HistoryBuffer.test.ts +6 -3
  60. package/src/lib/__tests__/computed.test.ts +75 -0
  61. package/src/lib/__tests__/errors.test.ts +24 -0
  62. package/src/lib/__tests__/helpers.test.ts +7 -11
  63. package/src/lib/__tests__/history.test.ts +32 -2
  64. package/src/lib/__tests__/localStorageAtom.test.ts +15 -0
  65. package/src/lib/capture.ts +13 -13
  66. package/src/lib/constants.ts +3 -22
  67. package/src/lib/helpers.ts +15 -140
  68. package/src/lib/localStorageAtom.ts +9 -2
  69. package/src/lib/transactions.ts +23 -47
  70. package/src/lib/types.ts +7 -7
  71. package/src/lib/warnings.ts +2 -10
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/helpers.ts"],
4
- "sourcesContent": ["import { Child, Signal } from './types'\n\n/**\n * Get whether the given value is a child.\n *\n * @param x The value to check.\n * @returns True if the value is a child, false otherwise.\n * @internal\n */\nfunction isChild(x: any): x is Child {\n\treturn x && typeof x === 'object' && 'parents' in x\n}\n\n/**\n * Checks if any of a child's parent signals have changed by comparing their current epochs\n * with the child's cached view of those epochs.\n *\n * This function is used internally to determine if a computed signal or effect needs to\n * be re-evaluated because one of its dependencies has changed.\n *\n * @param child - The child (computed signal or effect) to check for parent changes\n * @returns `true` if any parent signal has changed since the child last observed it, `false` otherwise\n * @example\n * ```ts\n * const childSignal = computed('child', () => parentAtom.get())\n * // Check if the child needs to recompute\n * if (haveParentsChanged(childSignal)) {\n * // Recompute the child's value\n * }\n * ```\n * @internal\n */\nexport function haveParentsChanged(child: Child): boolean {\n\tfor (let i = 0, n = child.parents.length; i < n; i++) {\n\t\t// Get the parent's value without capturing it.\n\t\tchild.parents[i].__unsafe__getWithoutCapture(true)\n\n\t\t// If the parent's epoch does not match the child's view of the parent's epoch, then the parent has changed.\n\t\tif (child.parents[i].lastChangedEpoch !== child.parentEpochs[i]) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * Detaches a child signal from its parent signal, removing the parent-child relationship\n * in the reactive dependency graph. If the parent has no remaining children and is itself\n * a child, it will recursively detach from its own parents.\n *\n * This function is used internally to clean up the dependency graph when signals are no\n * longer needed or when dependencies change.\n *\n * @param parent - The parent signal to detach from\n * @param child - The child signal to detach\n * @example\n * ```ts\n * // When a computed signal's dependencies change\n * const oldParent = atom('old', 1)\n * const child = computed('child', () => oldParent.get())\n * // Later, detach the child from the old parent\n * detach(oldParent, child)\n * ```\n * @internal\n */\nexport function detach(parent: Signal<any>, child: Child) {\n\t// If the child is not attached to the parent, do nothing.\n\tif (!parent.children.remove(child)) {\n\t\treturn\n\t}\n\n\t// If the parent has no more children, then detach the parent from its parents.\n\tif (parent.children.isEmpty && isChild(parent)) {\n\t\tfor (let i = 0, n = parent.parents.length; i < n; i++) {\n\t\t\tdetach(parent.parents[i], parent)\n\t\t}\n\t}\n}\n\n/**\n * Attaches a child signal to its parent signal, establishing a parent-child relationship\n * in the reactive dependency graph. If the parent is itself a child, it will recursively\n * attach to its own parents to maintain the dependency chain.\n *\n * This function is used internally when dependencies are captured during computed signal\n * evaluation or effect execution.\n *\n * @param parent - The parent signal to attach to\n * @param child - The child signal to attach\n * @example\n * ```ts\n * // When a computed signal captures a new dependency\n * const parentAtom = atom('parent', 1)\n * const child = computed('child', () => parentAtom.get())\n * // Internally, attach is called to establish the dependency\n * attach(parentAtom, child)\n * ```\n * @internal\n */\nexport function attach(parent: Signal<any>, child: Child) {\n\t// If the child is already attached to the parent, do nothing.\n\tif (!parent.children.add(child)) {\n\t\treturn\n\t}\n\n\t// If the parent itself is a child, add the parent to the parent's parents.\n\tif (isChild(parent)) {\n\t\tfor (let i = 0, n = parent.parents.length; i < n; i++) {\n\t\t\tattach(parent.parents[i], parent)\n\t\t}\n\t}\n}\n\n/**\n * Checks if two values are equal using the equality semantics of @tldraw/state.\n *\n * This function performs equality checks in the following order:\n * 1. Reference equality (`===`)\n * 2. `Object.is()` equality (handles NaN and -0/+0 cases)\n * 3. Custom `.equals()` method when the left-hand value provides one\n *\n * This is used internally to determine if a signal's value has actually changed\n * when setting new values, preventing unnecessary updates and re-computations.\n *\n * @param a - The first value to compare\n * @param b - The second value to compare\n * @returns `true` if the values are considered equal, `false` otherwise\n * @example\n * ```ts\n * equals(1, 1) // true\n * equals(NaN, NaN) // true (unlike === which returns false)\n * equals({ equals: (other: any) => other.id === 1 }, { id: 1 }) // Uses custom equals method\n * ```\n * @internal\n */\nexport function equals(a: any, b: any): boolean {\n\tconst shallowEquals =\n\t\ta === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === 'function' && a.equals(b))\n\treturn shallowEquals\n}\n\n/**\n * A TypeScript utility function for exhaustiveness checking in switch statements and\n * conditional branches. This function should never be called at runtime\u2014it exists\n * purely for compile-time type checking and is `undefined` in emitted JavaScript.\n *\n * @param x - A value that should be of type `never`\n * @throws Always at runtime because the identifier is undefined\n * @example\n * ```ts\n * type Color = 'red' | 'blue'\n *\n * function handleColor(color: Color) {\n * switch (color) {\n * case 'red':\n * return 'Stop'\n * case 'blue':\n * return 'Go'\n * default:\n * return assertNever(color) // TypeScript error if not all cases handled\n * }\n * }\n * ```\n * @public\n */\nexport declare function assertNever(x: never): never\n\n/**\n * Creates or retrieves a singleton instance using a global symbol registry.\n * This ensures that the same instance is shared across all code that uses\n * the same key, even across different module boundaries.\n *\n * The singleton is stored on `globalThis` using a symbol created with\n * `Symbol.for()`, which ensures global uniqueness across realms.\n *\n * @param key - A unique string identifier for the singleton\n * @param init - A function that creates the initial value if it doesn't exist\n * @returns The singleton instance\n * @example\n * ```ts\n * // Create a singleton logger\n * const logger = singleton('logger', () => new Logger())\n *\n * // Elsewhere in the codebase, get the same logger instance\n * const sameLogger = singleton('logger', () => new Logger())\n * // logger === sameLogger\n * ```\n * @internal\n */\nexport function singleton<T>(key: string, init: () => T): T {\n\tconst symbol = Symbol.for(`com.tldraw.state/${key}`)\n\tconst global = globalThis as any\n\tglobal[symbol] ??= init()\n\treturn global[symbol]\n}\n\n/**\n * @public\n */\nexport const EMPTY_ARRAY: [] = singleton('empty_array', () => Object.freeze([]) as any)\n\n/**\n * Checks if a signal has any active reactors (effects or computed signals) that are\n * currently listening to it. This determines whether changes to the signal will\n * cause any side effects or recomputations to occur.\n *\n * A signal is considered to have active reactors if any of its child dependencies\n * are actively listening for changes.\n *\n * @param signal - The signal to check for active reactors\n * @returns `true` if the signal has active reactors, `false` otherwise\n * @example\n * ```ts\n * const count = atom('count', 0)\n *\n * console.log(hasReactors(count)) // false - no effects listening\n *\n * const stop = react('logger', () => console.log(count.get()))\n * console.log(hasReactors(count)) // true - effect is listening\n *\n * stop()\n * console.log(hasReactors(count)) // false - effect stopped\n * ```\n * @public\n */\nexport function hasReactors(signal: Signal<any>) {\n\tfor (const child of signal.children) {\n\t\tif (child.isActivelyListening) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,SAAS,QAAQ,GAAoB;AACpC,SAAO,KAAK,OAAO,MAAM,YAAY,aAAa;AACnD;AAqBO,SAAS,mBAAmB,OAAuB;AACzD,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,QAAQ,IAAI,GAAG,KAAK;AAErD,UAAM,QAAQ,CAAC,EAAE,4BAA4B,IAAI;AAGjD,QAAI,MAAM,QAAQ,CAAC,EAAE,qBAAqB,MAAM,aAAa,CAAC,GAAG;AAChE,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO;AACR;AAsBO,SAAS,OAAO,QAAqB,OAAc;AAEzD,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,GAAG;AACnC;AAAA,EACD;AAGA,MAAI,OAAO,SAAS,WAAW,QAAQ,MAAM,GAAG;AAC/C,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IAAI,GAAG,KAAK;AACtD,aAAO,OAAO,QAAQ,CAAC,GAAG,MAAM;AAAA,IACjC;AAAA,EACD;AACD;AAsBO,SAAS,OAAO,QAAqB,OAAc;AAEzD,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,GAAG;AAChC;AAAA,EACD;AAGA,MAAI,QAAQ,MAAM,GAAG;AACpB,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IAAI,GAAG,KAAK;AACtD,aAAO,OAAO,QAAQ,CAAC,GAAG,MAAM;AAAA,IACjC;AAAA,EACD;AACD;AAwBO,SAAS,OAAO,GAAQ,GAAiB;AAC/C,QAAM,gBACL,MAAM,KAAK,OAAO,GAAG,GAAG,CAAC,KAAK,QAAQ,KAAK,KAAK,OAAO,EAAE,WAAW,cAAc,EAAE,OAAO,CAAC,CAAC;AAC9F,SAAO;AACR;AAkDO,SAAS,UAAa,KAAa,MAAkB;AAC3D,QAAM,SAAS,uBAAO,IAAI,oBAAoB,GAAG,EAAE;AACnD,QAAM,SAAS;AACf,SAAO,MAAM,MAAM,KAAK;AACxB,SAAO,OAAO,MAAM;AACrB;AAKO,MAAM,cAAkB,UAAU,eAAe,MAAM,OAAO,OAAO,CAAC,CAAC,CAAQ;AA0B/E,SAAS,YAAY,QAAqB;AAChD,aAAW,SAAS,OAAO,UAAU;AACpC,QAAI,MAAM,qBAAqB;AAC9B,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO;AACR;",
4
+ "sourcesContent": ["import { Child, Signal } from './types'\n\nfunction isChild(x: any): x is Child {\n\treturn x && typeof x === 'object' && 'parents' in x\n}\n\n/**\n * Whether any of the child's parents changed since the child last recorded their epochs. O(parents);\n * returns at the first changed parent, so parents after it are not brought up to date.\n *\n * @internal\n */\nexport function haveParentsChanged(child: Child): boolean {\n\tfor (let i = 0, n = child.parents.length; i < n; i++) {\n\t\tconst parent = child.parents[i]\n\t\t// Bring the parent up to date first: a computed parent's `lastChangedEpoch` only moves when\n\t\t// it is re-derived.\n\t\tparent.__unsafe__getWithoutCapture(true)\n\n\t\tif (parent.lastChangedEpoch !== child.parentEpochs[i]) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * Removes `child` from `parent.children`. A computed parent that loses its last child stops\n * listening itself, recursively, so a signal's `children` set is empty whenever nothing downstream\n * is actively listening.\n *\n * @internal\n */\nexport function detach(parent: Signal<any>, child: Child) {\n\tif (!parent.children.remove(child)) {\n\t\treturn\n\t}\n\n\tif (parent.children.isEmpty && isChild(parent)) {\n\t\tfor (let i = 0, n = parent.parents.length; i < n; i++) {\n\t\t\tdetach(parent.parents[i], parent)\n\t\t}\n\t}\n}\n\n/**\n * Adds `child` to `parent.children`. A computed parent that gains its first child starts\n * listening itself, recursively, so that changes to any ancestor are traversed down to the child.\n *\n * @internal\n */\nexport function attach(parent: Signal<any>, child: Child) {\n\tif (!parent.children.add(child)) {\n\t\treturn\n\t}\n\n\tif (isChild(parent)) {\n\t\tfor (let i = 0, n = parent.parents.length; i < n; i++) {\n\t\t\tattach(parent.parents[i], parent)\n\t\t}\n\t}\n}\n\n/**\n * The default equality used for change detection: `===`, then `Object.is` (so `NaN` equals\n * `NaN`; `0` and `-0` are already equal by `===`), then the old value's own `.equals(b)` method\n * if it has one. Only the old value's `equals` is consulted.\n *\n * @internal\n */\nexport function equals(a: any, b: any): boolean {\n\tconst shallowEquals =\n\t\ta === b || Object.is(a, b) || Boolean(a && b && typeof a.equals === 'function' && a.equals(b))\n\treturn shallowEquals\n}\n\n/**\n * Creates or retrieves a singleton instance using a global symbol registry.\n * This ensures that the same instance is shared across all code that uses\n * the same key, even across different module boundaries.\n *\n * The singleton is stored on `globalThis` using a symbol created with\n * `Symbol.for()`, which ensures global uniqueness across realms.\n *\n * @param key - A unique string identifier for the singleton\n * @param init - A function that creates the initial value if it doesn't exist\n * @returns The singleton instance\n * @example\n * ```ts\n * // Create a singleton logger\n * const logger = singleton('logger', () => new Logger())\n *\n * // Elsewhere in the codebase, get the same logger instance\n * const sameLogger = singleton('logger', () => new Logger())\n * // logger === sameLogger\n * ```\n * @internal\n */\nexport function singleton<T>(key: string, init: () => T): T {\n\tconst symbol = Symbol.for(`com.tldraw.state/${key}`)\n\tconst global = globalThis as any\n\tglobal[symbol] ??= init()\n\treturn global[symbol]\n}\n\n/**\n * @public\n */\nexport const EMPTY_ARRAY: [] = singleton('empty_array', () => Object.freeze([]) as any)\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;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;AAQO,SAAS,OAAO,QAAqB,OAAc;AACzD,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,GAAG;AAChC;AAAA,EACD;AAEA,MAAI,QAAQ,MAAM,GAAG;AACpB,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,IAAI,GAAG,KAAK;AACtD,aAAO,OAAO,QAAQ,CAAC,GAAG,MAAM;AAAA,IACjC;AAAA,EACD;AACD;AASO,SAAS,OAAO,GAAQ,GAAiB;AAC/C,QAAM,gBACL,MAAM,KAAK,OAAO,GAAG,GAAG,CAAC,KAAK,QAAQ,KAAK,KAAK,OAAO,EAAE,WAAW,cAAc,EAAE,OAAO,CAAC,CAAC;AAC9F,SAAO;AACR;AAwBO,SAAS,UAAa,KAAa,MAAkB;AAC3D,QAAM,SAAS,uBAAO,IAAI,oBAAoB,GAAG,EAAE;AACnD,QAAM,SAAS;AACf,SAAO,MAAM,MAAM,KAAK;AACxB,SAAO,OAAO,MAAM;AACrB;AAKO,MAAM,cAAkB,UAAU,eAAe,MAAM,OAAO,OAAO,CAAC,CAAC,CAAQ;",
6
6
  "names": []
7
7
  }
@@ -50,10 +50,15 @@ function localStorageAtom(name, initialValue, options) {
50
50
  } catch {
51
51
  }
52
52
  };
53
- window.addEventListener("storage", handleStorageEvent);
53
+ const canListen = typeof window !== "undefined";
54
+ if (canListen) {
55
+ window.addEventListener("storage", handleStorageEvent);
56
+ }
54
57
  const cleanup = () => {
55
58
  reactCleanup();
56
- window.removeEventListener("storage", handleStorageEvent);
59
+ if (canListen) {
60
+ window.removeEventListener("storage", handleStorageEvent);
61
+ }
57
62
  };
58
63
  return [outAtom, cleanup];
59
64
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/localStorageAtom.ts"],
4
- "sourcesContent": ["import { deleteFromLocalStorage, getFromLocalStorage, setInLocalStorage } from '@tldraw/utils'\nimport { Atom, atom, AtomOptions } from './Atom'\nimport { react } from './EffectScheduler'\n\n/**\n * Creates a new {@link Atom} that persists its value to localStorage.\n *\n * The atom is automatically synced with localStorage - changes to the atom are saved to localStorage,\n * and the initial value is read from localStorage if it exists. Returns both the atom and a cleanup\n * function that should be called to stop syncing when the atom is no longer needed. If you need to delete\n * the atom, you should do it manually after all cleanup functions have been called.\n *\n * @example\n * ```ts\n * const [theme, cleanup] = localStorageAtom('theme', 'light')\n *\n * theme.get() // 'light' or value from localStorage if it exists\n *\n * theme.set('dark') // updates atom and saves to localStorage\n *\n * // When done:\n * cleanup() // stops syncing to localStorage\n * ```\n *\n * @param name - The localStorage key and atom name. This is used for both localStorage persistence\n * and debugging/profiling purposes.\n * @param initialValue - The initial value of the atom, used if no value exists in localStorage.\n * @param options - Optional atom configuration. See {@link AtomOptions}.\n * @returns A tuple containing the atom and a cleanup function to stop localStorage syncing.\n * @public\n */\nexport function localStorageAtom<Value, Diff = unknown>(\n\tname: string,\n\tinitialValue: Value,\n\toptions?: AtomOptions<Value, Diff>\n): [Atom<Value, Diff>, () => void] {\n\t// Try to restore the initial value from localStorage\n\tlet _initialValue = initialValue\n\n\ttry {\n\t\tconst value = getFromLocalStorage(name)\n\t\tif (value) {\n\t\t\t_initialValue = JSON.parse(value) as Value\n\t\t}\n\t} catch {\n\t\t// If parsing fails, the stored value is corrupted - delete it and use the provided initial value\n\t\tdeleteFromLocalStorage(name)\n\t}\n\n\t// Create the atom with the restored or initial value\n\tconst outAtom = atom(name, _initialValue, options)\n\n\t// Set up automatic syncing: whenever the atom changes, save it to localStorage\n\tconst reactCleanup = react(`save ${name} to localStorage`, () => {\n\t\tsetInLocalStorage(name, JSON.stringify(outAtom.get()))\n\t})\n\n\t// Set up cross-tab sync: listen for storage events from other tabs\n\tconst handleStorageEvent = (event: StorageEvent) => {\n\t\t// Only handle events for this specific key\n\t\tif (event.key !== name) return\n\n\t\t// If the value was deleted in another tab\n\t\tif (event.newValue === null) {\n\t\t\toutAtom.set(initialValue)\n\t\t\treturn\n\t\t}\n\n\t\t// If the value was changed in another tab, update the atom\n\t\ttry {\n\t\t\tconst newValue = JSON.parse(event.newValue) as Value\n\t\t\toutAtom.set(newValue)\n\t\t} catch {\n\t\t\t// If parsing fails, the stored value is corrupted; preserve the existing value\n\t\t}\n\t}\n\n\twindow.addEventListener('storage', handleStorageEvent)\n\n\t// Combined cleanup function\n\tconst cleanup = () => {\n\t\treactCleanup()\n\t\twindow.removeEventListener('storage', handleStorageEvent)\n\t}\n\n\treturn [outAtom, cleanup]\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA+E;AAC/E,kBAAwC;AACxC,6BAAsB;AA6Bf,SAAS,iBACf,MACA,cACA,SACkC;AAElC,MAAI,gBAAgB;AAEpB,MAAI;AACH,UAAM,YAAQ,kCAAoB,IAAI;AACtC,QAAI,OAAO;AACV,sBAAgB,KAAK,MAAM,KAAK;AAAA,IACjC;AAAA,EACD,QAAQ;AAEP,6CAAuB,IAAI;AAAA,EAC5B;AAGA,QAAM,cAAU,kBAAK,MAAM,eAAe,OAAO;AAGjD,QAAM,mBAAe,8BAAM,QAAQ,IAAI,oBAAoB,MAAM;AAChE,wCAAkB,MAAM,KAAK,UAAU,QAAQ,IAAI,CAAC,CAAC;AAAA,EACtD,CAAC;AAGD,QAAM,qBAAqB,CAAC,UAAwB;AAEnD,QAAI,MAAM,QAAQ,KAAM;AAGxB,QAAI,MAAM,aAAa,MAAM;AAC5B,cAAQ,IAAI,YAAY;AACxB;AAAA,IACD;AAGA,QAAI;AACH,YAAM,WAAW,KAAK,MAAM,MAAM,QAAQ;AAC1C,cAAQ,IAAI,QAAQ;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACD;AAEA,SAAO,iBAAiB,WAAW,kBAAkB;AAGrD,QAAM,UAAU,MAAM;AACrB,iBAAa;AACb,WAAO,oBAAoB,WAAW,kBAAkB;AAAA,EACzD;AAEA,SAAO,CAAC,SAAS,OAAO;AACzB;",
4
+ "sourcesContent": ["import { deleteFromLocalStorage, getFromLocalStorage, setInLocalStorage } from '@tldraw/utils'\nimport { Atom, atom, AtomOptions } from './Atom'\nimport { react } from './EffectScheduler'\n\n/**\n * Creates a new {@link Atom} that persists its value to localStorage.\n *\n * The atom is automatically synced with localStorage - changes to the atom are saved to localStorage,\n * and the initial value is read from localStorage if it exists. Returns both the atom and a cleanup\n * function that should be called to stop syncing when the atom is no longer needed. If you need to delete\n * the atom, you should do it manually after all cleanup functions have been called.\n *\n * @example\n * ```ts\n * const [theme, cleanup] = localStorageAtom('theme', 'light')\n *\n * theme.get() // 'light' or value from localStorage if it exists\n *\n * theme.set('dark') // updates atom and saves to localStorage\n *\n * // When done:\n * cleanup() // stops syncing to localStorage\n * ```\n *\n * @param name - The localStorage key and atom name. This is used for both localStorage persistence\n * and debugging/profiling purposes.\n * @param initialValue - The initial value of the atom, used if no value exists in localStorage.\n * @param options - Optional atom configuration. See {@link AtomOptions}.\n * @returns A tuple containing the atom and a cleanup function to stop localStorage syncing.\n * @public\n */\nexport function localStorageAtom<Value, Diff = unknown>(\n\tname: string,\n\tinitialValue: Value,\n\toptions?: AtomOptions<Value, Diff>\n): [Atom<Value, Diff>, () => void] {\n\t// Try to restore the initial value from localStorage\n\tlet _initialValue = initialValue\n\n\ttry {\n\t\tconst value = getFromLocalStorage(name)\n\t\tif (value) {\n\t\t\t_initialValue = JSON.parse(value) as Value\n\t\t}\n\t} catch {\n\t\t// If parsing fails, the stored value is corrupted - delete it and use the provided initial value\n\t\tdeleteFromLocalStorage(name)\n\t}\n\n\t// Create the atom with the restored or initial value\n\tconst outAtom = atom(name, _initialValue, options)\n\n\t// Set up automatic syncing: whenever the atom changes, save it to localStorage\n\tconst reactCleanup = react(`save ${name} to localStorage`, () => {\n\t\tsetInLocalStorage(name, JSON.stringify(outAtom.get()))\n\t})\n\n\t// Set up cross-tab sync: listen for storage events from other tabs\n\tconst handleStorageEvent = (event: StorageEvent) => {\n\t\t// Only handle events for this specific key\n\t\tif (event.key !== name) return\n\n\t\t// If the value was deleted in another tab\n\t\tif (event.newValue === null) {\n\t\t\toutAtom.set(initialValue)\n\t\t\treturn\n\t\t}\n\n\t\t// If the value was changed in another tab, update the atom\n\t\ttry {\n\t\t\tconst newValue = JSON.parse(event.newValue) as Value\n\t\t\toutAtom.set(newValue)\n\t\t} catch {\n\t\t\t// If parsing fails, the stored value is corrupted; preserve the existing value\n\t\t}\n\t}\n\n\t// The storage helpers above tolerate environments without localStorage (Node, SSR); do the\n\t// same here rather than throwing on `window`.\n\tconst canListen = typeof window !== 'undefined'\n\tif (canListen) {\n\t\twindow.addEventListener('storage', handleStorageEvent)\n\t}\n\n\t// Combined cleanup function\n\tconst cleanup = () => {\n\t\treactCleanup()\n\t\tif (canListen) {\n\t\t\twindow.removeEventListener('storage', handleStorageEvent)\n\t\t}\n\t}\n\n\treturn [outAtom, cleanup]\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA+E;AAC/E,kBAAwC;AACxC,6BAAsB;AA6Bf,SAAS,iBACf,MACA,cACA,SACkC;AAElC,MAAI,gBAAgB;AAEpB,MAAI;AACH,UAAM,YAAQ,kCAAoB,IAAI;AACtC,QAAI,OAAO;AACV,sBAAgB,KAAK,MAAM,KAAK;AAAA,IACjC;AAAA,EACD,QAAQ;AAEP,6CAAuB,IAAI;AAAA,EAC5B;AAGA,QAAM,cAAU,kBAAK,MAAM,eAAe,OAAO;AAGjD,QAAM,mBAAe,8BAAM,QAAQ,IAAI,oBAAoB,MAAM;AAChE,wCAAkB,MAAM,KAAK,UAAU,QAAQ,IAAI,CAAC,CAAC;AAAA,EACtD,CAAC;AAGD,QAAM,qBAAqB,CAAC,UAAwB;AAEnD,QAAI,MAAM,QAAQ,KAAM;AAGxB,QAAI,MAAM,aAAa,MAAM;AAC5B,cAAQ,IAAI,YAAY;AACxB;AAAA,IACD;AAGA,QAAI;AACH,YAAM,WAAW,KAAK,MAAM,MAAM,QAAQ;AAC1C,cAAQ,IAAI,QAAQ;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACD;AAIA,QAAM,YAAY,OAAO,WAAW;AACpC,MAAI,WAAW;AACd,WAAO,iBAAiB,WAAW,kBAAkB;AAAA,EACtD;AAGA,QAAM,UAAU,MAAM;AACrB,iBAAa;AACb,QAAI,WAAW;AACd,aAAO,oBAAoB,WAAW,kBAAkB;AAAA,IACzD;AAAA,EACD;AAEA,SAAO,CAAC,SAAS,OAAO;AACzB;",
6
6
  "names": []
7
7
  }
@@ -39,20 +39,10 @@ class Transaction {
39
39
  isSync;
40
40
  asyncProcessCount = 0;
41
41
  initialAtomValues = /* @__PURE__ */ new Map();
42
- /**
43
- * Get whether this transaction is a root (no parents).
44
- *
45
- * @public
46
- */
47
42
  // eslint-disable-next-line tldraw/no-setter-getter
48
43
  get isRoot() {
49
44
  return this.parent === null;
50
45
  }
51
- /**
52
- * Commit the transaction's changes.
53
- *
54
- * @public
55
- */
56
46
  commit() {
57
47
  if (inst.globalIsReacting) {
58
48
  for (const atom of this.initialAtomValues.keys()) {
@@ -61,9 +51,14 @@ class Transaction {
61
51
  } else if (this.isRoot) {
62
52
  flushChanges(this.initialAtomValues.keys());
63
53
  } else {
54
+ const parentValues = this.parent.initialAtomValues;
55
+ if (parentValues.size === 0) {
56
+ this.parent.initialAtomValues = this.initialAtomValues;
57
+ return;
58
+ }
64
59
  this.initialAtomValues.forEach((value, atom) => {
65
- if (!this.parent.initialAtomValues.has(atom)) {
66
- this.parent.initialAtomValues.set(atom, value);
60
+ if (!parentValues.has(atom)) {
61
+ parentValues.set(atom, value);
67
62
  }
68
63
  });
69
64
  }
@@ -113,10 +108,6 @@ function traverseChild(child) {
113
108
  child.children.visit(traverseChild);
114
109
  }
115
110
  }
116
- function traverse(reactors, child) {
117
- traverseReactors = reactors;
118
- traverseChild(child);
119
- }
120
111
  function flushChanges(atoms) {
121
112
  if (inst.globalIsReacting) {
122
113
  throw new Error("flushChanges cannot be called during a reaction");
@@ -127,8 +118,9 @@ function flushChanges(atoms) {
127
118
  inst.globalIsReacting = true;
128
119
  inst.reactionEpoch = inst.globalEpoch;
129
120
  const reactors = /* @__PURE__ */ new Set();
121
+ traverseReactors = reactors;
130
122
  for (const atom of atoms) {
131
- atom.children.visit((child) => traverse(reactors, child));
123
+ atom.children.visit(traverseChild);
132
124
  }
133
125
  for (const r of reactors) {
134
126
  r.maybeScheduleEffect();
@@ -163,8 +155,8 @@ function atomDidChange(atom, previousValue) {
163
155
  }
164
156
  }
165
157
  function traverseAtomForCleanup(atom) {
166
- const rs = inst.cleanupReactors ??= /* @__PURE__ */ new Set();
167
- atom.children.visit((child) => traverse(rs, child));
158
+ traverseReactors = inst.cleanupReactors ??= /* @__PURE__ */ new Set();
159
+ atom.children.visit(traverseChild);
168
160
  }
169
161
  function advanceGlobalEpoch() {
170
162
  inst.globalEpoch++;
@@ -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/**\n\t * Get whether this transaction is a root (no parents).\n\t *\n\t * @public\n\t */\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget isRoot() {\n\t\treturn this.parent === null\n\t}\n\n\t/**\n\t * Commit the transaction's changes.\n\t *\n\t * @public\n\t */\n\tcommit() {\n\t\tif (inst.globalIsReacting) {\n\t\t\t// if we're committing during a reaction we actually need to\n\t\t\t// use the 'cleanup' reactors set to ensure we re-run effects if necessary\n\t\t\tfor (const atom of this.initialAtomValues.keys()) {\n\t\t\t\ttraverseAtomForCleanup(atom)\n\t\t\t}\n\t\t} else if (this.isRoot) {\n\t\t\t// For root transactions, flush changed atoms\n\t\t\tflushChanges(this.initialAtomValues.keys())\n\t\t} else {\n\t\t\t// For transactions with parents, add the transaction's initial values to the parent's.\n\t\t\tthis.initialAtomValues.forEach((value, atom) => {\n\t\t\t\tif (!this.parent!.initialAtomValues.has(atom)) {\n\t\t\t\t\tthis.parent!.initialAtomValues.set(atom, value)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\n\t/**\n\t * Abort the transaction.\n\t *\n\t * @public\n\t */\n\tabort() {\n\t\tinst.globalEpoch++\n\n\t\t// Reset each of the transaction's atoms to its initial value.\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\t// Commit the changes.\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\treactionEpoch: GLOBAL_START_EPOCH + 1,\n}))\n\n/**\n * Gets the current reaction epoch, which is used to track when reactions are running.\n * The reaction epoch is updated at the start of each reaction cycle.\n *\n * @returns The current reaction epoch number\n * @public\n */\nexport function getReactionEpoch() {\n\treturn inst.reactionEpoch\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 * Checks whether any reactions are currently executing.\n * When true, the system is in the middle of processing effects and side effects.\n *\n * @returns True if reactions are currently running, false otherwise\n * @public\n */\nexport function getIsReacting() {\n\treturn inst.globalIsReacting\n}\n\n// Reusable state for traverse to avoid closure allocation\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\nfunction traverse(reactors: Set<Reactor>, child: Child) {\n\ttraverseReactors = reactors\n\ttraverseChild(child)\n}\n\n/**\n * Collect all of the reactors that need to run for an atom and run them.\n *\n * @param atoms - The atoms to flush changes for.\n */\nfunction flushChanges(atoms: Iterable<_Atom>) {\n\tif (inst.globalIsReacting) {\n\t\tthrow new Error('flushChanges cannot be called during a reaction')\n\t}\n\n\tconst outerTxn = inst.currentTransaction\n\ttry {\n\t\t// clear the transaction stack\n\t\tinst.currentTransaction = null\n\t\tinst.globalIsReacting = true\n\t\tinst.reactionEpoch = inst.globalEpoch\n\n\t\t// Collect all of the visited reactors.\n\t\tconst reactors = new Set<Reactor>()\n\n\t\tfor (const atom of atoms) {\n\t\t\tatom.children.visit((child) => traverse(reactors, child))\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/**\n * Handle a change to an atom.\n *\n * @param atom The atom that changed.\n * @param previousValue The atom's previous value.\n *\n * @internal\n */\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.\n\t\tif (!inst.currentTransaction.initialAtomValues.has(atom)) {\n\t\t\tinst.currentTransaction.initialAtomValues.set(atom, previousValue)\n\t\t}\n\t} else if (inst.globalIsReacting) {\n\t\t// If the atom changed during the reaction phase of flushChanges\n\t\t// (and there are no transactions started inside the reaction phase)\n\t\t// then we are past the point where a transaction can be aborted\n\t\t// so we don't need to note down the previousValue.\n\t\ttraverseAtomForCleanup(atom)\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\tconst rs = (inst.cleanupReactors ??= new Set())\n\tatom.children.visit((child) => traverse(rs, child))\n}\n\n/**\n * Advances the global epoch counter by one.\n * This is used internally to track when changes occur across the reactive system.\n *\n * @internal\n */\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.\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 * // Does not log\n * // firstName.get() === 'John'\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 * // Does not log\n * // firstName.get() === 'John'\n * // lastName.get() === 'Doe'\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\t// Set the current transaction to the transaction\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\t// Run the function.\n\t\t\tresult = fn(() => (rollback = true))\n\t\t} catch (e) {\n\t\t\t// Abort the transaction if the function throws.\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\t// If the rollback was triggered, abort the transaction.\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\t// Set the current transaction to the transaction's parent.\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\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\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\tinst.currentTransaction = null\n\n\tif (typeof error !== 'undefined') {\n\t\t// If the rollback was triggered, abort the transaction.\n\t\ttxn.abort()\n\t\tthrow error\n\t} else {\n\t\ttxn.commit()\n\t\treturn result\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\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 (inst.globalIsReacting) {\n\t\t\t// if we're committing during a reaction we actually need to\n\t\t\t// use the 'cleanup' reactors set to ensure we re-run effects if necessary\n\t\t\tfor (const atom of this.initialAtomValues.keys()) {\n\t\t\t\ttraverseAtomForCleanup(atom)\n\t\t\t}\n\t\t} else if (this.isRoot) {\n\t\t\t// For root transactions, flush changed atoms\n\t\t\tflushChanges(this.initialAtomValues.keys())\n\t\t} else {\n\t\t\t// For transactions with parents, add the transaction's initial values to the parent's.\n\t\t\t// A parent that has recorded nothing yet adopts the map outright: this transaction is\n\t\t\t// finished with it, and the common nested case is a single inner transaction doing all\n\t\t\t// the writes.\n\t\t\tconst parentValues = this.parent!.initialAtomValues\n\t\t\tif (parentValues.size === 0) {\n\t\t\t\tthis.parent!.initialAtomValues = this.initialAtomValues\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.initialAtomValues.forEach((value, atom) => {\n\t\t\t\tif (!parentValues.has(atom)) {\n\t\t\t\t\tparentValues.set(atom, value)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\n\t/**\n\t * Abort the transaction.\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\treactionEpoch: GLOBAL_START_EPOCH + 1,\n}))\n\n/**\n * Gets the current reaction epoch, which is used to track when reactions are running.\n * The reaction epoch is updated at the start of each reaction cycle.\n *\n * @returns The current reaction epoch number\n * @public\n */\nexport function getReactionEpoch() {\n\treturn inst.reactionEpoch\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 * Checks whether any reactions are currently executing.\n * When true, the system is in the middle of processing effects and side effects.\n *\n * @returns True if reactions are currently running, false otherwise\n * @public\n */\nexport function getIsReacting() {\n\treturn inst.globalIsReacting\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.\n *\n * @param atoms - The atoms to flush changes for.\n */\nfunction flushChanges(atoms: Iterable<_Atom>) {\n\tif (inst.globalIsReacting) {\n\t\tthrow new Error('flushChanges cannot be called during a reaction')\n\t}\n\n\tconst outerTxn = inst.currentTransaction\n\ttry {\n\t\t// clear the transaction stack\n\t\tinst.currentTransaction = null\n\t\tinst.globalIsReacting = true\n\t\tinst.reactionEpoch = inst.globalEpoch\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.\n\t\tif (!inst.currentTransaction.initialAtomValues.has(atom)) {\n\t\t\tinst.currentTransaction.initialAtomValues.set(atom, previousValue)\n\t\t}\n\t} else if (inst.globalIsReacting) {\n\t\t// If the atom changed during the reaction phase of flushChanges\n\t\t// (and there are no transactions started inside the reaction phase)\n\t\t// then we are past the point where a transaction can be aborted\n\t\t// so we don't need to note down the previousValue.\n\t\ttraverseAtomForCleanup(atom)\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\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\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\tinst.currentTransaction = null\n\n\tif (typeof error !== 'undefined') {\n\t\t// If the rollback was triggered, abort the transaction.\n\t\ttxn.abort()\n\t\tthrow error\n\t} else {\n\t\ttxn.commit()\n\t\treturn result\n\t}\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,uBAAmC;AACnC,qBAA0B;AAQ1B,MAAM,YAAY;AAAA,EAEjB,YACiB,QACA,QACf;AAFe;AACA;AAAA,EACd;AAAA,EAFc;AAAA,EACA;AAAA,EAHjB,oBAAoB;AAAA,EAMpB,oBAAoB,oBAAI,IAAgB;AAAA;AAAA,EAGxC,IAAI,SAAS;AACZ,WAAO,KAAK,WAAW;AAAA,EACxB;AAAA,EAEA,SAAS;AACR,QAAI,KAAK,kBAAkB;AAG1B,iBAAW,QAAQ,KAAK,kBAAkB,KAAK,GAAG;AACjD,+BAAuB,IAAI;AAAA,MAC5B;AAAA,IACD,WAAW,KAAK,QAAQ;AAEvB,mBAAa,KAAK,kBAAkB,KAAK,CAAC;AAAA,IAC3C,OAAO;AAKN,YAAM,eAAe,KAAK,OAAQ;AAClC,UAAI,aAAa,SAAS,GAAG;AAC5B,aAAK,OAAQ,oBAAoB,KAAK;AACtC;AAAA,MACD;AACA,WAAK,kBAAkB,QAAQ,CAAC,OAAO,SAAS;AAC/C,YAAI,CAAC,aAAa,IAAI,IAAI,GAAG;AAC5B,uBAAa,IAAI,MAAM,KAAK;AAAA,QAC7B;AAAA,MACD,CAAC;AAAA,IACF;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ;AACP,SAAK;AAEL,SAAK,kBAAkB,QAAQ,CAAC,OAAO,SAAS;AAC/C,WAAK,IAAI,KAAK;AACd,WAAK,eAAe,MAAM;AAAA,IAC3B,CAAC;AAED,SAAK,OAAO;AAAA,EACb;AACD;AAEA,MAAM,WAAO,0BAAU,gBAAgB,OAAO;AAAA;AAAA,EAE7C,aAAa,sCAAqB;AAAA;AAAA,EAElC,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EAEpB,iBAAiB;AAAA,EACjB,eAAe,sCAAqB;AACrC,EAAE;AASK,SAAS,mBAAmB;AAClC,SAAO,KAAK;AACb;AASO,SAAS,iBAAiB;AAChC,SAAO,KAAK;AACb;AASO,SAAS,gBAAgB;AAC/B,SAAO,KAAK;AACb;AAKA,IAAI;AAEJ,SAAS,cAAc,OAAc;AACpC,MAAI,MAAM,uBAAuB,KAAK,aAAa;AAClD;AAAA,EACD;AAEA,QAAM,qBAAqB,KAAK;AAEhC,MAAI,yBAAyB,OAAO;AACnC,qBAAiB,IAAI,KAA2B;AAAA,EACjD,OAAO;AACN;AAAC,IAAC,MAA6B,SAAS,MAAM,aAAa;AAAA,EAC5D;AACD;AAOA,SAAS,aAAa,OAAwB;AAC7C,MAAI,KAAK,kBAAkB;AAC1B,UAAM,IAAI,MAAM,iDAAiD;AAAA,EAClE;AAEA,QAAM,WAAW,KAAK;AACtB,MAAI;AAEH,SAAK,qBAAqB;AAC1B,SAAK,mBAAmB;AACxB,SAAK,gBAAgB,KAAK;AAE1B,UAAM,WAAW,oBAAI,IAAa;AAClC,uBAAmB;AACnB,eAAW,QAAQ,OAAO;AACzB,WAAK,SAAS,MAAM,aAAa;AAAA,IAClC;AAGA,eAAW,KAAK,UAAU;AACzB,QAAE,oBAAoB;AAAA,IACvB;AAEA,QAAI,cAAc;AAClB,WAAO,KAAK,iBAAiB,MAAM;AAClC,UAAI,gBAAgB,KAAM;AACzB,cAAM,IAAI,MAAM,sCAAsC;AAAA,MACvD;AACA,YAAMA,YAAW,KAAK;AACtB,WAAK,kBAAkB;AACvB,iBAAW,KAAKA,WAAU;AACzB,UAAE,oBAAoB;AAAA,MACvB;AAAA,IACD;AAAA,EACD,UAAE;AACD,SAAK,kBAAkB;AACvB,SAAK,mBAAmB;AACxB,SAAK,qBAAqB;AAC1B,uBAAmB;AAAA,EACpB;AACD;AAGO,SAAS,cAAc,MAAa,eAAoB;AAC9D,MAAI,KAAK,oBAAoB;AAI5B,QAAI,CAAC,KAAK,mBAAmB,kBAAkB,IAAI,IAAI,GAAG;AACzD,WAAK,mBAAmB,kBAAkB,IAAI,MAAM,aAAa;AAAA,IAClE;AAAA,EACD,WAAW,KAAK,kBAAkB;AAKjC,2BAAuB,IAAI;AAAA,EAC5B,OAAO;AAEN,iBAAa,CAAC,IAAI,CAAC;AAAA,EACpB;AACD;AAEA,SAAS,uBAAuB,MAAa;AAC5C,qBAAmB,KAAK,oBAAoB,oBAAI,IAAI;AACpD,OAAK,SAAS,MAAM,aAAa;AAClC;AAGO,SAAS,qBAAqB;AACpC,OAAK;AACN;AA6EO,SAAS,YAAe,IAAiC;AAC/D,QAAM,MAAM,IAAI,YAAY,KAAK,oBAAoB,IAAI;AAEzD,OAAK,qBAAqB;AAE1B,MAAI;AACH,QAAI,SAAS;AACb,QAAI,WAAW;AAEf,QAAI;AACH,eAAS,GAAG,MAAO,WAAW,IAAK;AAAA,IACpC,SAAS,GAAG;AACX,UAAI,MAAM;AACV,YAAM;AAAA,IACP;AAEA,QAAI,KAAK,uBAAuB,KAAK;AACpC,YAAM,IAAI,MAAM,gCAAgC;AAAA,IACjD;AAEA,QAAI,UAAU;AACb,UAAI,MAAM;AAAA,IACX,OAAO;AACN,UAAI,OAAO;AAAA,IACZ;AAEA,WAAO;AAAA,EACR,UAAE;AACD,SAAK,qBAAqB,IAAI;AAAA,EAC/B;AACD;AA2BO,SAAS,SAAY,IAAgB;AAC3C,MAAI,KAAK,oBAAoB;AAC5B,WAAO,GAAG;AAAA,EACX;AACA,SAAO,YAAY,EAAE;AACtB;AA0BA,eAAsB,kBAAqB,IAAsB;AAIhE,MAAI,KAAK,oBAAoB,QAAQ;AACpC,UAAM,IAAI,MAAM,8DAA8D;AAAA,EAC/E;AAKA,SAAO,KAAK,kBAAkB;AAC7B,UAAM,IAAI,QAAQ,CAAC,MAAM,eAAe,MAAM,EAAE,IAAI,CAAC,CAAC;AAAA,EACvD;AAEA,QAAM,MAAM,KAAK,sBAAsB,IAAI,YAAY,MAAM,KAAK;AAGlE,MAAI,IAAI,OAAQ,OAAM,IAAI,MAAM,8DAA8D;AAE9F,OAAK,qBAAqB;AAC1B,MAAI;AAEJ,MAAI,SAAS;AAEb,MAAI,QAAQ;AACZ,MAAI;AAEH,aAAS,MAAM,GAAG;AAAA,EACnB,SAAS,GAAG;AAEX,YAAQ,KAAK;AAAA,EACd;AAEA,MAAI,EAAE,IAAI,oBAAoB,GAAG;AAChC,QAAI,OAAO,UAAU,aAAa;AAEjC,YAAM;AAAA,IACP,OAAO;AACN,aAAO;AAAA,IACR;AAAA,EACD;AAEA,OAAK,qBAAqB;AAE1B,MAAI,OAAO,UAAU,aAAa;AAEjC,QAAI,MAAM;AACV,UAAM;AAAA,EACP,OAAO;AACN,QAAI,OAAO;AACX,WAAO;AAAA,EACR;AACD;",
6
6
  "names": ["reactors"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/types.ts"],
4
- "sourcesContent": ["import { ArraySet } from './ArraySet'\n\n/**\n * A unique symbol used to indicate that a signal's value should be reset or that\n * there is insufficient history to compute diffs between epochs.\n *\n * This value is returned by {@link Signal.getDiffSince} when the requested epoch\n * is too far in the past and the diff sequence cannot be reconstructed.\n *\n * @example\n * ```ts\n * import { atom, getGlobalEpoch, RESET_VALUE } from '@tldraw/state'\n *\n * const count = atom('count', 0, { historyLength: 3 })\n * const oldEpoch = getGlobalEpoch()\n *\n * // Make many changes that exceed history length\n * count.set(1)\n * count.set(2)\n * count.set(3)\n * count.set(4)\n *\n * const diffs = count.getDiffSince(oldEpoch)\n * if (diffs === RESET_VALUE) {\n * console.log('Too many changes, need to reset state')\n * }\n * ```\n *\n * @public\n */\nexport const RESET_VALUE: unique symbol = Symbol.for('com.tldraw.state/RESET_VALUE')\n\n/**\n * Type representing the the unique symbol RESET_VALUE symbol, used in type annotations\n * to indicate when a signal value should be reset or when diff computation\n * cannot proceed due to insufficient history.\n *\n * @public\n */\nexport type RESET_VALUE = typeof RESET_VALUE\n\n/**\n * A reactive value container that can change over time and track diffs between sequential values.\n *\n * Signals are the foundation of the \\@tldraw/state reactive system. They automatically manage\n * dependencies and trigger updates when their values change. Any computed signal or effect\n * that reads from this signal will be automatically recomputed when the signal's value changes.\n *\n * There are two types of signal:\n * - **Atomic signals** - Created using `atom()`. These are mutable containers that can be\n * directly updated using `set()` or `update()` methods.\n * - **Computed signals** - Created using `computed()`. These derive their values from other\n * signals and are automatically recomputed when dependencies change.\n *\n * @example\n * ```ts\n * import { atom, computed } from '@tldraw/state'\n *\n * // Create an atomic signal\n * const count = atom('count', 0)\n *\n * // Create a computed signal that derives from the atom\n * const doubled = computed('doubled', () => count.get() * 2)\n *\n * console.log(doubled.get()) // 0\n * count.set(5)\n * console.log(doubled.get()) // 10\n * ```\n *\n * @public\n */\nexport interface Signal<Value, Diff = unknown> {\n\t/**\n\t * A human-readable identifier for this signal, used primarily for debugging and performance profiling.\n\t *\n\t * The name is displayed in debug output from {@link whyAmIRunning} and other diagnostic tools.\n\t * It does not need to be globally unique within your application.\n\t */\n\tname: string\n\t/**\n\t * Gets the current value of the signal and establishes a dependency relationship.\n\t *\n\t * When called from within a computed signal or effect, this signal will be automatically\n\t * tracked as a dependency. If this signal's value changes, any dependent computations\n\t * or effects will be marked for re-execution.\n\t *\n\t * @returns The current value stored in the signal\n\t */\n\tget(): Value\n\n\t/**\n\t * The global epoch number when this signal's value last changed.\n\t *\n\t * Note that this represents when the value actually changed, not when it was last computed.\n\t * A computed signal may recalculate and produce the same value without changing its epoch.\n\t * This is used internally for dependency tracking and history management.\n\t */\n\tlastChangedEpoch: number\n\t/**\n\t * Gets the sequence of diffs that occurred between a specific epoch and the current state.\n\t *\n\t * This method enables incremental synchronization by providing a list of changes that\n\t * have occurred since a specific point in time. If the requested epoch is too far in\n\t * the past or the signal doesn't have enough history, it returns the unique symbol RESET_VALUE\n\t * to indicate that a full state reset is required.\n\t *\n\t * @param epoch - The epoch timestamp to get diffs since\n\t * @returns An array of diff objects representing changes since the epoch, or the unique symbol RESET_VALUE if insufficient history is available\n\t */\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[]\n\t/**\n\t * Gets the current value of the signal without establishing a dependency relationship.\n\t *\n\t * This method bypasses the automatic dependency tracking system, making it useful for\n\t * performance-critical code paths where the overhead of dependency capture would be\n\t * problematic. Use with caution as it breaks the reactive guarantees of the system.\n\t *\n\t * **Warning**: This method should only be used when you're certain that you don't need\n\t * the calling context to react to changes in this signal.\n\t *\n\t * @param ignoreErrors - Whether to suppress errors during value retrieval (optional)\n\t * @returns The current value without establishing dependencies\n\t */\n\t__unsafe__getWithoutCapture(ignoreErrors?: boolean): Value\n\t/** @internal */\n\tchildren: ArraySet<Child>\n}\n\n/**\n * Internal interface representing a child node in the signal dependency graph.\n *\n * This interface is used internally by the reactive system to manage dependencies\n * between signals, computed values, and effects. Each child tracks its parent\n * signals and maintains state needed for efficient dependency graph traversal\n * and change propagation.\n *\n * @internal\n */\nexport interface Child {\n\t/**\n\t * The epoch when this child was last traversed during dependency graph updates.\n\t * Used to prevent redundant traversals during change propagation.\n\t */\n\tlastTraversedEpoch: number\n\n\t/**\n\t * Set of parent signals that this child depends on.\n\t * Used for efficient lookup and cleanup operations.\n\t */\n\treadonly parentSet: ArraySet<Signal<any, any>>\n\n\t/**\n\t * Array of parent signals that this child depends on.\n\t * Maintained in parallel with parentSet for ordered access.\n\t */\n\treadonly parents: Signal<any, any>[]\n\n\t/**\n\t * Array of epochs corresponding to each parent signal.\n\t * Used to detect which parents have changed since last computation.\n\t */\n\treadonly parentEpochs: number[]\n\n\t/**\n\t * Human-readable name for this child, used in debugging output.\n\t */\n\treadonly name: string\n\n\t/**\n\t * Whether this child is currently subscribed to change notifications.\n\t * Used to optimize resource usage by unsubscribing inactive dependencies.\n\t */\n\tisActivelyListening: boolean\n\n\t/**\n\t * Debug information tracking ancestor epochs in the dependency graph.\n\t * Only populated in debug builds for diagnostic purposes.\n\t */\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null\n}\n\n/**\n * A function type that computes the difference between two values of a signal.\n *\n * This function is used to generate incremental diffs that can be applied to\n * reconstruct state changes over time. It's particularly useful for features\n * like undo/redo, synchronization, and change tracking.\n *\n * The function should analyze the previous and current values and return a\n * diff object that represents the change. If the diff cannot be computed\n * (e.g., the values are too different or incompatible), it should return\n * the unique symbol RESET_VALUE to indicate that a full state reset is required.\n *\n * @param previousValue - The previous value of the signal\n * @param currentValue - The current value of the signal\n * @param lastComputedEpoch - The epoch when the previous value was set\n * @param currentEpoch - The epoch when the current value was set\n * @returns A diff object representing the change, or the unique symbol RESET_VALUE if no diff can be computed\n *\n * @example\n * ```ts\n * import { atom, RESET_VALUE } from '@tldraw/state'\n *\n * // Simple numeric diff\n * const numberDiff: ComputeDiff<number, number> = (prev, curr) => curr - prev\n *\n * // Array diff with reset fallback\n * const arrayDiff: ComputeDiff<string[], { added: string[], removed: string[] }> = (prev, curr) => {\n * if (prev.length > 1000 || curr.length > 1000) {\n * return RESET_VALUE // Too complex, force reset\n * }\n * return {\n * added: curr.filter(item => !prev.includes(item)),\n * removed: prev.filter(item => !curr.includes(item))\n * }\n * }\n *\n * const count = atom('count', 0, { computeDiff: numberDiff })\n * ```\n *\n * @public\n */\nexport type ComputeDiff<Value, Diff> = (\n\tpreviousValue: Value,\n\tcurrentValue: Value,\n\tlastComputedEpoch: number,\n\tcurrentEpoch: number\n) => Diff | RESET_VALUE\n"],
4
+ "sourcesContent": ["import { ArraySet } from './ArraySet'\n\n/**\n * A unique symbol used to indicate that a signal's value should be reset or that\n * there is insufficient history to compute diffs between epochs.\n *\n * This value is returned by {@link Signal.getDiffSince} when the requested epoch\n * is too far in the past and the diff sequence cannot be reconstructed.\n *\n * @example\n * ```ts\n * import { atom, RESET_VALUE } from '@tldraw/state'\n *\n * const count = atom('count', 0, { historyLength: 3, computeDiff: (prev, next) => next - prev })\n * const oldEpoch = count.lastChangedEpoch\n *\n * // Make more changes than the history length can hold\n * count.set(1)\n * count.set(2)\n * count.set(3)\n * count.set(4)\n *\n * const diffs = count.getDiffSince(oldEpoch)\n * if (diffs === RESET_VALUE) {\n * console.log('Too many changes, need to reset state')\n * }\n * ```\n *\n * @public\n */\nexport const RESET_VALUE: unique symbol = Symbol.for('com.tldraw.state/RESET_VALUE')\n\n/**\n * Type representing the the unique symbol RESET_VALUE symbol, used in type annotations\n * to indicate when a signal value should be reset or when diff computation\n * cannot proceed due to insufficient history.\n *\n * @public\n */\nexport type RESET_VALUE = typeof RESET_VALUE\n\n/**\n * A reactive value container that can change over time and track diffs between sequential values.\n *\n * Signals are the foundation of the \\@tldraw/state reactive system. They automatically manage\n * dependencies and trigger updates when their values change. Any computed signal or effect\n * that reads from this signal will be automatically recomputed when the signal's value changes.\n *\n * There are two types of signal:\n * - **Atomic signals** - Created using `atom()`. These are mutable containers that can be\n * directly updated using `set()` or `update()` methods.\n * - **Computed signals** - Created using `computed()`. These derive their values from other\n * signals and are automatically recomputed when dependencies change.\n *\n * @example\n * ```ts\n * import { atom, computed } from '@tldraw/state'\n *\n * // Create an atomic signal\n * const count = atom('count', 0)\n *\n * // Create a computed signal that derives from the atom\n * const doubled = computed('doubled', () => count.get() * 2)\n *\n * console.log(doubled.get()) // 0\n * count.set(5)\n * console.log(doubled.get()) // 10\n * ```\n *\n * @public\n */\nexport interface Signal<Value, Diff = unknown> {\n\t/**\n\t * A human-readable identifier for this signal, used primarily for debugging and performance profiling.\n\t *\n\t * The name is displayed in debug output from {@link whyAmIRunning} and other diagnostic tools.\n\t * It does not need to be globally unique within your application.\n\t */\n\tname: string\n\t/**\n\t * Gets the current value of the signal and establishes a dependency relationship.\n\t *\n\t * When called from within a computed signal or effect, this signal will be automatically\n\t * tracked as a dependency. If this signal's value changes, any dependent computations\n\t * or effects will be marked for re-execution.\n\t *\n\t * @returns The current value stored in the signal\n\t */\n\tget(): Value\n\n\t/**\n\t * The global epoch number when this signal's value last changed.\n\t *\n\t * Note that this represents when the value actually changed, not when it was last computed.\n\t * A computed signal may recalculate and produce the same value without changing its epoch.\n\t * This is used internally for dependency tracking and history management.\n\t */\n\tlastChangedEpoch: number\n\t/**\n\t * Gets the sequence of diffs that occurred between a specific epoch and the current state.\n\t *\n\t * This method enables incremental synchronization by providing a list of changes that\n\t * have occurred since a specific point in time. If the requested epoch is too far in\n\t * the past or the signal doesn't have enough history, it returns the unique symbol RESET_VALUE\n\t * to indicate that a full state reset is required.\n\t *\n\t * @param epoch - The epoch timestamp to get diffs since\n\t * @returns An array of diff objects representing changes since the epoch, or the unique symbol RESET_VALUE if insufficient history is available\n\t */\n\tgetDiffSince(epoch: number): RESET_VALUE | Diff[]\n\t/**\n\t * Gets the current value of the signal without establishing a dependency relationship.\n\t *\n\t * This method bypasses the automatic dependency tracking system, making it useful for\n\t * performance-critical code paths where the overhead of dependency capture would be\n\t * problematic. Use with caution as it breaks the reactive guarantees of the system.\n\t *\n\t * **Warning**: This method should only be used when you're certain that you don't need\n\t * the calling context to react to changes in this signal.\n\t *\n\t * @param ignoreErrors - Whether to suppress errors during value retrieval (optional)\n\t * @returns The current value without establishing dependencies\n\t */\n\t__unsafe__getWithoutCapture(ignoreErrors?: boolean): Value\n\t/** @internal */\n\tchildren: ArraySet<Child>\n}\n\n/**\n * Internal interface representing a child node in the signal dependency graph.\n *\n * This interface is used internally by the reactive system to manage dependencies\n * between signals, computed values, and effects. Each child tracks its parent\n * signals and maintains state needed for efficient dependency graph traversal\n * and change propagation.\n *\n * @internal\n */\nexport interface Child {\n\t/**\n\t * The epoch when this child was last traversed during dependency graph updates.\n\t * Used to prevent redundant traversals during change propagation.\n\t */\n\tlastTraversedEpoch: number\n\n\t/**\n\t * Set of parent signals that this child depends on.\n\t * Used for efficient lookup and cleanup operations.\n\t */\n\treadonly parentSet: ArraySet<Signal<any, any>>\n\n\t/**\n\t * Array of parent signals that this child depends on.\n\t * Maintained in parallel with parentSet for ordered access.\n\t */\n\treadonly parents: Signal<any, any>[]\n\n\t/**\n\t * Array of epochs corresponding to each parent signal.\n\t * Used to detect which parents have changed since last computation.\n\t */\n\treadonly parentEpochs: number[]\n\n\t/**\n\t * Human-readable name for this child, used in debugging output.\n\t */\n\treadonly name: string\n\n\t/**\n\t * Whether this child is currently subscribed to change notifications.\n\t * Used to optimize resource usage by unsubscribing inactive dependencies.\n\t */\n\tisActivelyListening: boolean\n\n\t/**\n\t * Debug information tracking ancestor epochs in the dependency graph.\n\t * Only populated in debug builds for diagnostic purposes.\n\t */\n\t__debug_ancestor_epochs__: Map<Signal<any, any>, number> | null\n}\n\n/**\n * A function type that computes the difference between two values of a signal.\n *\n * This function is used to generate incremental diffs that can be applied to\n * reconstruct state changes over time, so that downstream computeds and effects can\n * update incrementally instead of recomputing from scratch.\n *\n * The function should analyze the previous and current values and return a\n * diff object that represents the change. If the diff cannot be computed\n * (e.g., the values are too different or incompatible), it should return\n * the unique symbol RESET_VALUE to indicate that a full state reset is required.\n *\n * @param previousValue - The previous value of the signal\n * @param currentValue - The current value of the signal\n * @param lastComputedEpoch - For an atom, the epoch when the previous value was set. For a computed, the epoch at which it was last checked (the same value its compute function receives), so that `other.getDiffSince(lastComputedEpoch)` yields exactly the changes not yet accounted for.\n * @param currentEpoch - The epoch when the current value was set\n * @returns A diff object representing the change, or the unique symbol RESET_VALUE if no diff can be computed\n *\n * @example\n * ```ts\n * import { atom, RESET_VALUE } from '@tldraw/state'\n *\n * // Simple numeric diff\n * const numberDiff: ComputeDiff<number, number> = (prev, curr) => curr - prev\n *\n * // Array diff with reset fallback\n * const arrayDiff: ComputeDiff<string[], { added: string[], removed: string[] }> = (prev, curr) => {\n * if (prev.length > 1000 || curr.length > 1000) {\n * return RESET_VALUE // Too complex, force reset\n * }\n * return {\n * added: curr.filter(item => !prev.includes(item)),\n * removed: prev.filter(item => !curr.includes(item))\n * }\n * }\n *\n * const count = atom('count', 0, { computeDiff: numberDiff })\n * ```\n *\n * @public\n */\nexport type ComputeDiff<Value, Diff> = (\n\tpreviousValue: Value,\n\tcurrentValue: Value,\n\tlastComputedEpoch: number,\n\tcurrentEpoch: number\n) => Diff | RESET_VALUE\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BO,MAAM,cAA6B,uBAAO,IAAI,8BAA8B;",
6
6
  "names": []
7
7
  }
@@ -21,11 +21,9 @@ __export(warnings_exports, {
21
21
  logComputedGetterWarning: () => logComputedGetterWarning
22
22
  });
23
23
  module.exports = __toCommonJS(warnings_exports);
24
- let didWarnComputedGetter = false;
24
+ var import_utils = require("@tldraw/utils");
25
25
  function logComputedGetterWarning() {
26
- if (didWarnComputedGetter) return;
27
- didWarnComputedGetter = true;
28
- console.warn(
26
+ (0, import_utils.warnOnce)(
29
27
  `Using \`@computed\` as a decorator for getters is deprecated and will be removed in the near future. Please refactor to use \`@computed\` as a decorator for methods.
30
28
 
31
29
  // Before
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/warnings.ts"],
4
- "sourcesContent": ["/**\n * Flag to track whether the computed getter deprecation warning has already been shown.\n * Prevents the same warning from being logged multiple times during application runtime.\n *\n * @internal\n */\nlet didWarnComputedGetter = false\n\n/**\n * Logs a deprecation warning for the deprecated `@computed` getter decorator syntax.\n * This function is called internally when the library detects usage of `@computed`\n * on a getter method instead of the recommended method syntax.\n *\n * The warning is only shown once per application session to avoid spam in the console.\n * It provides clear guidance on how to migrate from the deprecated getter syntax\n * to the current method-based approach.\n *\n * @example\n * ```ts\n * // Deprecated pattern that triggers this warning:\n * class MyClass {\n * @computed\n * get value() {\n * return this.someAtom.get()\n * }\n * }\n *\n * // Recommended pattern:\n * class MyClass {\n * @computed\n * getValue() {\n * return this.someAtom.get()\n * }\n * }\n * ```\n *\n * @internal\n */\nexport function logComputedGetterWarning() {\n\tif (didWarnComputedGetter) return\n\tdidWarnComputedGetter = true\n\tconsole.warn(\n\t\t`Using \\`@computed\\` as a decorator for getters is deprecated and will be removed in the near future. Please refactor to use \\`@computed\\` as a decorator for methods.\n\n// Before\n@computed\nget foo() {\n\treturn 'foo'\n}\n\n// After\n@computed\ngetFoo() {\n\treturn 'foo'\n}\n`\n\t)\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,IAAI,wBAAwB;AAgCrB,SAAS,2BAA2B;AAC1C,MAAI,sBAAuB;AAC3B,0BAAwB;AACxB,UAAQ;AAAA,IACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcD;AACD;",
4
+ "sourcesContent": ["import { warnOnce } from '@tldraw/utils'\n\n/**\n * Logs a deprecation warning for the deprecated `@computed` getter decorator syntax.\n * This function is called internally when the library detects usage of `@computed`\n * on a getter method instead of the recommended method syntax.\n *\n * The warning is only shown once per application session to avoid spam in the console.\n * It provides clear guidance on how to migrate from the deprecated getter syntax\n * to the current method-based approach.\n *\n * @example\n * ```ts\n * // Deprecated pattern that triggers this warning:\n * class MyClass {\n * @computed\n * get value() {\n * return this.someAtom.get()\n * }\n * }\n *\n * // Recommended pattern:\n * class MyClass {\n * @computed\n * getValue() {\n * return this.someAtom.get()\n * }\n * }\n * ```\n *\n * @internal\n */\nexport function logComputedGetterWarning() {\n\twarnOnce(\n\t\t`Using \\`@computed\\` as a decorator for getters is deprecated and will be removed in the near future. Please refactor to use \\`@computed\\` as a decorator for methods.\n\n// Before\n@computed\nget foo() {\n\treturn 'foo'\n}\n\n// After\n@computed\ngetFoo() {\n\treturn 'foo'\n}\n`\n\t)\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAyB;AAgClB,SAAS,2BAA2B;AAC1C;AAAA,IACC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcD;AACD;",
6
6
  "names": []
7
7
  }
@@ -70,7 +70,7 @@ export declare interface AtomOptions<Value, Diff> {
70
70
  /**
71
71
  * The maximum number of diffs to keep in the history buffer.
72
72
  *
73
- * If you don't need to compute diffs, or if you will supply diffs manually via {@link Atom.set}, you can leave this as `undefined` and no history buffer will be created.
73
+ * 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.
74
74
  *
75
75
  * 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).
76
76
  *
@@ -142,7 +142,7 @@ export declare interface Computed<Value, Diff = unknown> extends Signal<Value, D
142
142
  * ```ts
143
143
  * class Counter {
144
144
  * max = 100
145
- * count = atom<number>(0)
145
+ * count = atom('count', 0)
146
146
  *
147
147
  * @computed getRemaining() {
148
148
  * return this.max - this.count.get()
@@ -156,7 +156,7 @@ export declare interface Computed<Value, Diff = unknown> extends Signal<Value, D
156
156
  * ```ts
157
157
  * class Counter {
158
158
  * max = 100
159
- * count = atom<number>(0)
159
+ * count = atom('count', 0)
160
160
  *
161
161
  * @computed({isEqual: (a, b) => a === b})
162
162
  * getRemaining() {
@@ -243,8 +243,8 @@ export declare function computed<Value, Diff = unknown>(options?: ComputedOption
243
243
  * A function type that computes the difference between two values of a signal.
244
244
  *
245
245
  * This function is used to generate incremental diffs that can be applied to
246
- * reconstruct state changes over time. It's particularly useful for features
247
- * like undo/redo, synchronization, and change tracking.
246
+ * reconstruct state changes over time, so that downstream computeds and effects can
247
+ * update incrementally instead of recomputing from scratch.
248
248
  *
249
249
  * The function should analyze the previous and current values and return a
250
250
  * diff object that represents the change. If the diff cannot be computed
@@ -253,7 +253,7 @@ export declare function computed<Value, Diff = unknown>(options?: ComputedOption
253
253
  *
254
254
  * @param previousValue - The previous value of the signal
255
255
  * @param currentValue - The current value of the signal
256
- * @param lastComputedEpoch - The epoch when the previous value was set
256
+ * @param lastComputedEpoch - For an atom, the epoch when the previous value was set. For a computed, the epoch at which it was last checked (the same value its compute function receives), so that `other.getDiffSince(lastComputedEpoch)` yields exactly the changes not yet accounted for.
257
257
  * @param currentEpoch - The epoch when the current value was set
258
258
  * @returns A diff object representing the change, or the unique symbol RESET_VALUE if no diff can be computed
259
259
  *
@@ -300,7 +300,7 @@ export declare interface ComputedOptions<Value, Diff> {
300
300
  /**
301
301
  * The maximum number of diffs to keep in the history buffer.
302
302
  *
303
- * If you don't need to compute diffs, or if you will supply diffs manually via {@link Atom.set}, you can leave this as `undefined` and no history buffer will be created.
303
+ * If you don't need diffs, leave this as `undefined` and no history buffer will be created. Diffs supplied via {@link withDiff} or {@link ComputedOptions.computeDiff} are only recorded when this is set.
304
304
  *
305
305
  * 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).
306
306
  *
@@ -341,7 +341,7 @@ export declare interface ComputedOptions<Value, Diff> {
341
341
  *
342
342
  * @public
343
343
  */
344
- export declare const EffectScheduler: new <Result>(name: string, runEffect: (lastReactedEpoch: number) => Result, options?: EffectSchedulerOptions | undefined) => EffectScheduler<Result>;
344
+ export declare const EffectScheduler: new <Result>(name: string, runEffect: (lastReactedEpoch: number) => Result, options?: EffectSchedulerOptions) => EffectScheduler<Result>;
345
345
 
346
346
  /** @public */
347
347
  export declare interface EffectScheduler<Result> {
@@ -410,12 +410,9 @@ export declare interface EffectSchedulerOptions {
410
410
  * }
411
411
  * }
412
412
  * const stop = react('set page title', () => {
413
- * document.title = doc.title,
414
- * }, scheduleEffect)
413
+ * document.title = doc.title
414
+ * }, { scheduleEffect })
415
415
  * ```
416
- *
417
- * @param execute - A function that will execute the effect.
418
- * @returns void
419
416
  */
420
417
  scheduleEffect?: (execute: () => void) => void;
421
418
  }
@@ -433,7 +430,7 @@ export declare const EMPTY_ARRAY: [];
433
430
  * ```ts
434
431
  * class Counter {
435
432
  * max = 100
436
- * count = atom(0)
433
+ * count = atom('count', 0)
437
434
  *
438
435
  * @computed getRemaining() {
439
436
  * return this.max - this.count.get()
@@ -451,7 +448,7 @@ export declare const EMPTY_ARRAY: [];
451
448
  * @param propertyName - The property name
452
449
  * @public
453
450
  */
454
- export declare function getComputedInstance<Obj extends object, Prop extends keyof Obj>(obj: Obj, propertyName: Prop): Computed<Obj[Prop]>;
451
+ export declare function getComputedInstance<Obj extends object, Prop extends keyof Obj>(obj: Obj, propertyName: Prop): Computed<Obj[Prop] extends () => infer Value ? Value : Obj[Prop]>;
455
452
 
456
453
  /**
457
454
  * Returns true if the given value is an {@link Atom}.
@@ -631,12 +628,12 @@ export declare function reactor<Result>(name: string, fn: (lastReactedEpoch: num
631
628
  *
632
629
  * @example
633
630
  * ```ts
634
- * import { atom, getGlobalEpoch, RESET_VALUE } from '@tldraw/state'
631
+ * import { atom, RESET_VALUE } from '@tldraw/state'
635
632
  *
636
- * const count = atom('count', 0, { historyLength: 3 })
637
- * const oldEpoch = getGlobalEpoch()
633
+ * const count = atom('count', 0, { historyLength: 3, computeDiff: (prev, next) => next - prev })
634
+ * const oldEpoch = count.lastChangedEpoch
638
635
  *
639
- * // Make many changes that exceed history length
636
+ * // Make more changes than the history length can hold
640
637
  * count.set(1)
641
638
  * count.set(2)
642
639
  * count.set(3)
@@ -796,7 +793,7 @@ export declare function transact<T>(fn: () => T): T;
796
793
  * // Logs "Hello, Jane Smith!"
797
794
  * ```
798
795
  *
799
- * 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.
796
+ * 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.
800
797
  *
801
798
  * @example
802
799
  * ```ts
@@ -814,8 +811,9 @@ export declare function transact<T>(fn: () => T): T;
814
811
  * throw new Error('oops')
815
812
  * })
816
813
  *
817
- * // Does not log
818
814
  * // firstName.get() === 'John'
815
+ * // Logs "Hello, John Doe!" again: effects whose parents were changed and restored still run,
816
+ * // and observe the restored values.
819
817
  * ```
820
818
  *
821
819
  * A `rollback` callback is passed into the function.
@@ -838,9 +836,9 @@ export declare function transact<T>(fn: () => T): T;
838
836
  * rollback()
839
837
  * })
840
838
  *
841
- * // Does not log
842
839
  * // firstName.get() === 'John'
843
840
  * // lastName.get() === 'Doe'
841
+ * // Logs "Hello, John Doe!" again, as above.
844
842
  * ```
845
843
  *
846
844
  * @param fn - The function to run in a transaction, called with a function to roll back the change.
@@ -886,10 +884,10 @@ export declare type UNINITIALIZED = typeof UNINITIALIZED;
886
884
  * @example
887
885
  * ```ts
888
886
  * const name = atom('name', 'Sam')
889
- * const time = atom('time', () => new Date().getTime())
887
+ * const time = atom('time', Date.now())
890
888
  *
891
889
  * setInterval(() => {
892
- * time.set(new Date().getTime())
890
+ * time.set(Date.now())
893
891
  * })
894
892
  *
895
893
  * react('log name changes', () => {
@@ -904,7 +902,8 @@ export declare function unsafe__withoutCapture<T>(fn: () => T): T;
904
902
 
905
903
  /**
906
904
  * A debugging tool that tells you why a computed signal or effect is running.
907
- * Call in the body of a computed signal or effect function.
905
+ * Call in the body of a computed signal or effect function. Nothing is logged for the run that
906
+ * calls it; from the next run on, each run logs the ancestors that changed.
908
907
  *
909
908
  * @example
910
909
  * ```ts
@@ -916,8 +915,8 @@ export declare function unsafe__withoutCapture<T>(fn: () => T): T;
916
915
  *
917
916
  * name.set('Alice')
918
917
  *
919
- * // 'greeting' is running because:
920
- * // 'name' changed => 'Alice'
918
+ * // Effect(greeting) is executing because:
919
+ * // ↳ Atom(name) changed
921
920
  * ```
922
921
  *
923
922
  * @public
@@ -25,7 +25,7 @@ if (actualApiVersion !== currentApiVersion) {
25
25
  }
26
26
  registerTldrawLibraryVersion(
27
27
  "@tldraw/state",
28
- "5.3.2",
28
+ "5.4.0-canary.02cd0bd3b597",
29
29
  "esm"
30
30
  );
31
31
  export {