@tldraw/store 4.5.2 → 4.6.0-canary.00a8c03b5687

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
@@ -56,7 +56,7 @@ var import_StoreSchema = require("./lib/StoreSchema");
56
56
  var import_StoreSideEffects = require("./lib/StoreSideEffects");
57
57
  (0, import_utils.registerTldrawLibraryVersion)(
58
58
  "@tldraw/store",
59
- "4.5.2",
59
+ "4.6.0-canary.00a8c03b5687",
60
60
  "cjs"
61
61
  );
62
62
  //# sourceMappingURL=index.js.map
@@ -345,7 +345,7 @@ class AtomMap {
345
345
  * console.log(map.size) // 1
346
346
  * ```
347
347
  */
348
- // eslint-disable-next-line no-restricted-syntax
348
+ // eslint-disable-next-line tldraw/no-setter-getter
349
349
  get size() {
350
350
  return this.atoms.get().size;
351
351
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/AtomMap.ts"],
4
- "sourcesContent": ["import { atom, Atom, transact, UNINITIALIZED } from '@tldraw/state'\nimport { assert } from '@tldraw/utils'\nimport { emptyMap, ImmutableMap } from './ImmutableMap'\n\n/**\n * A drop-in replacement for Map that stores values in atoms and can be used in reactive contexts.\n * @public\n */\nexport class AtomMap<K, V> implements Map<K, V> {\n\tprivate atoms: Atom<ImmutableMap<K, Atom<V | UNINITIALIZED>>>\n\n\t/**\n\t * Creates a new AtomMap instance.\n\t *\n\t * name - A unique name for this map, used for atom identification\n\t * entries - Optional initial entries to populate the map with\n\t * @example\n\t * ```ts\n\t * // Create an empty map\n\t * const map = new AtomMap('userMap')\n\t *\n\t * // Create a map with initial data\n\t * const initialData: [string, number][] = [['a', 1], ['b', 2]]\n\t * const mapWithData = new AtomMap('numbersMap', initialData)\n\t * ```\n\t */\n\tconstructor(\n\t\tprivate readonly name: string,\n\t\tentries?: Iterable<readonly [K, V]>\n\t) {\n\t\tlet atoms = emptyMap<K, Atom<V>>()\n\t\tif (entries) {\n\t\t\tatoms = atoms.withMutations((atoms) => {\n\t\t\t\tfor (const [k, v] of entries) {\n\t\t\t\t\tatoms.set(k, atom(`${name}:${String(k)}`, v))\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t\tthis.atoms = atom(`${name}:atoms`, atoms)\n\t}\n\n\t/**\n\t * Retrieves the underlying atom for a given key.\n\t *\n\t * @param key - The key to retrieve the atom for\n\t * @returns The atom containing the value, or undefined if the key doesn't exist\n\t * @internal\n\t */\n\tgetAtom(key: K): Atom<V | UNINITIALIZED> | undefined {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\t// if the value is missing, we want to track whether it's in the present keys set\n\t\t\tthis.atoms.get()\n\t\t\treturn undefined\n\t\t}\n\t\treturn valueAtom\n\t}\n\n\t/**\n\t * Gets the value associated with a key. Returns undefined if the key doesn't exist.\n\t * This method is reactive and will cause reactive contexts to update when the value changes.\n\t *\n\t * @param key - The key to retrieve the value for\n\t * @returns The value associated with the key, or undefined if not found\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice')\n\t * console.log(map.get('name')) // 'Alice'\n\t * console.log(map.get('missing')) // undefined\n\t * ```\n\t */\n\tget(key: K): V | undefined {\n\t\tconst value = this.getAtom(key)?.get()\n\t\tassert(value !== UNINITIALIZED)\n\t\treturn value\n\t}\n\n\t/**\n\t * Gets the value associated with a key without creating reactive dependencies.\n\t * This method will not cause reactive contexts to update when the value changes.\n\t *\n\t * @param key - The key to retrieve the value for\n\t * @returns The value associated with the key, or undefined if not found\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('count', 42)\n\t * const value = map.__unsafe__getWithoutCapture('count') // No reactive subscription\n\t * ```\n\t */\n\t__unsafe__getWithoutCapture(key: K): V | undefined {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) return undefined\n\t\tconst value = valueAtom.__unsafe__getWithoutCapture()\n\t\tassert(value !== UNINITIALIZED)\n\t\treturn value\n\t}\n\n\t/**\n\t * Checks whether a key exists in the map.\n\t * This method is reactive and will cause reactive contexts to update when keys are added or removed.\n\t *\n\t * @param key - The key to check for\n\t * @returns True if the key exists in the map, false otherwise\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(map.has('name')) // false\n\t * map.set('name', 'Alice')\n\t * console.log(map.has('name')) // true\n\t * ```\n\t */\n\thas(key: K): boolean {\n\t\tconst valueAtom = this.getAtom(key)\n\t\tif (!valueAtom) {\n\t\t\treturn false\n\t\t}\n\t\treturn valueAtom.get() !== UNINITIALIZED\n\t}\n\n\t/**\n\t * Checks whether a key exists in the map without creating reactive dependencies.\n\t * This method will not cause reactive contexts to update when keys are added or removed.\n\t *\n\t * @param key - The key to check for\n\t * @returns True if the key exists in the map, false otherwise\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('active', true)\n\t * const exists = map.__unsafe__hasWithoutCapture('active') // No reactive subscription\n\t * ```\n\t */\n\t__unsafe__hasWithoutCapture(key: K): boolean {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) return false\n\t\tassert(valueAtom.__unsafe__getWithoutCapture() !== UNINITIALIZED)\n\t\treturn true\n\t}\n\n\t/**\n\t * Sets a value for the given key. If the key already exists, its value is updated.\n\t * If the key doesn't exist, a new entry is created.\n\t *\n\t * @param key - The key to set the value for\n\t * @param value - The value to associate with the key\n\t * @returns This AtomMap instance for method chaining\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * ```\n\t */\n\tset(key: K, value: V) {\n\t\tconst existingAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (existingAtom) {\n\t\t\texistingAtom.set(value)\n\t\t} else {\n\t\t\tthis.atoms.update((atoms) => {\n\t\t\t\treturn atoms.set(key, atom(`${this.name}:${String(key)}`, value))\n\t\t\t})\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * Updates an existing value using an updater function.\n\t *\n\t * @param key - The key of the value to update\n\t * @param updater - A function that receives the current value and returns the new value\n\t * @throws Error if the key doesn't exist in the map\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('count', 5)\n\t * map.update('count', count => count + 1) // count is now 6\n\t * ```\n\t */\n\tupdate(key: K, updater: (value: V) => V) {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\tthrow new Error(`AtomMap: key ${key} not found`)\n\t\t}\n\t\tconst value = valueAtom.__unsafe__getWithoutCapture()\n\t\tassert(value !== UNINITIALIZED)\n\t\tvalueAtom.set(updater(value))\n\t}\n\n\t/**\n\t * Removes a key-value pair from the map.\n\t *\n\t * @param key - The key to remove\n\t * @returns True if the key existed and was removed, false if it didn't exist\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('temp', 'value')\n\t * console.log(map.delete('temp')) // true\n\t * console.log(map.delete('missing')) // false\n\t * ```\n\t */\n\tdelete(key: K) {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\treturn false\n\t\t}\n\n\t\ttransact(() => {\n\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\tthis.atoms.update((atoms) => {\n\t\t\t\treturn atoms.delete(key)\n\t\t\t})\n\t\t})\n\t\treturn true\n\t}\n\n\t/**\n\t * Removes multiple key-value pairs from the map in a single transaction.\n\t *\n\t * @param keys - An iterable of keys to remove\n\t * @returns An array of [key, value] pairs that were actually deleted\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2).set('c', 3)\n\t * const deleted = map.deleteMany(['a', 'c', 'missing'])\n\t * console.log(deleted) // [['a', 1], ['c', 3]]\n\t * ```\n\t */\n\tdeleteMany(keys: Iterable<K>): [K, V][] {\n\t\treturn transact(() => {\n\t\t\tconst deleted: [K, V][] = []\n\t\t\tconst newAtoms = this.atoms.get().withMutations((atoms) => {\n\t\t\t\tfor (const key of keys) {\n\t\t\t\t\tconst valueAtom = atoms.get(key)\n\t\t\t\t\tif (!valueAtom) continue\n\t\t\t\t\tconst oldValue = valueAtom.get()\n\t\t\t\t\tassert(oldValue !== UNINITIALIZED)\n\n\t\t\t\t\tdeleted.push([key, oldValue])\n\n\t\t\t\t\tatoms.delete(key)\n\t\t\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif (deleted.length) {\n\t\t\t\tthis.atoms.set(newAtoms)\n\t\t\t}\n\n\t\t\treturn deleted\n\t\t})\n\t}\n\n\t/**\n\t * Removes all key-value pairs from the map.\n\t *\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * map.clear()\n\t * console.log(map.size) // 0\n\t * ```\n\t */\n\tclear() {\n\t\treturn transact(() => {\n\t\t\tfor (const valueAtom of this.atoms.__unsafe__getWithoutCapture().values()) {\n\t\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\t}\n\t\t\tthis.atoms.set(emptyMap())\n\t\t})\n\t}\n\n\t/**\n\t * Returns an iterator that yields [key, value] pairs for each entry in the map.\n\t * This method is reactive and will cause reactive contexts to update when entries change.\n\t *\n\t * @returns A generator that yields [key, value] tuples\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * for (const [key, value] of map.entries()) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t * ```\n\t */\n\t*entries(): Generator<[K, V], undefined, unknown> {\n\t\tfor (const [key, valueAtom] of this.atoms.get()) {\n\t\t\tconst value = valueAtom.get()\n\t\t\tassert(value !== UNINITIALIZED)\n\t\t\tyield [key, value]\n\t\t}\n\t}\n\n\t/**\n\t * Returns an iterator that yields all keys in the map.\n\t * This method is reactive and will cause reactive contexts to update when keys change.\n\t *\n\t * @returns A generator that yields keys\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * for (const key of map.keys()) {\n\t * console.log(key) // 'name', 'age'\n\t * }\n\t * ```\n\t */\n\t*keys(): Generator<K, undefined, unknown> {\n\t\tfor (const key of this.atoms.get().keys()) {\n\t\t\tyield key\n\t\t}\n\t}\n\n\t/**\n\t * Returns an iterator that yields all values in the map.\n\t * This method is reactive and will cause reactive contexts to update when values change.\n\t *\n\t * @returns A generator that yields values\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * for (const value of map.values()) {\n\t * console.log(value) // 'Alice', 30\n\t * }\n\t * ```\n\t */\n\t*values(): Generator<V, undefined, unknown> {\n\t\tfor (const valueAtom of this.atoms.get().values()) {\n\t\t\tconst value = valueAtom.get()\n\t\t\tassert(value !== UNINITIALIZED)\n\t\t\tyield value\n\t\t}\n\t}\n\n\t/**\n\t * The number of key-value pairs in the map.\n\t * This property is reactive and will cause reactive contexts to update when the size changes.\n\t *\n\t * @returns The number of entries in the map\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(map.size) // 0\n\t * map.set('a', 1)\n\t * console.log(map.size) // 1\n\t * ```\n\t */\n\t// eslint-disable-next-line no-restricted-syntax\n\tget size() {\n\t\treturn this.atoms.get().size\n\t}\n\n\t/**\n\t * Executes a provided function once for each key-value pair in the map.\n\t * This method is reactive and will cause reactive contexts to update when entries change.\n\t *\n\t * @param callbackfn - Function to execute for each entry\n\t * - value - The value of the current entry\n\t * - key - The key of the current entry\n\t * - map - The AtomMap being traversed\n\t * @param thisArg - Value to use as `this` when executing the callback\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * map.forEach((value, key) => {\n\t * console.log(`${key} = ${value}`)\n\t * })\n\t * ```\n\t */\n\tforEach(callbackfn: (value: V, key: K, map: AtomMap<K, V>) => void, thisArg?: any): void {\n\t\tfor (const [key, value] of this.entries()) {\n\t\t\tcallbackfn.call(thisArg, value, key, this)\n\t\t}\n\t}\n\n\t/**\n\t * Returns the default iterator for the map, which is the same as entries().\n\t * This allows the map to be used in for...of loops and other iterable contexts.\n\t *\n\t * @returns The same iterator as entries()\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t *\n\t * // These are equivalent:\n\t * for (const [key, value] of map) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t *\n\t * for (const [key, value] of map.entries()) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t * ```\n\t */\n\t[Symbol.iterator]() {\n\t\treturn this.entries()\n\t}\n\n\t/**\n\t * The string tag used by Object.prototype.toString for this class.\n\t *\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(Object.prototype.toString.call(map)) // '[object AtomMap]'\n\t * ```\n\t */\n\t[Symbol.toStringTag] = 'AtomMap'\n}\n"],
4
+ "sourcesContent": ["import { atom, Atom, transact, UNINITIALIZED } from '@tldraw/state'\nimport { assert } from '@tldraw/utils'\nimport { emptyMap, ImmutableMap } from './ImmutableMap'\n\n/**\n * A drop-in replacement for Map that stores values in atoms and can be used in reactive contexts.\n * @public\n */\nexport class AtomMap<K, V> implements Map<K, V> {\n\tprivate atoms: Atom<ImmutableMap<K, Atom<V | UNINITIALIZED>>>\n\n\t/**\n\t * Creates a new AtomMap instance.\n\t *\n\t * name - A unique name for this map, used for atom identification\n\t * entries - Optional initial entries to populate the map with\n\t * @example\n\t * ```ts\n\t * // Create an empty map\n\t * const map = new AtomMap('userMap')\n\t *\n\t * // Create a map with initial data\n\t * const initialData: [string, number][] = [['a', 1], ['b', 2]]\n\t * const mapWithData = new AtomMap('numbersMap', initialData)\n\t * ```\n\t */\n\tconstructor(\n\t\tprivate readonly name: string,\n\t\tentries?: Iterable<readonly [K, V]>\n\t) {\n\t\tlet atoms = emptyMap<K, Atom<V>>()\n\t\tif (entries) {\n\t\t\tatoms = atoms.withMutations((atoms) => {\n\t\t\t\tfor (const [k, v] of entries) {\n\t\t\t\t\tatoms.set(k, atom(`${name}:${String(k)}`, v))\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t\tthis.atoms = atom(`${name}:atoms`, atoms)\n\t}\n\n\t/**\n\t * Retrieves the underlying atom for a given key.\n\t *\n\t * @param key - The key to retrieve the atom for\n\t * @returns The atom containing the value, or undefined if the key doesn't exist\n\t * @internal\n\t */\n\tgetAtom(key: K): Atom<V | UNINITIALIZED> | undefined {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\t// if the value is missing, we want to track whether it's in the present keys set\n\t\t\tthis.atoms.get()\n\t\t\treturn undefined\n\t\t}\n\t\treturn valueAtom\n\t}\n\n\t/**\n\t * Gets the value associated with a key. Returns undefined if the key doesn't exist.\n\t * This method is reactive and will cause reactive contexts to update when the value changes.\n\t *\n\t * @param key - The key to retrieve the value for\n\t * @returns The value associated with the key, or undefined if not found\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice')\n\t * console.log(map.get('name')) // 'Alice'\n\t * console.log(map.get('missing')) // undefined\n\t * ```\n\t */\n\tget(key: K): V | undefined {\n\t\tconst value = this.getAtom(key)?.get()\n\t\tassert(value !== UNINITIALIZED)\n\t\treturn value\n\t}\n\n\t/**\n\t * Gets the value associated with a key without creating reactive dependencies.\n\t * This method will not cause reactive contexts to update when the value changes.\n\t *\n\t * @param key - The key to retrieve the value for\n\t * @returns The value associated with the key, or undefined if not found\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('count', 42)\n\t * const value = map.__unsafe__getWithoutCapture('count') // No reactive subscription\n\t * ```\n\t */\n\t__unsafe__getWithoutCapture(key: K): V | undefined {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) return undefined\n\t\tconst value = valueAtom.__unsafe__getWithoutCapture()\n\t\tassert(value !== UNINITIALIZED)\n\t\treturn value\n\t}\n\n\t/**\n\t * Checks whether a key exists in the map.\n\t * This method is reactive and will cause reactive contexts to update when keys are added or removed.\n\t *\n\t * @param key - The key to check for\n\t * @returns True if the key exists in the map, false otherwise\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(map.has('name')) // false\n\t * map.set('name', 'Alice')\n\t * console.log(map.has('name')) // true\n\t * ```\n\t */\n\thas(key: K): boolean {\n\t\tconst valueAtom = this.getAtom(key)\n\t\tif (!valueAtom) {\n\t\t\treturn false\n\t\t}\n\t\treturn valueAtom.get() !== UNINITIALIZED\n\t}\n\n\t/**\n\t * Checks whether a key exists in the map without creating reactive dependencies.\n\t * This method will not cause reactive contexts to update when keys are added or removed.\n\t *\n\t * @param key - The key to check for\n\t * @returns True if the key exists in the map, false otherwise\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('active', true)\n\t * const exists = map.__unsafe__hasWithoutCapture('active') // No reactive subscription\n\t * ```\n\t */\n\t__unsafe__hasWithoutCapture(key: K): boolean {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) return false\n\t\tassert(valueAtom.__unsafe__getWithoutCapture() !== UNINITIALIZED)\n\t\treturn true\n\t}\n\n\t/**\n\t * Sets a value for the given key. If the key already exists, its value is updated.\n\t * If the key doesn't exist, a new entry is created.\n\t *\n\t * @param key - The key to set the value for\n\t * @param value - The value to associate with the key\n\t * @returns This AtomMap instance for method chaining\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * ```\n\t */\n\tset(key: K, value: V) {\n\t\tconst existingAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (existingAtom) {\n\t\t\texistingAtom.set(value)\n\t\t} else {\n\t\t\tthis.atoms.update((atoms) => {\n\t\t\t\treturn atoms.set(key, atom(`${this.name}:${String(key)}`, value))\n\t\t\t})\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * Updates an existing value using an updater function.\n\t *\n\t * @param key - The key of the value to update\n\t * @param updater - A function that receives the current value and returns the new value\n\t * @throws Error if the key doesn't exist in the map\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('count', 5)\n\t * map.update('count', count => count + 1) // count is now 6\n\t * ```\n\t */\n\tupdate(key: K, updater: (value: V) => V) {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\tthrow new Error(`AtomMap: key ${key} not found`)\n\t\t}\n\t\tconst value = valueAtom.__unsafe__getWithoutCapture()\n\t\tassert(value !== UNINITIALIZED)\n\t\tvalueAtom.set(updater(value))\n\t}\n\n\t/**\n\t * Removes a key-value pair from the map.\n\t *\n\t * @param key - The key to remove\n\t * @returns True if the key existed and was removed, false if it didn't exist\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('temp', 'value')\n\t * console.log(map.delete('temp')) // true\n\t * console.log(map.delete('missing')) // false\n\t * ```\n\t */\n\tdelete(key: K) {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\treturn false\n\t\t}\n\n\t\ttransact(() => {\n\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\tthis.atoms.update((atoms) => {\n\t\t\t\treturn atoms.delete(key)\n\t\t\t})\n\t\t})\n\t\treturn true\n\t}\n\n\t/**\n\t * Removes multiple key-value pairs from the map in a single transaction.\n\t *\n\t * @param keys - An iterable of keys to remove\n\t * @returns An array of [key, value] pairs that were actually deleted\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2).set('c', 3)\n\t * const deleted = map.deleteMany(['a', 'c', 'missing'])\n\t * console.log(deleted) // [['a', 1], ['c', 3]]\n\t * ```\n\t */\n\tdeleteMany(keys: Iterable<K>): [K, V][] {\n\t\treturn transact(() => {\n\t\t\tconst deleted: [K, V][] = []\n\t\t\tconst newAtoms = this.atoms.get().withMutations((atoms) => {\n\t\t\t\tfor (const key of keys) {\n\t\t\t\t\tconst valueAtom = atoms.get(key)\n\t\t\t\t\tif (!valueAtom) continue\n\t\t\t\t\tconst oldValue = valueAtom.get()\n\t\t\t\t\tassert(oldValue !== UNINITIALIZED)\n\n\t\t\t\t\tdeleted.push([key, oldValue])\n\n\t\t\t\t\tatoms.delete(key)\n\t\t\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif (deleted.length) {\n\t\t\t\tthis.atoms.set(newAtoms)\n\t\t\t}\n\n\t\t\treturn deleted\n\t\t})\n\t}\n\n\t/**\n\t * Removes all key-value pairs from the map.\n\t *\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * map.clear()\n\t * console.log(map.size) // 0\n\t * ```\n\t */\n\tclear() {\n\t\treturn transact(() => {\n\t\t\tfor (const valueAtom of this.atoms.__unsafe__getWithoutCapture().values()) {\n\t\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\t}\n\t\t\tthis.atoms.set(emptyMap())\n\t\t})\n\t}\n\n\t/**\n\t * Returns an iterator that yields [key, value] pairs for each entry in the map.\n\t * This method is reactive and will cause reactive contexts to update when entries change.\n\t *\n\t * @returns A generator that yields [key, value] tuples\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * for (const [key, value] of map.entries()) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t * ```\n\t */\n\t*entries(): Generator<[K, V], undefined, unknown> {\n\t\tfor (const [key, valueAtom] of this.atoms.get()) {\n\t\t\tconst value = valueAtom.get()\n\t\t\tassert(value !== UNINITIALIZED)\n\t\t\tyield [key, value]\n\t\t}\n\t}\n\n\t/**\n\t * Returns an iterator that yields all keys in the map.\n\t * This method is reactive and will cause reactive contexts to update when keys change.\n\t *\n\t * @returns A generator that yields keys\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * for (const key of map.keys()) {\n\t * console.log(key) // 'name', 'age'\n\t * }\n\t * ```\n\t */\n\t*keys(): Generator<K, undefined, unknown> {\n\t\tfor (const key of this.atoms.get().keys()) {\n\t\t\tyield key\n\t\t}\n\t}\n\n\t/**\n\t * Returns an iterator that yields all values in the map.\n\t * This method is reactive and will cause reactive contexts to update when values change.\n\t *\n\t * @returns A generator that yields values\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * for (const value of map.values()) {\n\t * console.log(value) // 'Alice', 30\n\t * }\n\t * ```\n\t */\n\t*values(): Generator<V, undefined, unknown> {\n\t\tfor (const valueAtom of this.atoms.get().values()) {\n\t\t\tconst value = valueAtom.get()\n\t\t\tassert(value !== UNINITIALIZED)\n\t\t\tyield value\n\t\t}\n\t}\n\n\t/**\n\t * The number of key-value pairs in the map.\n\t * This property is reactive and will cause reactive contexts to update when the size changes.\n\t *\n\t * @returns The number of entries in the map\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(map.size) // 0\n\t * map.set('a', 1)\n\t * console.log(map.size) // 1\n\t * ```\n\t */\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget size() {\n\t\treturn this.atoms.get().size\n\t}\n\n\t/**\n\t * Executes a provided function once for each key-value pair in the map.\n\t * This method is reactive and will cause reactive contexts to update when entries change.\n\t *\n\t * @param callbackfn - Function to execute for each entry\n\t * - value - The value of the current entry\n\t * - key - The key of the current entry\n\t * - map - The AtomMap being traversed\n\t * @param thisArg - Value to use as `this` when executing the callback\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * map.forEach((value, key) => {\n\t * console.log(`${key} = ${value}`)\n\t * })\n\t * ```\n\t */\n\tforEach(callbackfn: (value: V, key: K, map: AtomMap<K, V>) => void, thisArg?: any): void {\n\t\tfor (const [key, value] of this.entries()) {\n\t\t\tcallbackfn.call(thisArg, value, key, this)\n\t\t}\n\t}\n\n\t/**\n\t * Returns the default iterator for the map, which is the same as entries().\n\t * This allows the map to be used in for...of loops and other iterable contexts.\n\t *\n\t * @returns The same iterator as entries()\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t *\n\t * // These are equivalent:\n\t * for (const [key, value] of map) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t *\n\t * for (const [key, value] of map.entries()) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t * ```\n\t */\n\t[Symbol.iterator]() {\n\t\treturn this.entries()\n\t}\n\n\t/**\n\t * The string tag used by Object.prototype.toString for this class.\n\t *\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(Object.prototype.toString.call(map)) // '[object AtomMap]'\n\t * ```\n\t */\n\t[Symbol.toStringTag] = 'AtomMap'\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["atoms"]
7
7
  }
@@ -47,7 +47,7 @@ class AtomSet {
47
47
  has(value) {
48
48
  return this.map.has(value);
49
49
  }
50
- // eslint-disable-next-line no-restricted-syntax
50
+ // eslint-disable-next-line tldraw/no-setter-getter
51
51
  get size() {
52
52
  return this.map.size;
53
53
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/AtomSet.ts"],
4
- "sourcesContent": ["import { AtomMap } from './AtomMap'\n\n/**\n * A drop-in replacement for Set that stores values in atoms and can be used in reactive contexts.\n * @public\n */\nexport class AtomSet<T> {\n\tprivate readonly map: AtomMap<T, T>\n\tconstructor(\n\t\tprivate readonly name: string,\n\t\tkeys?: Iterable<T>\n\t) {\n\t\tconst entries = keys ? Array.from(keys, (k) => [k, k] as const) : undefined\n\t\tthis.map = new AtomMap(name, entries)\n\t}\n\n\tadd(value: T): this {\n\t\tthis.map.set(value, value)\n\t\treturn this\n\t}\n\tclear(): void {\n\t\tthis.map.clear()\n\t}\n\tdelete(value: T): boolean {\n\t\treturn this.map.delete(value)\n\t}\n\tforEach(callbackfn: (value: T, value2: T, set: AtomSet<T>) => void, thisArg?: any): void {\n\t\tfor (const value of this) {\n\t\t\tcallbackfn.call(thisArg, value, value, this)\n\t\t}\n\t}\n\thas(value: T): boolean {\n\t\treturn this.map.has(value)\n\t}\n\t// eslint-disable-next-line no-restricted-syntax\n\tget size(): number {\n\t\treturn this.map.size\n\t}\n\tentries(): Generator<[T, T], undefined, unknown> {\n\t\treturn this.map.entries()\n\t}\n\tkeys(): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\tvalues(): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\t[Symbol.iterator](): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\t[Symbol.toStringTag]: string = 'AtomSet'\n}\n"],
4
+ "sourcesContent": ["import { AtomMap } from './AtomMap'\n\n/**\n * A drop-in replacement for Set that stores values in atoms and can be used in reactive contexts.\n * @public\n */\nexport class AtomSet<T> {\n\tprivate readonly map: AtomMap<T, T>\n\tconstructor(\n\t\tprivate readonly name: string,\n\t\tkeys?: Iterable<T>\n\t) {\n\t\tconst entries = keys ? Array.from(keys, (k) => [k, k] as const) : undefined\n\t\tthis.map = new AtomMap(name, entries)\n\t}\n\n\tadd(value: T): this {\n\t\tthis.map.set(value, value)\n\t\treturn this\n\t}\n\tclear(): void {\n\t\tthis.map.clear()\n\t}\n\tdelete(value: T): boolean {\n\t\treturn this.map.delete(value)\n\t}\n\tforEach(callbackfn: (value: T, value2: T, set: AtomSet<T>) => void, thisArg?: any): void {\n\t\tfor (const value of this) {\n\t\t\tcallbackfn.call(thisArg, value, value, this)\n\t\t}\n\t}\n\thas(value: T): boolean {\n\t\treturn this.map.has(value)\n\t}\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget size(): number {\n\t\treturn this.map.size\n\t}\n\tentries(): Generator<[T, T], undefined, unknown> {\n\t\treturn this.map.entries()\n\t}\n\tkeys(): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\tvalues(): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\t[Symbol.iterator](): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\t[Symbol.toStringTag]: string = 'AtomSet'\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAwB;AAMjB,MAAM,QAAW;AAAA,EAEvB,YACkB,MACjB,MACC;AAFgB;AAGjB,UAAM,UAAU,OAAO,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAU,IAAI;AAClE,SAAK,MAAM,IAAI,uBAAQ,MAAM,OAAO;AAAA,EACrC;AAAA,EAPiB;AAAA,EASjB,IAAI,OAAgB;AACnB,SAAK,IAAI,IAAI,OAAO,KAAK;AACzB,WAAO;AAAA,EACR;AAAA,EACA,QAAc;AACb,SAAK,IAAI,MAAM;AAAA,EAChB;AAAA,EACA,OAAO,OAAmB;AACzB,WAAO,KAAK,IAAI,OAAO,KAAK;AAAA,EAC7B;AAAA,EACA,QAAQ,YAA4D,SAAqB;AACxF,eAAW,SAAS,MAAM;AACzB,iBAAW,KAAK,SAAS,OAAO,OAAO,IAAI;AAAA,IAC5C;AAAA,EACD;AAAA,EACA,IAAI,OAAmB;AACtB,WAAO,KAAK,IAAI,IAAI,KAAK;AAAA,EAC1B;AAAA;AAAA,EAEA,IAAI,OAAe;AAClB,WAAO,KAAK,IAAI;AAAA,EACjB;AAAA,EACA,UAAiD;AAChD,WAAO,KAAK,IAAI,QAAQ;AAAA,EACzB;AAAA,EACA,OAAyC;AACxC,WAAO,KAAK,IAAI,KAAK;AAAA,EACtB;AAAA,EACA,SAA2C;AAC1C,WAAO,KAAK,IAAI,KAAK;AAAA,EACtB;AAAA,EACA,CAAC,OAAO,QAAQ,IAAsC;AACrD,WAAO,KAAK,IAAI,KAAK;AAAA,EACtB;AAAA,EACA,CAAC,OAAO,WAAW,IAAY;AAChC;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/RecordType.ts"],
4
- "sourcesContent": ["import { Expand, objectMapEntries, structuredClone, uniqueId } from '@tldraw/utils'\nimport { IdOf, UnknownRecord } from './BaseRecord'\nimport { StoreValidator } from './Store'\n\n/**\n * Utility type that extracts the record type from a RecordType instance.\n *\n * @example\n * ```ts\n * const Book = createRecordType<BookRecord>('book', { scope: 'document' })\n * type BookFromType = RecordTypeRecord<typeof Book> // BookRecord\n * ```\n *\n * @public\n */\nexport type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['create']>\n\n/**\n * Defines the scope of the record\n *\n * session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.\n * document: The record is persisted and synced. It is available to all store instances.\n * presence: The record belongs to a single instance of the store. It may be synced to other instances, but other instances should not make changes to it. It should not be persisted.\n *\n * @public\n * */\nexport type RecordScope = 'session' | 'document' | 'presence'\n\n/**\n * A record type is a type that can be stored in a record store. It is created with\n * `createRecordType`.\n *\n * @public\n */\nexport class RecordType<\n\tR extends UnknownRecord,\n\tRequiredProperties extends keyof Omit<R, 'id' | 'typeName'>,\n> {\n\t/**\n\t * Factory function that creates default properties for new records.\n\t * @public\n\t */\n\treadonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>\n\n\t/**\n\t * Validator function used to validate records of this type.\n\t * @public\n\t */\n\treadonly validator: StoreValidator<R>\n\n\t/**\n\t * Optional configuration specifying which record properties are ephemeral.\n\t * Ephemeral properties are not included in snapshots or synchronization.\n\t * @public\n\t */\n\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\n\t/**\n\t * Set of property names that are marked as ephemeral for efficient lookup.\n\t * @public\n\t */\n\treadonly ephemeralKeySet: ReadonlySet<string>\n\n\t/**\n\t * The scope that determines how records of this type are persisted and synchronized.\n\t * @public\n\t */\n\treadonly scope: RecordScope\n\n\t/**\n\t * Creates a new RecordType instance.\n\t *\n\t * typeName - The unique type name for records created by this RecordType\n\t * config - Configuration object for the RecordType\n\t * - createDefaultProperties - Function that returns default properties for new records\n\t * - validator - Optional validator function for record validation\n\t * - scope - Optional scope determining persistence behavior (defaults to 'document')\n\t * - ephemeralKeys - Optional mapping of property names to ephemeral status\n\t * @public\n\t */\n\tconstructor(\n\t\t/**\n\t\t * The unique type associated with this record.\n\t\t *\n\t\t * @public\n\t\t * @readonly\n\t\t */\n\t\tpublic readonly typeName: R['typeName'],\n\t\tconfig: {\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly createDefaultProperties: () => Exclude<\n\t\t\t\tOmit<R, 'id' | 'typeName'>,\n\t\t\t\tRequiredProperties\n\t\t\t>\n\t\t\treadonly validator?: StoreValidator<R>\n\t\t\treadonly scope?: RecordScope\n\t\t\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t\t}\n\t) {\n\t\tthis.createDefaultProperties = config.createDefaultProperties\n\t\tthis.validator = config.validator ?? { validate: (r: unknown) => r as R }\n\t\tthis.scope = config.scope ?? 'document'\n\t\tthis.ephemeralKeys = config.ephemeralKeys\n\n\t\tconst ephemeralKeySet = new Set<string>()\n\t\tif (config.ephemeralKeys) {\n\t\t\tfor (const [key, isEphemeral] of objectMapEntries(config.ephemeralKeys)) {\n\t\t\t\tif (isEphemeral) ephemeralKeySet.add(key as string)\n\t\t\t}\n\t\t}\n\t\tthis.ephemeralKeySet = ephemeralKeySet\n\t}\n\n\t/**\n\t * Creates a new record of this type with the given properties.\n\t *\n\t * Properties are merged with default properties from the RecordType configuration.\n\t * If no id is provided, a unique id will be generated automatically.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = Book.create({\n\t * title: 'The Great Gatsby',\n\t * author: 'F. Scott Fitzgerald'\n\t * })\n\t * // Result: { id: 'book:abc123', typeName: 'book', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', inStock: true }\n\t * ```\n\t *\n\t * @param properties - The properties for the new record, including both required and optional fields\n\t * @returns The newly created record with generated id and typeName\n\t * @public\n\t */\n\tcreate(\n\t\tproperties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>\n\t): R {\n\t\tconst result = {\n\t\t\t...this.createDefaultProperties(),\n\t\t\tid: 'id' in properties ? properties.id : this.createId(),\n\t\t} as any\n\n\t\tfor (const [k, v] of Object.entries(properties)) {\n\t\t\tif (v !== undefined) {\n\t\t\t\tresult[k] = v\n\t\t\t}\n\t\t}\n\n\t\tresult.typeName = this.typeName\n\n\t\treturn result as R\n\t}\n\n\t/**\n\t * Creates a deep copy of an existing record with a new unique id.\n\t *\n\t * This method performs a deep clone of all properties while generating a fresh id,\n\t * making it useful for duplicating records without id conflicts.\n\t *\n\t * @example\n\t * ```ts\n\t * const originalBook = Book.create({ title: '1984', author: 'George Orwell' })\n\t * const duplicatedBook = Book.clone(originalBook)\n\t * // duplicatedBook has same properties but different id\n\t * ```\n\t *\n\t * @param record - The record to clone\n\t * @returns A new record with the same properties but a different id\n\t * @public\n\t */\n\tclone(record: R): R {\n\t\treturn { ...structuredClone(record), id: this.createId() }\n\t}\n\n\t/**\n\t * Create a new ID for this record type.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const id = recordType.createId()\n\t * ```\n\t *\n\t * @returns The new ID.\n\t * @public\n\t */\n\tcreateId(customUniquePart?: string): IdOf<R> {\n\t\treturn (this.typeName + ':' + (customUniquePart ?? uniqueId())) as IdOf<R>\n\t}\n\n\t/**\n\t * Extracts the unique identifier part from a full record id.\n\t *\n\t * Record ids have the format `typeName:uniquePart`. This method returns just the unique part.\n\t *\n\t * @example\n\t * ```ts\n\t * const bookId = Book.createId() // 'book:abc123'\n\t * const uniquePart = Book.parseId(bookId) // 'abc123'\n\t * ```\n\t *\n\t * @param id - The full record id to parse\n\t * @returns The unique identifier portion after the colon\n\t * @throws Error if the id is not valid for this record type\n\t * @public\n\t */\n\tparseId(id: IdOf<R>): string {\n\t\tif (!this.isId(id)) {\n\t\t\tthrow new Error(`ID \"${id}\" is not a valid ID for type \"${this.typeName}\"`)\n\t\t}\n\n\t\treturn id.slice(this.typeName.length + 1)\n\t}\n\n\t/**\n\t * Type guard that checks whether a record belongs to this RecordType.\n\t *\n\t * This method performs a runtime check by comparing the record's typeName\n\t * against this RecordType's typeName.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isInstance(someRecord)) {\n\t * // someRecord is now typed as a book record\n\t * console.log(someRecord.title)\n\t * }\n\t * ```\n\t *\n\t * @param record - The record to check, may be undefined\n\t * @returns True if the record is an instance of this record type\n\t * @public\n\t */\n\tisInstance(record?: UnknownRecord): record is R {\n\t\treturn record?.typeName === this.typeName\n\t}\n\n\t/**\n\t * Type guard that checks whether an id string belongs to this RecordType.\n\t *\n\t * Validates that the id starts with this RecordType's typeName followed by a colon.\n\t * This is more efficient than parsing the full id when you only need to verify the type.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isId(someId)) {\n\t * // someId is now typed as IdOf<BookRecord>\n\t * const book = store.get(someId)\n\t * }\n\t * ```\n\t *\n\t * @param id - The id string to check, may be undefined\n\t * @returns True if the id belongs to this record type\n\t * @public\n\t */\n\tisId(id?: string): id is IdOf<R> {\n\t\tif (!id) return false\n\t\tfor (let i = 0; i < this.typeName.length; i++) {\n\t\t\tif (id[i] !== this.typeName[i]) return false\n\t\t}\n\n\t\treturn id[this.typeName.length] === ':'\n\t}\n\n\t/**\n\t * Create a new RecordType that has the same type name as this RecordType and includes the given\n\t * default properties.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const authorType = createRecordType('author', () => ({ living: true }))\n\t * const deadAuthorType = authorType.withDefaultProperties({ living: false })\n\t * ```\n\t *\n\t * @param createDefaultProperties - A function that returns the default properties of the new RecordType.\n\t * @returns The new RecordType.\n\t */\n\twithDefaultProperties<DefaultProps extends Omit<Partial<R>, 'typeName' | 'id'>>(\n\t\tcreateDefaultProperties: () => DefaultProps\n\t): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>> {\n\t\treturn new RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>(this.typeName, {\n\t\t\tcreateDefaultProperties: createDefaultProperties as any,\n\t\t\tvalidator: this.validator,\n\t\t\tscope: this.scope,\n\t\t\tephemeralKeys: this.ephemeralKeys,\n\t\t})\n\t}\n\n\t/**\n\t * Validates a record against this RecordType's validator and returns it with proper typing.\n\t *\n\t * This method runs the configured validator function and throws an error if validation fails.\n\t * If a previous version of the record is provided, it may use optimized validation.\n\t *\n\t * @example\n\t * ```ts\n\t * try {\n\t * const validBook = Book.validate(untrustedData)\n\t * // validBook is now properly typed and validated\n\t * } catch (error) {\n\t * console.log('Validation failed:', error.message)\n\t * }\n\t * ```\n\t *\n\t * @param record - The unknown record data to validate\n\t * @param recordBefore - Optional previous version for optimized validation\n\t * @returns The validated and properly typed record\n\t * @throws Error if validation fails\n\t * @public\n\t */\n\tvalidate(record: unknown, recordBefore?: R): R {\n\t\tif (recordBefore && this.validator.validateUsingKnownGoodVersion) {\n\t\t\treturn this.validator.validateUsingKnownGoodVersion(recordBefore, record)\n\t\t}\n\t\treturn this.validator.validate(record)\n\t}\n}\n\n/**\n * Creates a new RecordType with the specified configuration.\n *\n * This factory function creates a RecordType that can be used to create, validate, and manage\n * records of a specific type within a store. The resulting RecordType can be extended with\n * default properties using the withDefaultProperties method.\n *\n * @example\n * ```ts\n * interface BookRecord extends BaseRecord<'book', RecordId<BookRecord>> {\n * title: string\n * author: string\n * inStock: boolean\n * }\n *\n * const Book = createRecordType<BookRecord>('book', {\n * scope: 'document',\n * validator: bookValidator\n * })\n * ```\n *\n * @param typeName - The unique type name for this record type\n * @param config - Configuration object containing validator, scope, and ephemeral keys\n * @returns A new RecordType instance for creating and managing records\n * @public\n */\nexport function createRecordType<R extends UnknownRecord>(\n\ttypeName: R['typeName'],\n\tconfig: {\n\t\tvalidator?: StoreValidator<R>\n\t\tscope: RecordScope\n\t\tephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t}\n): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {\n\treturn new RecordType<R, keyof Omit<R, 'id' | 'typeName'>>(typeName, {\n\t\tcreateDefaultProperties: () => ({}) as any,\n\t\tvalidator: config.validator,\n\t\tscope: config.scope,\n\t\tephemeralKeys: config.ephemeralKeys,\n\t})\n}\n\n/**\n * Assert whether an id correspond to a record type.\n *\n * @example\n *\n * ```ts\n * assertIdType(myId, \"shape\")\n * ```\n *\n * @param id - The id to check.\n * @param type - The type of the record.\n * @public\n */\nexport function assertIdType<R extends UnknownRecord>(\n\tid: string | undefined,\n\ttype: RecordType<R, any>\n): asserts id is IdOf<R> {\n\tif (!id || !type.isId(id)) {\n\t\tthrow new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`)\n\t}\n}\n"],
4
+ "sourcesContent": ["import { Expand, objectMapEntries, structuredClone, uniqueId } from '@tldraw/utils'\nimport { IdOf, UnknownRecord } from './BaseRecord'\nimport { StoreValidator } from './Store'\n\n/**\n * Utility type that extracts the record type from a RecordType instance.\n *\n * @example\n * ```ts\n * const Book = createRecordType<BookRecord>('book', { scope: 'document' })\n * type BookFromType = RecordTypeRecord<typeof Book> // BookRecord\n * ```\n *\n * @public\n */\nexport type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['create']>\n\n/**\n * Defines the scope of the record\n *\n * session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.\n * document: The record is persisted and synced. It is available to all store instances.\n * presence: The record belongs to a single instance of the store. It may be synced to other instances, but other instances should not make changes to it. It should not be persisted.\n *\n * @public\n * */\nexport type RecordScope = 'session' | 'document' | 'presence'\n\n/**\n * A record type is a type that can be stored in a record store. It is created with\n * `createRecordType`.\n *\n * @public\n */\nexport class RecordType<\n\tR extends UnknownRecord,\n\tRequiredProperties extends keyof Omit<R, 'id' | 'typeName'>,\n> {\n\t/**\n\t * Factory function that creates default properties for new records.\n\t * @public\n\t */\n\treadonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>\n\n\t/**\n\t * Validator function used to validate records of this type.\n\t * @public\n\t */\n\treadonly validator: StoreValidator<R>\n\n\t/**\n\t * Optional configuration specifying which record properties are ephemeral.\n\t * Ephemeral properties are not included in snapshots or synchronization.\n\t * @public\n\t */\n\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\n\t/**\n\t * Set of property names that are marked as ephemeral for efficient lookup.\n\t * @public\n\t */\n\treadonly ephemeralKeySet: ReadonlySet<string>\n\n\t/**\n\t * The scope that determines how records of this type are persisted and synchronized.\n\t * @public\n\t */\n\treadonly scope: RecordScope\n\n\t/**\n\t * Creates a new RecordType instance.\n\t *\n\t * typeName - The unique type name for records created by this RecordType\n\t * config - Configuration object for the RecordType\n\t * - createDefaultProperties - Function that returns default properties for new records\n\t * - validator - Optional validator function for record validation\n\t * - scope - Optional scope determining persistence behavior (defaults to 'document')\n\t * - ephemeralKeys - Optional mapping of property names to ephemeral status\n\t * @public\n\t */\n\tconstructor(\n\t\t/**\n\t\t * The unique type associated with this record.\n\t\t *\n\t\t * @public\n\t\t * @readonly\n\t\t */\n\t\tpublic readonly typeName: R['typeName'],\n\t\tconfig: {\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly createDefaultProperties: () => Exclude<\n\t\t\t\tOmit<R, 'id' | 'typeName'>,\n\t\t\t\tRequiredProperties\n\t\t\t>\n\t\t\treadonly validator?: StoreValidator<R>\n\t\t\treadonly scope?: RecordScope\n\t\t\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t\t}\n\t) {\n\t\tthis.createDefaultProperties = config.createDefaultProperties\n\t\tthis.validator = config.validator ?? { validate: (r: unknown) => r as R }\n\t\tthis.scope = config.scope ?? 'document'\n\t\tthis.ephemeralKeys = config.ephemeralKeys\n\n\t\tconst ephemeralKeySet = new Set<string>()\n\t\tif (config.ephemeralKeys) {\n\t\t\tfor (const [key, isEphemeral] of objectMapEntries(config.ephemeralKeys)) {\n\t\t\t\tif (isEphemeral) ephemeralKeySet.add(key as string)\n\t\t\t}\n\t\t}\n\t\tthis.ephemeralKeySet = ephemeralKeySet\n\t}\n\n\t/**\n\t * Creates a new record of this type with the given properties.\n\t *\n\t * Properties are merged with default properties from the RecordType configuration.\n\t * If no id is provided, a unique id will be generated automatically.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = Book.create({\n\t * title: 'The Great Gatsby',\n\t * author: 'F. Scott Fitzgerald'\n\t * })\n\t * // Result: { id: 'book:abc123', typeName: 'book', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', inStock: true }\n\t * ```\n\t *\n\t * @param properties - The properties for the new record, including both required and optional fields\n\t * @returns The newly created record with generated id and typeName\n\t * @public\n\t */\n\tcreate(\n\t\tproperties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>\n\t): R {\n\t\tconst result = {\n\t\t\t...this.createDefaultProperties(),\n\t\t\tid: 'id' in properties ? properties.id : this.createId(),\n\t\t} as any\n\n\t\tfor (const [k, v] of Object.entries(properties)) {\n\t\t\tif (v !== undefined) {\n\t\t\t\tresult[k] = v\n\t\t\t}\n\t\t}\n\n\t\tresult.typeName = this.typeName\n\n\t\treturn result as R\n\t}\n\n\t/**\n\t * Creates a deep copy of an existing record with a new unique id.\n\t *\n\t * This method performs a deep clone of all properties while generating a fresh id,\n\t * making it useful for duplicating records without id conflicts.\n\t *\n\t * @example\n\t * ```ts\n\t * const originalBook = Book.create({ title: '1984', author: 'George Orwell' })\n\t * const duplicatedBook = Book.clone(originalBook)\n\t * // duplicatedBook has same properties but different id\n\t * ```\n\t *\n\t * @param record - The record to clone\n\t * @returns A new record with the same properties but a different id\n\t * @public\n\t */\n\tclone(record: R): R {\n\t\treturn { ...structuredClone(record), id: this.createId() }\n\t}\n\n\t/**\n\t * Create a new ID for this record type.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const id = recordType.createId()\n\t * ```\n\t *\n\t * @returns The new ID.\n\t * @public\n\t */\n\tcreateId(customUniquePart?: string): IdOf<R> {\n\t\treturn (this.typeName + ':' + (customUniquePart ?? uniqueId())) as IdOf<R>\n\t}\n\n\t/**\n\t * Extracts the unique identifier part from a full record id.\n\t *\n\t * Record ids have the format `typeName:uniquePart`. This method returns just the unique part.\n\t *\n\t * @example\n\t * ```ts\n\t * const bookId = Book.createId() // 'book:abc123'\n\t * const uniquePart = Book.parseId(bookId) // 'abc123'\n\t * ```\n\t *\n\t * @param id - The full record id to parse\n\t * @returns The unique identifier portion after the colon\n\t * @throws Error if the id is not valid for this record type\n\t * @public\n\t */\n\tparseId(id: IdOf<R>): string {\n\t\tif (!this.isId(id)) {\n\t\t\tthrow new Error(`ID \"${id}\" is not a valid ID for type \"${this.typeName}\"`)\n\t\t}\n\n\t\treturn id.slice(this.typeName.length + 1)\n\t}\n\n\t/**\n\t * Type guard that checks whether a record belongs to this RecordType.\n\t *\n\t * This method performs a runtime check by comparing the record's typeName\n\t * against this RecordType's typeName.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isInstance(someRecord)) {\n\t * // someRecord is now typed as a book record\n\t * console.log(someRecord.title)\n\t * }\n\t * ```\n\t *\n\t * @param record - The record to check, may be undefined\n\t * @returns True if the record is an instance of this record type\n\t * @public\n\t */\n\tisInstance(record?: UnknownRecord): record is R {\n\t\treturn record?.typeName === this.typeName\n\t}\n\n\t/**\n\t * Type guard that checks whether an id string belongs to this RecordType.\n\t *\n\t * Validates that the id starts with this RecordType's typeName followed by a colon.\n\t * This is more efficient than parsing the full id when you only need to verify the type.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isId(someId)) {\n\t * // someId is now typed as IdOf<BookRecord>\n\t * const book = store.get(someId)\n\t * }\n\t * ```\n\t *\n\t * @param id - The id string to check, may be undefined\n\t * @returns True if the id belongs to this record type\n\t * @public\n\t */\n\tisId(id?: string): id is IdOf<R> {\n\t\tif (!id) return false\n\t\tfor (let i = 0; i < this.typeName.length; i++) {\n\t\t\tif (id[i] !== this.typeName[i]) return false\n\t\t}\n\n\t\treturn id[this.typeName.length] === ':'\n\t}\n\n\t/**\n\t * Create a new RecordType that has the same type name as this RecordType and includes the given\n\t * default properties.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const authorType = createRecordType('author', () => ({ living: true }))\n\t * const deadAuthorType = authorType.withDefaultProperties({ living: false })\n\t * ```\n\t *\n\t * @param createDefaultProperties - A function that returns the default properties of the new RecordType.\n\t * @returns The new RecordType.\n\t */\n\twithDefaultProperties<DefaultProps extends Omit<Partial<R>, 'typeName' | 'id'>>(\n\t\tcreateDefaultProperties: () => DefaultProps\n\t): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>> {\n\t\treturn new RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>(this.typeName, {\n\t\t\tcreateDefaultProperties: createDefaultProperties as any,\n\t\t\tvalidator: this.validator,\n\t\t\tscope: this.scope,\n\t\t\tephemeralKeys: this.ephemeralKeys,\n\t\t})\n\t}\n\n\t/**\n\t * Validates a record against this RecordType's validator and returns it with proper typing.\n\t *\n\t * This method runs the configured validator function and throws an error if validation fails.\n\t * If a previous version of the record is provided, it may use optimized validation.\n\t *\n\t * @example\n\t * ```ts\n\t * try {\n\t * const validBook = Book.validate(untrustedData)\n\t * // validBook is now properly typed and validated\n\t * } catch (error) {\n\t * console.log('Validation failed:', error.message)\n\t * }\n\t * ```\n\t *\n\t * @param record - The unknown record data to validate\n\t * @param recordBefore - Optional previous version for optimized validation\n\t * @returns The validated and properly typed record\n\t * @throws Error if validation fails\n\t * @public\n\t */\n\tvalidate(record: unknown, recordBefore?: R): R {\n\t\tif (recordBefore && this.validator.validateUsingKnownGoodVersion) {\n\t\t\treturn this.validator.validateUsingKnownGoodVersion(recordBefore, record)\n\t\t}\n\t\treturn this.validator.validate(record)\n\t}\n}\n\n/**\n * Creates a new RecordType with the specified configuration.\n *\n * This factory function creates a RecordType that can be used to create, validate, and manage\n * records of a specific type within a store. The resulting RecordType can be extended with\n * default properties using the withDefaultProperties method.\n *\n * @example\n * ```ts\n * interface BookRecord extends BaseRecord<'book', RecordId<BookRecord>> {\n * title: string\n * author: string\n * inStock: boolean\n * }\n *\n * const Book = createRecordType<BookRecord>('book', {\n * scope: 'document',\n * validator: bookValidator\n * })\n * ```\n *\n * @param typeName - The unique type name for this record type\n * @param config - Configuration object containing validator, scope, and ephemeral keys\n * @returns A new RecordType instance for creating and managing records\n * @public\n */\nexport function createRecordType<R extends UnknownRecord>(\n\ttypeName: R['typeName'],\n\tconfig: {\n\t\tvalidator?: StoreValidator<R>\n\t\tscope: RecordScope\n\t\tephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t}\n): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {\n\treturn new RecordType<R, keyof Omit<R, 'id' | 'typeName'>>(typeName, {\n\t\tcreateDefaultProperties: () => ({}) as any,\n\t\tvalidator: config.validator,\n\t\tscope: config.scope,\n\t\tephemeralKeys: config.ephemeralKeys,\n\t})\n}\n\n/**\n * Assert whether an id correspond to a record type.\n *\n * @example\n *\n * ```ts\n * assertIdType(myId, \"shape\")\n * ```\n *\n * @param id - The id to check.\n * @param type - The type of the record.\n * @public\n */\nexport function assertIdType<R extends UnknownRecord>(\n\tid: string | undefined,\n\ttype: RecordType<R, any>\n): asserts id is IdOf<R> {\n\tif (!id || !type.isId(id)) {\n\t\tthrow new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`)\n\t}\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/migrate.ts"],
4
- "sourcesContent": ["import { assert, objectMapEntries } from '@tldraw/utils'\nimport { UnknownRecord } from './BaseRecord'\nimport { SerializedStore } from './Store'\nimport { SerializedSchema } from './StoreSchema'\n\nfunction squashDependsOn(sequence: Array<Migration | StandaloneDependsOn>): Migration[] {\n\tconst result: Migration[] = []\n\tfor (let i = sequence.length - 1; i >= 0; i--) {\n\t\tconst elem = sequence[i]\n\t\tif (!('id' in elem)) {\n\t\t\tconst dependsOn = elem.dependsOn\n\t\t\tconst prev = result[0]\n\t\t\tif (prev) {\n\t\t\t\tresult[0] = {\n\t\t\t\t\t...prev,\n\t\t\t\t\tdependsOn: dependsOn.concat(prev.dependsOn ?? []),\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tresult.unshift(elem)\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Creates a migration sequence that defines how to transform data as your schema evolves.\n *\n * A migration sequence contains a series of migrations that are applied in order to transform\n * data from older versions to newer versions. Each migration is identified by a unique ID\n * and can operate at either the record level (transforming individual records) or store level\n * (transforming the entire store structure).\n *\n * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.\n * @param options - Configuration for the migration sequence\n * - sequenceId - Unique identifier for this migration sequence (e.g., 'com.myapp.book')\n * - sequence - Array of migrations or dependency declarations to include in the sequence\n * - retroactive - Whether migrations should apply to snapshots created before this sequence was added (defaults to true)\n * @returns A validated migration sequence that can be included in a store schema\n * @example\n * ```ts\n * const bookMigrations = createMigrationSequence({\n * sequenceId: 'com.myapp.book',\n * sequence: [\n * {\n * id: 'com.myapp.book/1',\n * scope: 'record',\n * up: (record) => ({ ...record, newField: 'default' })\n * }\n * ]\n * })\n * ```\n * @public\n */\nexport function createMigrationSequence({\n\tsequence,\n\tsequenceId,\n\tretroactive = true,\n}: {\n\tsequenceId: string\n\tretroactive?: boolean\n\tsequence: Array<Migration | StandaloneDependsOn>\n}): MigrationSequence {\n\tconst migrations: MigrationSequence = {\n\t\tsequenceId,\n\t\tretroactive,\n\t\tsequence: squashDependsOn(sequence),\n\t}\n\tvalidateMigrations(migrations)\n\treturn migrations\n}\n\n/**\n * Creates a named set of migration IDs from version numbers and a sequence ID.\n *\n * This utility function helps generate properly formatted migration IDs that follow\n * the required `sequenceId/version` pattern. It takes a sequence ID and a record\n * of named versions, returning migration IDs that can be used in migration definitions.\n *\n * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.\n * @param sequenceId - The sequence identifier (e.g., 'com.myapp.book')\n * @param versions - Record mapping version names to numbers\n * @returns Record mapping version names to properly formatted migration IDs\n * @example\n * ```ts\n * const migrationIds = createMigrationIds('com.myapp.book', {\n * addGenre: 1,\n * addPublisher: 2,\n * removeOldField: 3\n * })\n * // Result: {\n * // addGenre: 'com.myapp.book/1',\n * // addPublisher: 'com.myapp.book/2',\n * // removeOldField: 'com.myapp.book/3'\n * // }\n * ```\n * @public\n */\nexport function createMigrationIds<\n\tconst ID extends string,\n\tconst Versions extends Record<string, number>,\n>(sequenceId: ID, versions: Versions): { [K in keyof Versions]: `${ID}/${Versions[K]}` } {\n\treturn Object.fromEntries(\n\t\tobjectMapEntries(versions).map(([key, version]) => [key, `${sequenceId}/${version}`] as const)\n\t) as any\n}\n\n/**\n * Creates a migration sequence specifically for record-level migrations.\n *\n * This is a convenience function that creates a migration sequence where all migrations\n * operate at the record scope and are automatically filtered to apply only to records\n * of a specific type. Each migration in the sequence will be enhanced with the record\n * scope and appropriate filtering logic.\n * @param opts - Configuration for the record migration sequence\n * - recordType - The record type name these migrations should apply to\n * - filter - Optional additional filter function to determine which records to migrate\n * - retroactive - Whether migrations should apply to snapshots created before this sequence was added\n * - sequenceId - Unique identifier for this migration sequence\n * - sequence - Array of record migration definitions (scope will be added automatically)\n * @returns A migration sequence configured for record-level operations\n * @internal\n */\nexport function createRecordMigrationSequence(opts: {\n\trecordType: string\n\tfilter?(record: UnknownRecord): boolean\n\tretroactive?: boolean\n\tsequenceId: string\n\tsequence: Omit<Extract<Migration, { scope: 'record' }>, 'scope'>[]\n}): MigrationSequence {\n\tconst sequenceId = opts.sequenceId\n\treturn createMigrationSequence({\n\t\tsequenceId,\n\t\tretroactive: opts.retroactive ?? true,\n\t\tsequence: opts.sequence.map((m) =>\n\t\t\t'id' in m\n\t\t\t\t? {\n\t\t\t\t\t\t...m,\n\t\t\t\t\t\tscope: 'record',\n\t\t\t\t\t\tfilter: (r: UnknownRecord) =>\n\t\t\t\t\t\t\tr.typeName === opts.recordType &&\n\t\t\t\t\t\t\t(m.filter?.(r) ?? true) &&\n\t\t\t\t\t\t\t(opts.filter?.(r) ?? true),\n\t\t\t\t\t}\n\t\t\t\t: m\n\t\t),\n\t})\n}\n\n/**\n * Legacy migration interface for backward compatibility.\n *\n * This interface represents the old migration format that included both `up` and `down`\n * transformation functions. While still supported, new code should use the `Migration`\n * type which provides more flexibility and better integration with the current system.\n * @public\n */\nexport interface LegacyMigration<Before = any, After = any> {\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tup: (oldState: Before) => After\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tdown: (newState: After) => Before\n}\n\n/**\n * Unique identifier for a migration in the format `sequenceId/version`.\n *\n * Migration IDs follow a specific pattern where the sequence ID identifies the migration\n * sequence and the version number indicates the order within that sequence. For example:\n * 'com.myapp.book/1', 'com.myapp.book/2', etc.\n * @public\n */\nexport type MigrationId = `${string}/${number}`\n\n/**\n * Declares dependencies for migrations without being a migration itself.\n *\n * This interface allows you to specify that future migrations in a sequence depend on\n * migrations from other sequences, without defining an actual migration transformation.\n * It's used to establish cross-sequence dependencies in the migration graph.\n * @public\n */\nexport interface StandaloneDependsOn {\n\treadonly dependsOn: readonly MigrationId[]\n}\n\n/**\n * Defines a single migration that transforms data from one schema version to another.\n *\n * A migration can operate at two different scopes:\n * - `record`: Transforms individual records, with optional filtering to target specific records\n * - `store`: Transforms the entire serialized store structure\n *\n * Each migration has a unique ID and can declare dependencies on other migrations that must\n * be applied first. The `up` function performs the forward transformation, while the optional\n * `down` function can reverse the migration if needed.\n * @public\n */\nexport type Migration = {\n\treadonly id: MigrationId\n\treadonly dependsOn?: readonly MigrationId[] | undefined\n} & (\n\t| {\n\t\t\treadonly scope: 'record'\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly filter?: (record: UnknownRecord) => boolean\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly up: (oldState: UnknownRecord) => void | UnknownRecord\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly down?: (newState: UnknownRecord) => void | UnknownRecord\n\t }\n\t| {\n\t\t\treadonly scope: 'store'\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly up: (\n\t\t\t\toldState: SerializedStore<UnknownRecord>\n\t\t\t) => void | SerializedStore<UnknownRecord>\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly down?: (\n\t\t\t\tnewState: SerializedStore<UnknownRecord>\n\t\t\t) => void | SerializedStore<UnknownRecord>\n\t }\n\t| {\n\t\t\treadonly scope: 'storage'\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly up: (storage: SynchronousRecordStorage<UnknownRecord>) => void\n\t\t\treadonly down?: never\n\t }\n)\n\n/**\n * Abstraction over the store that can be used to perform migrations.\n * @public\n */\nexport interface SynchronousRecordStorage<R extends UnknownRecord> {\n\tget(id: string): R | undefined\n\tset(id: string, record: R): void\n\tdelete(id: string): void\n\tkeys(): Iterable<string>\n\tvalues(): Iterable<R>\n\tentries(): Iterable<[string, R]>\n}\n\n/**\n * Abstraction over the storage that can be used to perform migrations.\n * @public\n */\nexport interface SynchronousStorage<R extends UnknownRecord> extends SynchronousRecordStorage<R> {\n\tgetSchema(): SerializedSchema\n\tsetSchema(schema: SerializedSchema): void\n}\n\n/**\n * Base interface for legacy migration information.\n *\n * Contains the basic structure used by the legacy migration system, including version\n * range information and the migration functions indexed by version number. This is\n * maintained for backward compatibility with older migration definitions.\n * @public\n */\nexport interface LegacyBaseMigrationsInfo {\n\tfirstVersion: number\n\tcurrentVersion: number\n\tmigrators: { [version: number]: LegacyMigration }\n}\n\n/**\n * Legacy migration configuration with support for sub-type migrations.\n *\n * This interface extends the base legacy migration info to support migrations that\n * vary based on a sub-type key within records. This allows different migration paths\n * for different variants of the same record type, which was useful in older migration\n * systems but is now handled more elegantly by the current Migration system.\n * @public\n */\nexport interface LegacyMigrations extends LegacyBaseMigrationsInfo {\n\tsubTypeKey?: string\n\tsubTypeMigrations?: Record<string, LegacyBaseMigrationsInfo>\n}\n\n/**\n * A complete sequence of migrations that can be applied to transform data.\n *\n * A migration sequence represents a series of ordered migrations that belong together,\n * typically for a specific part of your schema. The sequence includes metadata about\n * whether it should be applied retroactively to existing data and contains the actual\n * migration definitions in execution order.\n * @public\n */\nexport interface MigrationSequence {\n\tsequenceId: string\n\t/**\n\t * retroactive should be true if the migrations should be applied to snapshots that were created before\n\t * this migration sequence was added to the schema.\n\t *\n\t * In general:\n\t *\n\t * - retroactive should be true when app developers create their own new migration sequences.\n\t * - retroactive should be false when library developers ship a migration sequence. When you install a library for the first time, any migrations that were added in the library before that point should generally _not_ be applied to your existing data.\n\t */\n\tretroactive: boolean\n\tsequence: Migration[]\n}\n\n/**\n * Sorts migrations using a distance-minimizing topological sort.\n *\n * This function respects two types of dependencies:\n * 1. Implicit sequence dependencies (foo/1 must come before foo/2)\n * 2. Explicit dependencies via `dependsOn` property\n *\n * The algorithm minimizes the total distance between migrations and their explicit\n * dependencies in the final ordering, while maintaining topological correctness.\n * This means when migration A depends on migration B, A will be scheduled as close\n * as possible to B (while respecting all constraints).\n *\n * Implementation uses Kahn's algorithm with priority scoring:\n * - Builds dependency graph and calculates in-degrees\n * - Uses priority queue that prioritizes migrations which unblock explicit dependencies\n * - Processes migrations in urgency order while maintaining topological constraints\n * - Detects cycles by ensuring all migrations are processed\n *\n * @param migrations - Array of migrations to sort\n * @returns Sorted array of migrations in execution order\n * @throws Assertion error if circular dependencies are detected\n * @example\n * ```ts\n * const sorted = sortMigrations([\n * { id: 'app/2', scope: 'record', up: (r) => r },\n * { id: 'app/1', scope: 'record', up: (r) => r },\n * { id: 'lib/1', scope: 'record', up: (r) => r, dependsOn: ['app/1'] }\n * ])\n * // Result: [app/1, app/2, lib/1] (respects both sequence and explicit deps)\n * ```\n * @public\n */\nexport function sortMigrations(migrations: Migration[]): Migration[] {\n\tif (migrations.length === 0) return []\n\n\t// Build dependency graph and calculate in-degrees\n\tconst byId = new Map(migrations.map((m) => [m.id, m]))\n\tconst dependents = new Map<MigrationId, Set<MigrationId>>() // who depends on this\n\tconst inDegree = new Map<MigrationId, number>()\n\tconst explicitDeps = new Map<MigrationId, Set<MigrationId>>() // explicit dependsOn relationships\n\n\t// Initialize\n\tfor (const m of migrations) {\n\t\tinDegree.set(m.id, 0)\n\t\tdependents.set(m.id, new Set())\n\t\texplicitDeps.set(m.id, new Set())\n\t}\n\n\t// Add implicit sequence dependencies and explicit dependencies\n\tfor (const m of migrations) {\n\t\tconst { version, sequenceId } = parseMigrationId(m.id)\n\n\t\t// Implicit dependency on previous in sequence\n\t\tconst prevId = `${sequenceId}/${version - 1}` as MigrationId\n\t\tif (byId.has(prevId)) {\n\t\t\tdependents.get(prevId)!.add(m.id)\n\t\t\tinDegree.set(m.id, inDegree.get(m.id)! + 1)\n\t\t}\n\n\t\t// Explicit dependencies\n\t\tif (m.dependsOn) {\n\t\t\tfor (const depId of m.dependsOn) {\n\t\t\t\tif (byId.has(depId)) {\n\t\t\t\t\tdependents.get(depId)!.add(m.id)\n\t\t\t\t\texplicitDeps.get(m.id)!.add(depId)\n\t\t\t\t\tinDegree.set(m.id, inDegree.get(m.id)! + 1)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Priority queue: migrations ready to process (in-degree 0)\n\tconst ready = migrations.filter((m) => inDegree.get(m.id) === 0)\n\tconst result: Migration[] = []\n\tconst processed = new Set<MigrationId>()\n\n\twhile (ready.length > 0) {\n\t\t// Calculate urgency scores for ready migrations and pick the best one\n\t\tlet bestCandidate: Migration | undefined\n\t\tlet bestCandidateScore = -Infinity\n\n\t\tfor (const m of ready) {\n\t\t\tlet urgencyScore = 0\n\n\t\t\tfor (const depId of dependents.get(m.id) || []) {\n\t\t\t\tif (!processed.has(depId)) {\n\t\t\t\t\t// Priority 1: Count all unprocessed dependents (to break ties)\n\t\t\t\t\turgencyScore += 1\n\n\t\t\t\t\t// Priority 2: If this migration is explicitly depended on by others, boost priority\n\t\t\t\t\tif (explicitDeps.get(depId)!.has(m.id)) {\n\t\t\t\t\t\turgencyScore += 100\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\turgencyScore > bestCandidateScore ||\n\t\t\t\t// Tiebreaker: prefer lower sequence/version\n\t\t\t\t(urgencyScore === bestCandidateScore && m.id.localeCompare(bestCandidate?.id ?? '') < 0)\n\t\t\t) {\n\t\t\t\tbestCandidate = m\n\t\t\t\tbestCandidateScore = urgencyScore\n\t\t\t}\n\t\t}\n\n\t\tconst nextMigration = bestCandidate!\n\t\tready.splice(ready.indexOf(nextMigration), 1)\n\n\t\t// Cycle detection - if we have processed everything and still have items left, there's a cycle\n\t\t// This is handled by Kahn's algorithm naturally - if we finish with items unprocessed, there's a cycle\n\n\t\t// Process this migration\n\t\tresult.push(nextMigration)\n\t\tprocessed.add(nextMigration.id)\n\n\t\t// Update in-degrees and add newly ready migrations\n\t\tfor (const depId of dependents.get(nextMigration.id) || []) {\n\t\t\tif (!processed.has(depId)) {\n\t\t\t\tinDegree.set(depId, inDegree.get(depId)! - 1)\n\t\t\t\tif (inDegree.get(depId) === 0) {\n\t\t\t\t\tready.push(byId.get(depId)!)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check for cycles - if we didn't process all migrations, there's a cycle\n\tif (result.length !== migrations.length) {\n\t\tconst unprocessed = migrations.filter((m) => !processed.has(m.id))\n\t\tassert(false, `Circular dependency in migrations: ${unprocessed[0].id}`)\n\t}\n\n\treturn result\n}\n\n/**\n * Parses a migration ID to extract the sequence ID and version number.\n *\n * Migration IDs follow the format `sequenceId/version`, and this function splits\n * them into their component parts. This is used internally for sorting migrations\n * and understanding their relationships.\n * @param id - The migration ID to parse\n * @returns Object containing the sequence ID and numeric version\n * @example\n * ```ts\n * const { sequenceId, version } = parseMigrationId('com.myapp.book/5')\n * // sequenceId: 'com.myapp.book', version: 5\n * ```\n * @internal\n */\nexport function parseMigrationId(id: MigrationId): { sequenceId: string; version: number } {\n\tconst [sequenceId, version] = id.split('/')\n\treturn { sequenceId, version: parseInt(version) }\n}\n\nfunction validateMigrationId(id: string, expectedSequenceId?: string) {\n\tif (expectedSequenceId) {\n\t\tassert(\n\t\t\tid.startsWith(expectedSequenceId + '/'),\n\t\t\t`Every migration in sequence '${expectedSequenceId}' must have an id starting with '${expectedSequenceId}/'. Got invalid id: '${id}'`\n\t\t)\n\t}\n\n\tassert(id.match(/^(.*?)\\/(0|[1-9]\\d*)$/), `Invalid migration id: '${id}'`)\n}\n\n/**\n * Validates that a migration sequence is correctly structured.\n *\n * Performs several validation checks to ensure the migration sequence is valid:\n * - Sequence ID doesn't contain invalid characters\n * - All migration IDs belong to the expected sequence\n * - Migration versions start at 1 and increment by 1\n * - Migration IDs follow the correct format\n * @param migrations - The migration sequence to validate\n * @throws Assertion error if any validation checks fail\n * @example\n * ```ts\n * const sequence = createMigrationSequence({\n * sequenceId: 'com.myapp.book',\n * sequence: [{ id: 'com.myapp.book/1', scope: 'record', up: (r) => r }]\n * })\n * validateMigrations(sequence) // Passes validation\n * ```\n * @public\n */\nexport function validateMigrations(migrations: MigrationSequence) {\n\tassert(\n\t\t!migrations.sequenceId.includes('/'),\n\t\t`sequenceId cannot contain a '/', got ${migrations.sequenceId}`\n\t)\n\tassert(migrations.sequenceId.length, 'sequenceId must be a non-empty string')\n\n\tif (migrations.sequence.length === 0) {\n\t\treturn\n\t}\n\n\tvalidateMigrationId(migrations.sequence[0].id, migrations.sequenceId)\n\tlet n = parseMigrationId(migrations.sequence[0].id).version\n\tassert(\n\t\tn === 1,\n\t\t`Expected the first migrationId to be '${migrations.sequenceId}/1' but got '${migrations.sequence[0].id}'`\n\t)\n\tfor (let i = 1; i < migrations.sequence.length; i++) {\n\t\tconst id = migrations.sequence[i].id\n\t\tvalidateMigrationId(id, migrations.sequenceId)\n\t\tconst m = parseMigrationId(id).version\n\t\tassert(\n\t\t\tm === n + 1,\n\t\t\t`Migration id numbers must increase in increments of 1, expected ${migrations.sequenceId}/${n + 1} but got '${migrations.sequence[i].id}'`\n\t\t)\n\t\tn = m\n\t}\n}\n\n/**\n * Result type returned by migration operations.\n *\n * Migration operations can either succeed and return the transformed value,\n * or fail with a specific reason. This discriminated union type allows for\n * safe handling of both success and error cases when applying migrations.\n * @public\n */\nexport type MigrationResult<T> =\n\t| { type: 'success'; value: T }\n\t| { type: 'error'; reason: MigrationFailureReason }\n\n/** @public */\nexport const MigrationFailureReason = {\n\tIncompatibleSubtype: 'incompatible-subtype',\n\tUnknownType: 'unknown-type',\n\tTargetVersionTooNew: 'target-version-too-new',\n\tTargetVersionTooOld: 'target-version-too-old',\n\tMigrationError: 'migration-error',\n\tUnrecognizedSubtype: 'unrecognized-subtype',\n} as const\n\n/** @public */\nexport type MigrationFailureReason =\n\t(typeof MigrationFailureReason)[keyof typeof MigrationFailureReason]\n\n// This is the magic part for backward compat:\n/** @public */\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport declare namespace MigrationFailureReason {\n\texport type IncompatibleSubtype = typeof MigrationFailureReason.IncompatibleSubtype\n\texport type UnknownType = typeof MigrationFailureReason.UnknownType\n\texport type TargetVersionTooNew = typeof MigrationFailureReason.TargetVersionTooNew\n\texport type TargetVersionTooOld = typeof MigrationFailureReason.TargetVersionTooOld\n\texport type MigrationError = typeof MigrationFailureReason.MigrationError\n\texport type UnrecognizedSubtype = typeof MigrationFailureReason.UnrecognizedSubtype\n}\n"],
4
+ "sourcesContent": ["import { assert, objectMapEntries } from '@tldraw/utils'\nimport { UnknownRecord } from './BaseRecord'\nimport { SerializedStore } from './Store'\nimport { SerializedSchema } from './StoreSchema'\n\nfunction squashDependsOn(sequence: Array<Migration | StandaloneDependsOn>): Migration[] {\n\tconst result: Migration[] = []\n\tfor (let i = sequence.length - 1; i >= 0; i--) {\n\t\tconst elem = sequence[i]\n\t\tif (!('id' in elem)) {\n\t\t\tconst dependsOn = elem.dependsOn\n\t\t\tconst prev = result[0]\n\t\t\tif (prev) {\n\t\t\t\tresult[0] = {\n\t\t\t\t\t...prev,\n\t\t\t\t\tdependsOn: dependsOn.concat(prev.dependsOn ?? []),\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tresult.unshift(elem)\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Creates a migration sequence that defines how to transform data as your schema evolves.\n *\n * A migration sequence contains a series of migrations that are applied in order to transform\n * data from older versions to newer versions. Each migration is identified by a unique ID\n * and can operate at either the record level (transforming individual records) or store level\n * (transforming the entire store structure).\n *\n * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.\n * @param options - Configuration for the migration sequence\n * - sequenceId - Unique identifier for this migration sequence (e.g., 'com.myapp.book')\n * - sequence - Array of migrations or dependency declarations to include in the sequence\n * - retroactive - Whether migrations should apply to snapshots created before this sequence was added (defaults to true)\n * @returns A validated migration sequence that can be included in a store schema\n * @example\n * ```ts\n * const bookMigrations = createMigrationSequence({\n * sequenceId: 'com.myapp.book',\n * sequence: [\n * {\n * id: 'com.myapp.book/1',\n * scope: 'record',\n * up: (record) => ({ ...record, newField: 'default' })\n * }\n * ]\n * })\n * ```\n * @public\n */\nexport function createMigrationSequence({\n\tsequence,\n\tsequenceId,\n\tretroactive = true,\n}: {\n\tsequenceId: string\n\tretroactive?: boolean\n\tsequence: Array<Migration | StandaloneDependsOn>\n}): MigrationSequence {\n\tconst migrations: MigrationSequence = {\n\t\tsequenceId,\n\t\tretroactive,\n\t\tsequence: squashDependsOn(sequence),\n\t}\n\tvalidateMigrations(migrations)\n\treturn migrations\n}\n\n/**\n * Creates a named set of migration IDs from version numbers and a sequence ID.\n *\n * This utility function helps generate properly formatted migration IDs that follow\n * the required `sequenceId/version` pattern. It takes a sequence ID and a record\n * of named versions, returning migration IDs that can be used in migration definitions.\n *\n * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.\n * @param sequenceId - The sequence identifier (e.g., 'com.myapp.book')\n * @param versions - Record mapping version names to numbers\n * @returns Record mapping version names to properly formatted migration IDs\n * @example\n * ```ts\n * const migrationIds = createMigrationIds('com.myapp.book', {\n * addGenre: 1,\n * addPublisher: 2,\n * removeOldField: 3\n * })\n * // Result: {\n * // addGenre: 'com.myapp.book/1',\n * // addPublisher: 'com.myapp.book/2',\n * // removeOldField: 'com.myapp.book/3'\n * // }\n * ```\n * @public\n */\nexport function createMigrationIds<\n\tconst ID extends string,\n\tconst Versions extends Record<string, number>,\n>(sequenceId: ID, versions: Versions): { [K in keyof Versions]: `${ID}/${Versions[K]}` } {\n\treturn Object.fromEntries(\n\t\tobjectMapEntries(versions).map(([key, version]) => [key, `${sequenceId}/${version}`] as const)\n\t) as any\n}\n\n/**\n * Creates a migration sequence specifically for record-level migrations.\n *\n * This is a convenience function that creates a migration sequence where all migrations\n * operate at the record scope and are automatically filtered to apply only to records\n * of a specific type. Each migration in the sequence will be enhanced with the record\n * scope and appropriate filtering logic.\n * @param opts - Configuration for the record migration sequence\n * - recordType - The record type name these migrations should apply to\n * - filter - Optional additional filter function to determine which records to migrate\n * - retroactive - Whether migrations should apply to snapshots created before this sequence was added\n * - sequenceId - Unique identifier for this migration sequence\n * - sequence - Array of record migration definitions (scope will be added automatically)\n * @returns A migration sequence configured for record-level operations\n * @internal\n */\nexport function createRecordMigrationSequence(opts: {\n\trecordType: string\n\tfilter?(record: UnknownRecord): boolean\n\tretroactive?: boolean\n\tsequenceId: string\n\tsequence: Omit<Extract<Migration, { scope: 'record' }>, 'scope'>[]\n}): MigrationSequence {\n\tconst sequenceId = opts.sequenceId\n\treturn createMigrationSequence({\n\t\tsequenceId,\n\t\tretroactive: opts.retroactive ?? true,\n\t\tsequence: opts.sequence.map((m) =>\n\t\t\t'id' in m\n\t\t\t\t? {\n\t\t\t\t\t\t...m,\n\t\t\t\t\t\tscope: 'record',\n\t\t\t\t\t\tfilter: (r: UnknownRecord) =>\n\t\t\t\t\t\t\tr.typeName === opts.recordType &&\n\t\t\t\t\t\t\t(m.filter?.(r) ?? true) &&\n\t\t\t\t\t\t\t(opts.filter?.(r) ?? true),\n\t\t\t\t\t}\n\t\t\t\t: m\n\t\t),\n\t})\n}\n\n/**\n * Legacy migration interface for backward compatibility.\n *\n * This interface represents the old migration format that included both `up` and `down`\n * transformation functions. While still supported, new code should use the `Migration`\n * type which provides more flexibility and better integration with the current system.\n * @public\n */\nexport interface LegacyMigration<Before = any, After = any> {\n\t// eslint-disable-next-line tldraw/method-signature-style\n\tup: (oldState: Before) => After\n\t// eslint-disable-next-line tldraw/method-signature-style\n\tdown: (newState: After) => Before\n}\n\n/**\n * Unique identifier for a migration in the format `sequenceId/version`.\n *\n * Migration IDs follow a specific pattern where the sequence ID identifies the migration\n * sequence and the version number indicates the order within that sequence. For example:\n * 'com.myapp.book/1', 'com.myapp.book/2', etc.\n * @public\n */\nexport type MigrationId = `${string}/${number}`\n\n/**\n * Declares dependencies for migrations without being a migration itself.\n *\n * This interface allows you to specify that future migrations in a sequence depend on\n * migrations from other sequences, without defining an actual migration transformation.\n * It's used to establish cross-sequence dependencies in the migration graph.\n * @public\n */\nexport interface StandaloneDependsOn {\n\treadonly dependsOn: readonly MigrationId[]\n}\n\n/**\n * Defines a single migration that transforms data from one schema version to another.\n *\n * A migration can operate at two different scopes:\n * - `record`: Transforms individual records, with optional filtering to target specific records\n * - `store`: Transforms the entire serialized store structure\n *\n * Each migration has a unique ID and can declare dependencies on other migrations that must\n * be applied first. The `up` function performs the forward transformation, while the optional\n * `down` function can reverse the migration if needed.\n * @public\n */\nexport type Migration = {\n\treadonly id: MigrationId\n\treadonly dependsOn?: readonly MigrationId[] | undefined\n} & (\n\t| {\n\t\t\treadonly scope: 'record'\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly filter?: (record: UnknownRecord) => boolean\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly up: (oldState: UnknownRecord) => void | UnknownRecord\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly down?: (newState: UnknownRecord) => void | UnknownRecord\n\t }\n\t| {\n\t\t\treadonly scope: 'store'\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly up: (\n\t\t\t\toldState: SerializedStore<UnknownRecord>\n\t\t\t) => void | SerializedStore<UnknownRecord>\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly down?: (\n\t\t\t\tnewState: SerializedStore<UnknownRecord>\n\t\t\t) => void | SerializedStore<UnknownRecord>\n\t }\n\t| {\n\t\t\treadonly scope: 'storage'\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly up: (storage: SynchronousRecordStorage<UnknownRecord>) => void\n\t\t\treadonly down?: never\n\t }\n)\n\n/**\n * Abstraction over the store that can be used to perform migrations.\n * @public\n */\nexport interface SynchronousRecordStorage<R extends UnknownRecord> {\n\tget(id: string): R | undefined\n\tset(id: string, record: R): void\n\tdelete(id: string): void\n\tkeys(): Iterable<string>\n\tvalues(): Iterable<R>\n\tentries(): Iterable<[string, R]>\n}\n\n/**\n * Abstraction over the storage that can be used to perform migrations.\n * @public\n */\nexport interface SynchronousStorage<R extends UnknownRecord> extends SynchronousRecordStorage<R> {\n\tgetSchema(): SerializedSchema\n\tsetSchema(schema: SerializedSchema): void\n}\n\n/**\n * Base interface for legacy migration information.\n *\n * Contains the basic structure used by the legacy migration system, including version\n * range information and the migration functions indexed by version number. This is\n * maintained for backward compatibility with older migration definitions.\n * @public\n */\nexport interface LegacyBaseMigrationsInfo {\n\tfirstVersion: number\n\tcurrentVersion: number\n\tmigrators: { [version: number]: LegacyMigration }\n}\n\n/**\n * Legacy migration configuration with support for sub-type migrations.\n *\n * This interface extends the base legacy migration info to support migrations that\n * vary based on a sub-type key within records. This allows different migration paths\n * for different variants of the same record type, which was useful in older migration\n * systems but is now handled more elegantly by the current Migration system.\n * @public\n */\nexport interface LegacyMigrations extends LegacyBaseMigrationsInfo {\n\tsubTypeKey?: string\n\tsubTypeMigrations?: Record<string, LegacyBaseMigrationsInfo>\n}\n\n/**\n * A complete sequence of migrations that can be applied to transform data.\n *\n * A migration sequence represents a series of ordered migrations that belong together,\n * typically for a specific part of your schema. The sequence includes metadata about\n * whether it should be applied retroactively to existing data and contains the actual\n * migration definitions in execution order.\n * @public\n */\nexport interface MigrationSequence {\n\tsequenceId: string\n\t/**\n\t * retroactive should be true if the migrations should be applied to snapshots that were created before\n\t * this migration sequence was added to the schema.\n\t *\n\t * In general:\n\t *\n\t * - retroactive should be true when app developers create their own new migration sequences.\n\t * - retroactive should be false when library developers ship a migration sequence. When you install a library for the first time, any migrations that were added in the library before that point should generally _not_ be applied to your existing data.\n\t */\n\tretroactive: boolean\n\tsequence: Migration[]\n}\n\n/**\n * Sorts migrations using a distance-minimizing topological sort.\n *\n * This function respects two types of dependencies:\n * 1. Implicit sequence dependencies (foo/1 must come before foo/2)\n * 2. Explicit dependencies via `dependsOn` property\n *\n * The algorithm minimizes the total distance between migrations and their explicit\n * dependencies in the final ordering, while maintaining topological correctness.\n * This means when migration A depends on migration B, A will be scheduled as close\n * as possible to B (while respecting all constraints).\n *\n * Implementation uses Kahn's algorithm with priority scoring:\n * - Builds dependency graph and calculates in-degrees\n * - Uses priority queue that prioritizes migrations which unblock explicit dependencies\n * - Processes migrations in urgency order while maintaining topological constraints\n * - Detects cycles by ensuring all migrations are processed\n *\n * @param migrations - Array of migrations to sort\n * @returns Sorted array of migrations in execution order\n * @throws Assertion error if circular dependencies are detected\n * @example\n * ```ts\n * const sorted = sortMigrations([\n * { id: 'app/2', scope: 'record', up: (r) => r },\n * { id: 'app/1', scope: 'record', up: (r) => r },\n * { id: 'lib/1', scope: 'record', up: (r) => r, dependsOn: ['app/1'] }\n * ])\n * // Result: [app/1, app/2, lib/1] (respects both sequence and explicit deps)\n * ```\n * @public\n */\nexport function sortMigrations(migrations: Migration[]): Migration[] {\n\tif (migrations.length === 0) return []\n\n\t// Build dependency graph and calculate in-degrees\n\tconst byId = new Map(migrations.map((m) => [m.id, m]))\n\tconst dependents = new Map<MigrationId, Set<MigrationId>>() // who depends on this\n\tconst inDegree = new Map<MigrationId, number>()\n\tconst explicitDeps = new Map<MigrationId, Set<MigrationId>>() // explicit dependsOn relationships\n\n\t// Initialize\n\tfor (const m of migrations) {\n\t\tinDegree.set(m.id, 0)\n\t\tdependents.set(m.id, new Set())\n\t\texplicitDeps.set(m.id, new Set())\n\t}\n\n\t// Add implicit sequence dependencies and explicit dependencies\n\tfor (const m of migrations) {\n\t\tconst { version, sequenceId } = parseMigrationId(m.id)\n\n\t\t// Implicit dependency on previous in sequence\n\t\tconst prevId = `${sequenceId}/${version - 1}` as MigrationId\n\t\tif (byId.has(prevId)) {\n\t\t\tdependents.get(prevId)!.add(m.id)\n\t\t\tinDegree.set(m.id, inDegree.get(m.id)! + 1)\n\t\t}\n\n\t\t// Explicit dependencies\n\t\tif (m.dependsOn) {\n\t\t\tfor (const depId of m.dependsOn) {\n\t\t\t\tif (byId.has(depId)) {\n\t\t\t\t\tdependents.get(depId)!.add(m.id)\n\t\t\t\t\texplicitDeps.get(m.id)!.add(depId)\n\t\t\t\t\tinDegree.set(m.id, inDegree.get(m.id)! + 1)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Priority queue: migrations ready to process (in-degree 0)\n\tconst ready = migrations.filter((m) => inDegree.get(m.id) === 0)\n\tconst result: Migration[] = []\n\tconst processed = new Set<MigrationId>()\n\n\twhile (ready.length > 0) {\n\t\t// Calculate urgency scores for ready migrations and pick the best one\n\t\tlet bestCandidate: Migration | undefined\n\t\tlet bestCandidateScore = -Infinity\n\n\t\tfor (const m of ready) {\n\t\t\tlet urgencyScore = 0\n\n\t\t\tfor (const depId of dependents.get(m.id) || []) {\n\t\t\t\tif (!processed.has(depId)) {\n\t\t\t\t\t// Priority 1: Count all unprocessed dependents (to break ties)\n\t\t\t\t\turgencyScore += 1\n\n\t\t\t\t\t// Priority 2: If this migration is explicitly depended on by others, boost priority\n\t\t\t\t\tif (explicitDeps.get(depId)!.has(m.id)) {\n\t\t\t\t\t\turgencyScore += 100\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\turgencyScore > bestCandidateScore ||\n\t\t\t\t// Tiebreaker: prefer lower sequence/version\n\t\t\t\t(urgencyScore === bestCandidateScore && m.id.localeCompare(bestCandidate?.id ?? '') < 0)\n\t\t\t) {\n\t\t\t\tbestCandidate = m\n\t\t\t\tbestCandidateScore = urgencyScore\n\t\t\t}\n\t\t}\n\n\t\tconst nextMigration = bestCandidate!\n\t\tready.splice(ready.indexOf(nextMigration), 1)\n\n\t\t// Cycle detection - if we have processed everything and still have items left, there's a cycle\n\t\t// This is handled by Kahn's algorithm naturally - if we finish with items unprocessed, there's a cycle\n\n\t\t// Process this migration\n\t\tresult.push(nextMigration)\n\t\tprocessed.add(nextMigration.id)\n\n\t\t// Update in-degrees and add newly ready migrations\n\t\tfor (const depId of dependents.get(nextMigration.id) || []) {\n\t\t\tif (!processed.has(depId)) {\n\t\t\t\tinDegree.set(depId, inDegree.get(depId)! - 1)\n\t\t\t\tif (inDegree.get(depId) === 0) {\n\t\t\t\t\tready.push(byId.get(depId)!)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check for cycles - if we didn't process all migrations, there's a cycle\n\tif (result.length !== migrations.length) {\n\t\tconst unprocessed = migrations.filter((m) => !processed.has(m.id))\n\t\tassert(false, `Circular dependency in migrations: ${unprocessed[0].id}`)\n\t}\n\n\treturn result\n}\n\n/**\n * Parses a migration ID to extract the sequence ID and version number.\n *\n * Migration IDs follow the format `sequenceId/version`, and this function splits\n * them into their component parts. This is used internally for sorting migrations\n * and understanding their relationships.\n * @param id - The migration ID to parse\n * @returns Object containing the sequence ID and numeric version\n * @example\n * ```ts\n * const { sequenceId, version } = parseMigrationId('com.myapp.book/5')\n * // sequenceId: 'com.myapp.book', version: 5\n * ```\n * @internal\n */\nexport function parseMigrationId(id: MigrationId): { sequenceId: string; version: number } {\n\tconst [sequenceId, version] = id.split('/')\n\treturn { sequenceId, version: parseInt(version) }\n}\n\nfunction validateMigrationId(id: string, expectedSequenceId?: string) {\n\tif (expectedSequenceId) {\n\t\tassert(\n\t\t\tid.startsWith(expectedSequenceId + '/'),\n\t\t\t`Every migration in sequence '${expectedSequenceId}' must have an id starting with '${expectedSequenceId}/'. Got invalid id: '${id}'`\n\t\t)\n\t}\n\n\tassert(id.match(/^(.*?)\\/(0|[1-9]\\d*)$/), `Invalid migration id: '${id}'`)\n}\n\n/**\n * Validates that a migration sequence is correctly structured.\n *\n * Performs several validation checks to ensure the migration sequence is valid:\n * - Sequence ID doesn't contain invalid characters\n * - All migration IDs belong to the expected sequence\n * - Migration versions start at 1 and increment by 1\n * - Migration IDs follow the correct format\n * @param migrations - The migration sequence to validate\n * @throws Assertion error if any validation checks fail\n * @example\n * ```ts\n * const sequence = createMigrationSequence({\n * sequenceId: 'com.myapp.book',\n * sequence: [{ id: 'com.myapp.book/1', scope: 'record', up: (r) => r }]\n * })\n * validateMigrations(sequence) // Passes validation\n * ```\n * @public\n */\nexport function validateMigrations(migrations: MigrationSequence) {\n\tassert(\n\t\t!migrations.sequenceId.includes('/'),\n\t\t`sequenceId cannot contain a '/', got ${migrations.sequenceId}`\n\t)\n\tassert(migrations.sequenceId.length, 'sequenceId must be a non-empty string')\n\n\tif (migrations.sequence.length === 0) {\n\t\treturn\n\t}\n\n\tvalidateMigrationId(migrations.sequence[0].id, migrations.sequenceId)\n\tlet n = parseMigrationId(migrations.sequence[0].id).version\n\tassert(\n\t\tn === 1,\n\t\t`Expected the first migrationId to be '${migrations.sequenceId}/1' but got '${migrations.sequence[0].id}'`\n\t)\n\tfor (let i = 1; i < migrations.sequence.length; i++) {\n\t\tconst id = migrations.sequence[i].id\n\t\tvalidateMigrationId(id, migrations.sequenceId)\n\t\tconst m = parseMigrationId(id).version\n\t\tassert(\n\t\t\tm === n + 1,\n\t\t\t`Migration id numbers must increase in increments of 1, expected ${migrations.sequenceId}/${n + 1} but got '${migrations.sequence[i].id}'`\n\t\t)\n\t\tn = m\n\t}\n}\n\n/**\n * Result type returned by migration operations.\n *\n * Migration operations can either succeed and return the transformed value,\n * or fail with a specific reason. This discriminated union type allows for\n * safe handling of both success and error cases when applying migrations.\n * @public\n */\nexport type MigrationResult<T> =\n\t| { type: 'success'; value: T }\n\t| { type: 'error'; reason: MigrationFailureReason }\n\n/** @public */\nexport const MigrationFailureReason = {\n\tIncompatibleSubtype: 'incompatible-subtype',\n\tUnknownType: 'unknown-type',\n\tTargetVersionTooNew: 'target-version-too-new',\n\tTargetVersionTooOld: 'target-version-too-old',\n\tMigrationError: 'migration-error',\n\tUnrecognizedSubtype: 'unrecognized-subtype',\n} as const\n\n/** @public */\nexport type MigrationFailureReason =\n\t(typeof MigrationFailureReason)[keyof typeof MigrationFailureReason]\n\n// This is the magic part for backward compat:\n/** @public */\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport declare namespace MigrationFailureReason {\n\texport type IncompatibleSubtype = typeof MigrationFailureReason.IncompatibleSubtype\n\texport type UnknownType = typeof MigrationFailureReason.UnknownType\n\texport type TargetVersionTooNew = typeof MigrationFailureReason.TargetVersionTooNew\n\texport type TargetVersionTooOld = typeof MigrationFailureReason.TargetVersionTooOld\n\texport type MigrationError = typeof MigrationFailureReason.MigrationError\n\texport type UnrecognizedSubtype = typeof MigrationFailureReason.UnrecognizedSubtype\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -29,7 +29,7 @@ import {
29
29
  } from "./lib/StoreSideEffects.mjs";
30
30
  registerTldrawLibraryVersion(
31
31
  "@tldraw/store",
32
- "4.5.2",
32
+ "4.6.0-canary.00a8c03b5687",
33
33
  "esm"
34
34
  );
35
35
  export {
@@ -322,7 +322,7 @@ class AtomMap {
322
322
  * console.log(map.size) // 1
323
323
  * ```
324
324
  */
325
- // eslint-disable-next-line no-restricted-syntax
325
+ // eslint-disable-next-line tldraw/no-setter-getter
326
326
  get size() {
327
327
  return this.atoms.get().size;
328
328
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/AtomMap.ts"],
4
- "sourcesContent": ["import { atom, Atom, transact, UNINITIALIZED } from '@tldraw/state'\nimport { assert } from '@tldraw/utils'\nimport { emptyMap, ImmutableMap } from './ImmutableMap'\n\n/**\n * A drop-in replacement for Map that stores values in atoms and can be used in reactive contexts.\n * @public\n */\nexport class AtomMap<K, V> implements Map<K, V> {\n\tprivate atoms: Atom<ImmutableMap<K, Atom<V | UNINITIALIZED>>>\n\n\t/**\n\t * Creates a new AtomMap instance.\n\t *\n\t * name - A unique name for this map, used for atom identification\n\t * entries - Optional initial entries to populate the map with\n\t * @example\n\t * ```ts\n\t * // Create an empty map\n\t * const map = new AtomMap('userMap')\n\t *\n\t * // Create a map with initial data\n\t * const initialData: [string, number][] = [['a', 1], ['b', 2]]\n\t * const mapWithData = new AtomMap('numbersMap', initialData)\n\t * ```\n\t */\n\tconstructor(\n\t\tprivate readonly name: string,\n\t\tentries?: Iterable<readonly [K, V]>\n\t) {\n\t\tlet atoms = emptyMap<K, Atom<V>>()\n\t\tif (entries) {\n\t\t\tatoms = atoms.withMutations((atoms) => {\n\t\t\t\tfor (const [k, v] of entries) {\n\t\t\t\t\tatoms.set(k, atom(`${name}:${String(k)}`, v))\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t\tthis.atoms = atom(`${name}:atoms`, atoms)\n\t}\n\n\t/**\n\t * Retrieves the underlying atom for a given key.\n\t *\n\t * @param key - The key to retrieve the atom for\n\t * @returns The atom containing the value, or undefined if the key doesn't exist\n\t * @internal\n\t */\n\tgetAtom(key: K): Atom<V | UNINITIALIZED> | undefined {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\t// if the value is missing, we want to track whether it's in the present keys set\n\t\t\tthis.atoms.get()\n\t\t\treturn undefined\n\t\t}\n\t\treturn valueAtom\n\t}\n\n\t/**\n\t * Gets the value associated with a key. Returns undefined if the key doesn't exist.\n\t * This method is reactive and will cause reactive contexts to update when the value changes.\n\t *\n\t * @param key - The key to retrieve the value for\n\t * @returns The value associated with the key, or undefined if not found\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice')\n\t * console.log(map.get('name')) // 'Alice'\n\t * console.log(map.get('missing')) // undefined\n\t * ```\n\t */\n\tget(key: K): V | undefined {\n\t\tconst value = this.getAtom(key)?.get()\n\t\tassert(value !== UNINITIALIZED)\n\t\treturn value\n\t}\n\n\t/**\n\t * Gets the value associated with a key without creating reactive dependencies.\n\t * This method will not cause reactive contexts to update when the value changes.\n\t *\n\t * @param key - The key to retrieve the value for\n\t * @returns The value associated with the key, or undefined if not found\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('count', 42)\n\t * const value = map.__unsafe__getWithoutCapture('count') // No reactive subscription\n\t * ```\n\t */\n\t__unsafe__getWithoutCapture(key: K): V | undefined {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) return undefined\n\t\tconst value = valueAtom.__unsafe__getWithoutCapture()\n\t\tassert(value !== UNINITIALIZED)\n\t\treturn value\n\t}\n\n\t/**\n\t * Checks whether a key exists in the map.\n\t * This method is reactive and will cause reactive contexts to update when keys are added or removed.\n\t *\n\t * @param key - The key to check for\n\t * @returns True if the key exists in the map, false otherwise\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(map.has('name')) // false\n\t * map.set('name', 'Alice')\n\t * console.log(map.has('name')) // true\n\t * ```\n\t */\n\thas(key: K): boolean {\n\t\tconst valueAtom = this.getAtom(key)\n\t\tif (!valueAtom) {\n\t\t\treturn false\n\t\t}\n\t\treturn valueAtom.get() !== UNINITIALIZED\n\t}\n\n\t/**\n\t * Checks whether a key exists in the map without creating reactive dependencies.\n\t * This method will not cause reactive contexts to update when keys are added or removed.\n\t *\n\t * @param key - The key to check for\n\t * @returns True if the key exists in the map, false otherwise\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('active', true)\n\t * const exists = map.__unsafe__hasWithoutCapture('active') // No reactive subscription\n\t * ```\n\t */\n\t__unsafe__hasWithoutCapture(key: K): boolean {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) return false\n\t\tassert(valueAtom.__unsafe__getWithoutCapture() !== UNINITIALIZED)\n\t\treturn true\n\t}\n\n\t/**\n\t * Sets a value for the given key. If the key already exists, its value is updated.\n\t * If the key doesn't exist, a new entry is created.\n\t *\n\t * @param key - The key to set the value for\n\t * @param value - The value to associate with the key\n\t * @returns This AtomMap instance for method chaining\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * ```\n\t */\n\tset(key: K, value: V) {\n\t\tconst existingAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (existingAtom) {\n\t\t\texistingAtom.set(value)\n\t\t} else {\n\t\t\tthis.atoms.update((atoms) => {\n\t\t\t\treturn atoms.set(key, atom(`${this.name}:${String(key)}`, value))\n\t\t\t})\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * Updates an existing value using an updater function.\n\t *\n\t * @param key - The key of the value to update\n\t * @param updater - A function that receives the current value and returns the new value\n\t * @throws Error if the key doesn't exist in the map\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('count', 5)\n\t * map.update('count', count => count + 1) // count is now 6\n\t * ```\n\t */\n\tupdate(key: K, updater: (value: V) => V) {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\tthrow new Error(`AtomMap: key ${key} not found`)\n\t\t}\n\t\tconst value = valueAtom.__unsafe__getWithoutCapture()\n\t\tassert(value !== UNINITIALIZED)\n\t\tvalueAtom.set(updater(value))\n\t}\n\n\t/**\n\t * Removes a key-value pair from the map.\n\t *\n\t * @param key - The key to remove\n\t * @returns True if the key existed and was removed, false if it didn't exist\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('temp', 'value')\n\t * console.log(map.delete('temp')) // true\n\t * console.log(map.delete('missing')) // false\n\t * ```\n\t */\n\tdelete(key: K) {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\treturn false\n\t\t}\n\n\t\ttransact(() => {\n\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\tthis.atoms.update((atoms) => {\n\t\t\t\treturn atoms.delete(key)\n\t\t\t})\n\t\t})\n\t\treturn true\n\t}\n\n\t/**\n\t * Removes multiple key-value pairs from the map in a single transaction.\n\t *\n\t * @param keys - An iterable of keys to remove\n\t * @returns An array of [key, value] pairs that were actually deleted\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2).set('c', 3)\n\t * const deleted = map.deleteMany(['a', 'c', 'missing'])\n\t * console.log(deleted) // [['a', 1], ['c', 3]]\n\t * ```\n\t */\n\tdeleteMany(keys: Iterable<K>): [K, V][] {\n\t\treturn transact(() => {\n\t\t\tconst deleted: [K, V][] = []\n\t\t\tconst newAtoms = this.atoms.get().withMutations((atoms) => {\n\t\t\t\tfor (const key of keys) {\n\t\t\t\t\tconst valueAtom = atoms.get(key)\n\t\t\t\t\tif (!valueAtom) continue\n\t\t\t\t\tconst oldValue = valueAtom.get()\n\t\t\t\t\tassert(oldValue !== UNINITIALIZED)\n\n\t\t\t\t\tdeleted.push([key, oldValue])\n\n\t\t\t\t\tatoms.delete(key)\n\t\t\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif (deleted.length) {\n\t\t\t\tthis.atoms.set(newAtoms)\n\t\t\t}\n\n\t\t\treturn deleted\n\t\t})\n\t}\n\n\t/**\n\t * Removes all key-value pairs from the map.\n\t *\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * map.clear()\n\t * console.log(map.size) // 0\n\t * ```\n\t */\n\tclear() {\n\t\treturn transact(() => {\n\t\t\tfor (const valueAtom of this.atoms.__unsafe__getWithoutCapture().values()) {\n\t\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\t}\n\t\t\tthis.atoms.set(emptyMap())\n\t\t})\n\t}\n\n\t/**\n\t * Returns an iterator that yields [key, value] pairs for each entry in the map.\n\t * This method is reactive and will cause reactive contexts to update when entries change.\n\t *\n\t * @returns A generator that yields [key, value] tuples\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * for (const [key, value] of map.entries()) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t * ```\n\t */\n\t*entries(): Generator<[K, V], undefined, unknown> {\n\t\tfor (const [key, valueAtom] of this.atoms.get()) {\n\t\t\tconst value = valueAtom.get()\n\t\t\tassert(value !== UNINITIALIZED)\n\t\t\tyield [key, value]\n\t\t}\n\t}\n\n\t/**\n\t * Returns an iterator that yields all keys in the map.\n\t * This method is reactive and will cause reactive contexts to update when keys change.\n\t *\n\t * @returns A generator that yields keys\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * for (const key of map.keys()) {\n\t * console.log(key) // 'name', 'age'\n\t * }\n\t * ```\n\t */\n\t*keys(): Generator<K, undefined, unknown> {\n\t\tfor (const key of this.atoms.get().keys()) {\n\t\t\tyield key\n\t\t}\n\t}\n\n\t/**\n\t * Returns an iterator that yields all values in the map.\n\t * This method is reactive and will cause reactive contexts to update when values change.\n\t *\n\t * @returns A generator that yields values\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * for (const value of map.values()) {\n\t * console.log(value) // 'Alice', 30\n\t * }\n\t * ```\n\t */\n\t*values(): Generator<V, undefined, unknown> {\n\t\tfor (const valueAtom of this.atoms.get().values()) {\n\t\t\tconst value = valueAtom.get()\n\t\t\tassert(value !== UNINITIALIZED)\n\t\t\tyield value\n\t\t}\n\t}\n\n\t/**\n\t * The number of key-value pairs in the map.\n\t * This property is reactive and will cause reactive contexts to update when the size changes.\n\t *\n\t * @returns The number of entries in the map\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(map.size) // 0\n\t * map.set('a', 1)\n\t * console.log(map.size) // 1\n\t * ```\n\t */\n\t// eslint-disable-next-line no-restricted-syntax\n\tget size() {\n\t\treturn this.atoms.get().size\n\t}\n\n\t/**\n\t * Executes a provided function once for each key-value pair in the map.\n\t * This method is reactive and will cause reactive contexts to update when entries change.\n\t *\n\t * @param callbackfn - Function to execute for each entry\n\t * - value - The value of the current entry\n\t * - key - The key of the current entry\n\t * - map - The AtomMap being traversed\n\t * @param thisArg - Value to use as `this` when executing the callback\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * map.forEach((value, key) => {\n\t * console.log(`${key} = ${value}`)\n\t * })\n\t * ```\n\t */\n\tforEach(callbackfn: (value: V, key: K, map: AtomMap<K, V>) => void, thisArg?: any): void {\n\t\tfor (const [key, value] of this.entries()) {\n\t\t\tcallbackfn.call(thisArg, value, key, this)\n\t\t}\n\t}\n\n\t/**\n\t * Returns the default iterator for the map, which is the same as entries().\n\t * This allows the map to be used in for...of loops and other iterable contexts.\n\t *\n\t * @returns The same iterator as entries()\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t *\n\t * // These are equivalent:\n\t * for (const [key, value] of map) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t *\n\t * for (const [key, value] of map.entries()) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t * ```\n\t */\n\t[Symbol.iterator]() {\n\t\treturn this.entries()\n\t}\n\n\t/**\n\t * The string tag used by Object.prototype.toString for this class.\n\t *\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(Object.prototype.toString.call(map)) // '[object AtomMap]'\n\t * ```\n\t */\n\t[Symbol.toStringTag] = 'AtomMap'\n}\n"],
4
+ "sourcesContent": ["import { atom, Atom, transact, UNINITIALIZED } from '@tldraw/state'\nimport { assert } from '@tldraw/utils'\nimport { emptyMap, ImmutableMap } from './ImmutableMap'\n\n/**\n * A drop-in replacement for Map that stores values in atoms and can be used in reactive contexts.\n * @public\n */\nexport class AtomMap<K, V> implements Map<K, V> {\n\tprivate atoms: Atom<ImmutableMap<K, Atom<V | UNINITIALIZED>>>\n\n\t/**\n\t * Creates a new AtomMap instance.\n\t *\n\t * name - A unique name for this map, used for atom identification\n\t * entries - Optional initial entries to populate the map with\n\t * @example\n\t * ```ts\n\t * // Create an empty map\n\t * const map = new AtomMap('userMap')\n\t *\n\t * // Create a map with initial data\n\t * const initialData: [string, number][] = [['a', 1], ['b', 2]]\n\t * const mapWithData = new AtomMap('numbersMap', initialData)\n\t * ```\n\t */\n\tconstructor(\n\t\tprivate readonly name: string,\n\t\tentries?: Iterable<readonly [K, V]>\n\t) {\n\t\tlet atoms = emptyMap<K, Atom<V>>()\n\t\tif (entries) {\n\t\t\tatoms = atoms.withMutations((atoms) => {\n\t\t\t\tfor (const [k, v] of entries) {\n\t\t\t\t\tatoms.set(k, atom(`${name}:${String(k)}`, v))\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t\tthis.atoms = atom(`${name}:atoms`, atoms)\n\t}\n\n\t/**\n\t * Retrieves the underlying atom for a given key.\n\t *\n\t * @param key - The key to retrieve the atom for\n\t * @returns The atom containing the value, or undefined if the key doesn't exist\n\t * @internal\n\t */\n\tgetAtom(key: K): Atom<V | UNINITIALIZED> | undefined {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\t// if the value is missing, we want to track whether it's in the present keys set\n\t\t\tthis.atoms.get()\n\t\t\treturn undefined\n\t\t}\n\t\treturn valueAtom\n\t}\n\n\t/**\n\t * Gets the value associated with a key. Returns undefined if the key doesn't exist.\n\t * This method is reactive and will cause reactive contexts to update when the value changes.\n\t *\n\t * @param key - The key to retrieve the value for\n\t * @returns The value associated with the key, or undefined if not found\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice')\n\t * console.log(map.get('name')) // 'Alice'\n\t * console.log(map.get('missing')) // undefined\n\t * ```\n\t */\n\tget(key: K): V | undefined {\n\t\tconst value = this.getAtom(key)?.get()\n\t\tassert(value !== UNINITIALIZED)\n\t\treturn value\n\t}\n\n\t/**\n\t * Gets the value associated with a key without creating reactive dependencies.\n\t * This method will not cause reactive contexts to update when the value changes.\n\t *\n\t * @param key - The key to retrieve the value for\n\t * @returns The value associated with the key, or undefined if not found\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('count', 42)\n\t * const value = map.__unsafe__getWithoutCapture('count') // No reactive subscription\n\t * ```\n\t */\n\t__unsafe__getWithoutCapture(key: K): V | undefined {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) return undefined\n\t\tconst value = valueAtom.__unsafe__getWithoutCapture()\n\t\tassert(value !== UNINITIALIZED)\n\t\treturn value\n\t}\n\n\t/**\n\t * Checks whether a key exists in the map.\n\t * This method is reactive and will cause reactive contexts to update when keys are added or removed.\n\t *\n\t * @param key - The key to check for\n\t * @returns True if the key exists in the map, false otherwise\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(map.has('name')) // false\n\t * map.set('name', 'Alice')\n\t * console.log(map.has('name')) // true\n\t * ```\n\t */\n\thas(key: K): boolean {\n\t\tconst valueAtom = this.getAtom(key)\n\t\tif (!valueAtom) {\n\t\t\treturn false\n\t\t}\n\t\treturn valueAtom.get() !== UNINITIALIZED\n\t}\n\n\t/**\n\t * Checks whether a key exists in the map without creating reactive dependencies.\n\t * This method will not cause reactive contexts to update when keys are added or removed.\n\t *\n\t * @param key - The key to check for\n\t * @returns True if the key exists in the map, false otherwise\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('active', true)\n\t * const exists = map.__unsafe__hasWithoutCapture('active') // No reactive subscription\n\t * ```\n\t */\n\t__unsafe__hasWithoutCapture(key: K): boolean {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) return false\n\t\tassert(valueAtom.__unsafe__getWithoutCapture() !== UNINITIALIZED)\n\t\treturn true\n\t}\n\n\t/**\n\t * Sets a value for the given key. If the key already exists, its value is updated.\n\t * If the key doesn't exist, a new entry is created.\n\t *\n\t * @param key - The key to set the value for\n\t * @param value - The value to associate with the key\n\t * @returns This AtomMap instance for method chaining\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * ```\n\t */\n\tset(key: K, value: V) {\n\t\tconst existingAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (existingAtom) {\n\t\t\texistingAtom.set(value)\n\t\t} else {\n\t\t\tthis.atoms.update((atoms) => {\n\t\t\t\treturn atoms.set(key, atom(`${this.name}:${String(key)}`, value))\n\t\t\t})\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * Updates an existing value using an updater function.\n\t *\n\t * @param key - The key of the value to update\n\t * @param updater - A function that receives the current value and returns the new value\n\t * @throws Error if the key doesn't exist in the map\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('count', 5)\n\t * map.update('count', count => count + 1) // count is now 6\n\t * ```\n\t */\n\tupdate(key: K, updater: (value: V) => V) {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\tthrow new Error(`AtomMap: key ${key} not found`)\n\t\t}\n\t\tconst value = valueAtom.__unsafe__getWithoutCapture()\n\t\tassert(value !== UNINITIALIZED)\n\t\tvalueAtom.set(updater(value))\n\t}\n\n\t/**\n\t * Removes a key-value pair from the map.\n\t *\n\t * @param key - The key to remove\n\t * @returns True if the key existed and was removed, false if it didn't exist\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('temp', 'value')\n\t * console.log(map.delete('temp')) // true\n\t * console.log(map.delete('missing')) // false\n\t * ```\n\t */\n\tdelete(key: K) {\n\t\tconst valueAtom = this.atoms.__unsafe__getWithoutCapture().get(key)\n\t\tif (!valueAtom) {\n\t\t\treturn false\n\t\t}\n\n\t\ttransact(() => {\n\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\tthis.atoms.update((atoms) => {\n\t\t\t\treturn atoms.delete(key)\n\t\t\t})\n\t\t})\n\t\treturn true\n\t}\n\n\t/**\n\t * Removes multiple key-value pairs from the map in a single transaction.\n\t *\n\t * @param keys - An iterable of keys to remove\n\t * @returns An array of [key, value] pairs that were actually deleted\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2).set('c', 3)\n\t * const deleted = map.deleteMany(['a', 'c', 'missing'])\n\t * console.log(deleted) // [['a', 1], ['c', 3]]\n\t * ```\n\t */\n\tdeleteMany(keys: Iterable<K>): [K, V][] {\n\t\treturn transact(() => {\n\t\t\tconst deleted: [K, V][] = []\n\t\t\tconst newAtoms = this.atoms.get().withMutations((atoms) => {\n\t\t\t\tfor (const key of keys) {\n\t\t\t\t\tconst valueAtom = atoms.get(key)\n\t\t\t\t\tif (!valueAtom) continue\n\t\t\t\t\tconst oldValue = valueAtom.get()\n\t\t\t\t\tassert(oldValue !== UNINITIALIZED)\n\n\t\t\t\t\tdeleted.push([key, oldValue])\n\n\t\t\t\t\tatoms.delete(key)\n\t\t\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tif (deleted.length) {\n\t\t\t\tthis.atoms.set(newAtoms)\n\t\t\t}\n\n\t\t\treturn deleted\n\t\t})\n\t}\n\n\t/**\n\t * Removes all key-value pairs from the map.\n\t *\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * map.clear()\n\t * console.log(map.size) // 0\n\t * ```\n\t */\n\tclear() {\n\t\treturn transact(() => {\n\t\t\tfor (const valueAtom of this.atoms.__unsafe__getWithoutCapture().values()) {\n\t\t\t\tvalueAtom.set(UNINITIALIZED)\n\t\t\t}\n\t\t\tthis.atoms.set(emptyMap())\n\t\t})\n\t}\n\n\t/**\n\t * Returns an iterator that yields [key, value] pairs for each entry in the map.\n\t * This method is reactive and will cause reactive contexts to update when entries change.\n\t *\n\t * @returns A generator that yields [key, value] tuples\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * for (const [key, value] of map.entries()) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t * ```\n\t */\n\t*entries(): Generator<[K, V], undefined, unknown> {\n\t\tfor (const [key, valueAtom] of this.atoms.get()) {\n\t\t\tconst value = valueAtom.get()\n\t\t\tassert(value !== UNINITIALIZED)\n\t\t\tyield [key, value]\n\t\t}\n\t}\n\n\t/**\n\t * Returns an iterator that yields all keys in the map.\n\t * This method is reactive and will cause reactive contexts to update when keys change.\n\t *\n\t * @returns A generator that yields keys\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * for (const key of map.keys()) {\n\t * console.log(key) // 'name', 'age'\n\t * }\n\t * ```\n\t */\n\t*keys(): Generator<K, undefined, unknown> {\n\t\tfor (const key of this.atoms.get().keys()) {\n\t\t\tyield key\n\t\t}\n\t}\n\n\t/**\n\t * Returns an iterator that yields all values in the map.\n\t * This method is reactive and will cause reactive contexts to update when values change.\n\t *\n\t * @returns A generator that yields values\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('name', 'Alice').set('age', 30)\n\t * for (const value of map.values()) {\n\t * console.log(value) // 'Alice', 30\n\t * }\n\t * ```\n\t */\n\t*values(): Generator<V, undefined, unknown> {\n\t\tfor (const valueAtom of this.atoms.get().values()) {\n\t\t\tconst value = valueAtom.get()\n\t\t\tassert(value !== UNINITIALIZED)\n\t\t\tyield value\n\t\t}\n\t}\n\n\t/**\n\t * The number of key-value pairs in the map.\n\t * This property is reactive and will cause reactive contexts to update when the size changes.\n\t *\n\t * @returns The number of entries in the map\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(map.size) // 0\n\t * map.set('a', 1)\n\t * console.log(map.size) // 1\n\t * ```\n\t */\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget size() {\n\t\treturn this.atoms.get().size\n\t}\n\n\t/**\n\t * Executes a provided function once for each key-value pair in the map.\n\t * This method is reactive and will cause reactive contexts to update when entries change.\n\t *\n\t * @param callbackfn - Function to execute for each entry\n\t * - value - The value of the current entry\n\t * - key - The key of the current entry\n\t * - map - The AtomMap being traversed\n\t * @param thisArg - Value to use as `this` when executing the callback\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t * map.forEach((value, key) => {\n\t * console.log(`${key} = ${value}`)\n\t * })\n\t * ```\n\t */\n\tforEach(callbackfn: (value: V, key: K, map: AtomMap<K, V>) => void, thisArg?: any): void {\n\t\tfor (const [key, value] of this.entries()) {\n\t\t\tcallbackfn.call(thisArg, value, key, this)\n\t\t}\n\t}\n\n\t/**\n\t * Returns the default iterator for the map, which is the same as entries().\n\t * This allows the map to be used in for...of loops and other iterable contexts.\n\t *\n\t * @returns The same iterator as entries()\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * map.set('a', 1).set('b', 2)\n\t *\n\t * // These are equivalent:\n\t * for (const [key, value] of map) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t *\n\t * for (const [key, value] of map.entries()) {\n\t * console.log(`${key}: ${value}`)\n\t * }\n\t * ```\n\t */\n\t[Symbol.iterator]() {\n\t\treturn this.entries()\n\t}\n\n\t/**\n\t * The string tag used by Object.prototype.toString for this class.\n\t *\n\t * @example\n\t * ```ts\n\t * const map = new AtomMap('myMap')\n\t * console.log(Object.prototype.toString.call(map)) // '[object AtomMap]'\n\t * ```\n\t */\n\t[Symbol.toStringTag] = 'AtomMap'\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["atoms"]
7
7
  }
@@ -24,7 +24,7 @@ class AtomSet {
24
24
  has(value) {
25
25
  return this.map.has(value);
26
26
  }
27
- // eslint-disable-next-line no-restricted-syntax
27
+ // eslint-disable-next-line tldraw/no-setter-getter
28
28
  get size() {
29
29
  return this.map.size;
30
30
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/AtomSet.ts"],
4
- "sourcesContent": ["import { AtomMap } from './AtomMap'\n\n/**\n * A drop-in replacement for Set that stores values in atoms and can be used in reactive contexts.\n * @public\n */\nexport class AtomSet<T> {\n\tprivate readonly map: AtomMap<T, T>\n\tconstructor(\n\t\tprivate readonly name: string,\n\t\tkeys?: Iterable<T>\n\t) {\n\t\tconst entries = keys ? Array.from(keys, (k) => [k, k] as const) : undefined\n\t\tthis.map = new AtomMap(name, entries)\n\t}\n\n\tadd(value: T): this {\n\t\tthis.map.set(value, value)\n\t\treturn this\n\t}\n\tclear(): void {\n\t\tthis.map.clear()\n\t}\n\tdelete(value: T): boolean {\n\t\treturn this.map.delete(value)\n\t}\n\tforEach(callbackfn: (value: T, value2: T, set: AtomSet<T>) => void, thisArg?: any): void {\n\t\tfor (const value of this) {\n\t\t\tcallbackfn.call(thisArg, value, value, this)\n\t\t}\n\t}\n\thas(value: T): boolean {\n\t\treturn this.map.has(value)\n\t}\n\t// eslint-disable-next-line no-restricted-syntax\n\tget size(): number {\n\t\treturn this.map.size\n\t}\n\tentries(): Generator<[T, T], undefined, unknown> {\n\t\treturn this.map.entries()\n\t}\n\tkeys(): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\tvalues(): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\t[Symbol.iterator](): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\t[Symbol.toStringTag]: string = 'AtomSet'\n}\n"],
4
+ "sourcesContent": ["import { AtomMap } from './AtomMap'\n\n/**\n * A drop-in replacement for Set that stores values in atoms and can be used in reactive contexts.\n * @public\n */\nexport class AtomSet<T> {\n\tprivate readonly map: AtomMap<T, T>\n\tconstructor(\n\t\tprivate readonly name: string,\n\t\tkeys?: Iterable<T>\n\t) {\n\t\tconst entries = keys ? Array.from(keys, (k) => [k, k] as const) : undefined\n\t\tthis.map = new AtomMap(name, entries)\n\t}\n\n\tadd(value: T): this {\n\t\tthis.map.set(value, value)\n\t\treturn this\n\t}\n\tclear(): void {\n\t\tthis.map.clear()\n\t}\n\tdelete(value: T): boolean {\n\t\treturn this.map.delete(value)\n\t}\n\tforEach(callbackfn: (value: T, value2: T, set: AtomSet<T>) => void, thisArg?: any): void {\n\t\tfor (const value of this) {\n\t\t\tcallbackfn.call(thisArg, value, value, this)\n\t\t}\n\t}\n\thas(value: T): boolean {\n\t\treturn this.map.has(value)\n\t}\n\t// eslint-disable-next-line tldraw/no-setter-getter\n\tget size(): number {\n\t\treturn this.map.size\n\t}\n\tentries(): Generator<[T, T], undefined, unknown> {\n\t\treturn this.map.entries()\n\t}\n\tkeys(): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\tvalues(): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\t[Symbol.iterator](): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\t[Symbol.toStringTag]: string = 'AtomSet'\n}\n"],
5
5
  "mappings": "AAAA,SAAS,eAAe;AAMjB,MAAM,QAAW;AAAA,EAEvB,YACkB,MACjB,MACC;AAFgB;AAGjB,UAAM,UAAU,OAAO,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAU,IAAI;AAClE,SAAK,MAAM,IAAI,QAAQ,MAAM,OAAO;AAAA,EACrC;AAAA,EAPiB;AAAA,EASjB,IAAI,OAAgB;AACnB,SAAK,IAAI,IAAI,OAAO,KAAK;AACzB,WAAO;AAAA,EACR;AAAA,EACA,QAAc;AACb,SAAK,IAAI,MAAM;AAAA,EAChB;AAAA,EACA,OAAO,OAAmB;AACzB,WAAO,KAAK,IAAI,OAAO,KAAK;AAAA,EAC7B;AAAA,EACA,QAAQ,YAA4D,SAAqB;AACxF,eAAW,SAAS,MAAM;AACzB,iBAAW,KAAK,SAAS,OAAO,OAAO,IAAI;AAAA,IAC5C;AAAA,EACD;AAAA,EACA,IAAI,OAAmB;AACtB,WAAO,KAAK,IAAI,IAAI,KAAK;AAAA,EAC1B;AAAA;AAAA,EAEA,IAAI,OAAe;AAClB,WAAO,KAAK,IAAI;AAAA,EACjB;AAAA,EACA,UAAiD;AAChD,WAAO,KAAK,IAAI,QAAQ;AAAA,EACzB;AAAA,EACA,OAAyC;AACxC,WAAO,KAAK,IAAI,KAAK;AAAA,EACtB;AAAA,EACA,SAA2C;AAC1C,WAAO,KAAK,IAAI,KAAK;AAAA,EACtB;AAAA,EACA,CAAC,OAAO,QAAQ,IAAsC;AACrD,WAAO,KAAK,IAAI,KAAK;AAAA,EACtB;AAAA,EACA,CAAC,OAAO,WAAW,IAAY;AAChC;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/RecordType.ts"],
4
- "sourcesContent": ["import { Expand, objectMapEntries, structuredClone, uniqueId } from '@tldraw/utils'\nimport { IdOf, UnknownRecord } from './BaseRecord'\nimport { StoreValidator } from './Store'\n\n/**\n * Utility type that extracts the record type from a RecordType instance.\n *\n * @example\n * ```ts\n * const Book = createRecordType<BookRecord>('book', { scope: 'document' })\n * type BookFromType = RecordTypeRecord<typeof Book> // BookRecord\n * ```\n *\n * @public\n */\nexport type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['create']>\n\n/**\n * Defines the scope of the record\n *\n * session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.\n * document: The record is persisted and synced. It is available to all store instances.\n * presence: The record belongs to a single instance of the store. It may be synced to other instances, but other instances should not make changes to it. It should not be persisted.\n *\n * @public\n * */\nexport type RecordScope = 'session' | 'document' | 'presence'\n\n/**\n * A record type is a type that can be stored in a record store. It is created with\n * `createRecordType`.\n *\n * @public\n */\nexport class RecordType<\n\tR extends UnknownRecord,\n\tRequiredProperties extends keyof Omit<R, 'id' | 'typeName'>,\n> {\n\t/**\n\t * Factory function that creates default properties for new records.\n\t * @public\n\t */\n\treadonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>\n\n\t/**\n\t * Validator function used to validate records of this type.\n\t * @public\n\t */\n\treadonly validator: StoreValidator<R>\n\n\t/**\n\t * Optional configuration specifying which record properties are ephemeral.\n\t * Ephemeral properties are not included in snapshots or synchronization.\n\t * @public\n\t */\n\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\n\t/**\n\t * Set of property names that are marked as ephemeral for efficient lookup.\n\t * @public\n\t */\n\treadonly ephemeralKeySet: ReadonlySet<string>\n\n\t/**\n\t * The scope that determines how records of this type are persisted and synchronized.\n\t * @public\n\t */\n\treadonly scope: RecordScope\n\n\t/**\n\t * Creates a new RecordType instance.\n\t *\n\t * typeName - The unique type name for records created by this RecordType\n\t * config - Configuration object for the RecordType\n\t * - createDefaultProperties - Function that returns default properties for new records\n\t * - validator - Optional validator function for record validation\n\t * - scope - Optional scope determining persistence behavior (defaults to 'document')\n\t * - ephemeralKeys - Optional mapping of property names to ephemeral status\n\t * @public\n\t */\n\tconstructor(\n\t\t/**\n\t\t * The unique type associated with this record.\n\t\t *\n\t\t * @public\n\t\t * @readonly\n\t\t */\n\t\tpublic readonly typeName: R['typeName'],\n\t\tconfig: {\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly createDefaultProperties: () => Exclude<\n\t\t\t\tOmit<R, 'id' | 'typeName'>,\n\t\t\t\tRequiredProperties\n\t\t\t>\n\t\t\treadonly validator?: StoreValidator<R>\n\t\t\treadonly scope?: RecordScope\n\t\t\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t\t}\n\t) {\n\t\tthis.createDefaultProperties = config.createDefaultProperties\n\t\tthis.validator = config.validator ?? { validate: (r: unknown) => r as R }\n\t\tthis.scope = config.scope ?? 'document'\n\t\tthis.ephemeralKeys = config.ephemeralKeys\n\n\t\tconst ephemeralKeySet = new Set<string>()\n\t\tif (config.ephemeralKeys) {\n\t\t\tfor (const [key, isEphemeral] of objectMapEntries(config.ephemeralKeys)) {\n\t\t\t\tif (isEphemeral) ephemeralKeySet.add(key as string)\n\t\t\t}\n\t\t}\n\t\tthis.ephemeralKeySet = ephemeralKeySet\n\t}\n\n\t/**\n\t * Creates a new record of this type with the given properties.\n\t *\n\t * Properties are merged with default properties from the RecordType configuration.\n\t * If no id is provided, a unique id will be generated automatically.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = Book.create({\n\t * title: 'The Great Gatsby',\n\t * author: 'F. Scott Fitzgerald'\n\t * })\n\t * // Result: { id: 'book:abc123', typeName: 'book', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', inStock: true }\n\t * ```\n\t *\n\t * @param properties - The properties for the new record, including both required and optional fields\n\t * @returns The newly created record with generated id and typeName\n\t * @public\n\t */\n\tcreate(\n\t\tproperties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>\n\t): R {\n\t\tconst result = {\n\t\t\t...this.createDefaultProperties(),\n\t\t\tid: 'id' in properties ? properties.id : this.createId(),\n\t\t} as any\n\n\t\tfor (const [k, v] of Object.entries(properties)) {\n\t\t\tif (v !== undefined) {\n\t\t\t\tresult[k] = v\n\t\t\t}\n\t\t}\n\n\t\tresult.typeName = this.typeName\n\n\t\treturn result as R\n\t}\n\n\t/**\n\t * Creates a deep copy of an existing record with a new unique id.\n\t *\n\t * This method performs a deep clone of all properties while generating a fresh id,\n\t * making it useful for duplicating records without id conflicts.\n\t *\n\t * @example\n\t * ```ts\n\t * const originalBook = Book.create({ title: '1984', author: 'George Orwell' })\n\t * const duplicatedBook = Book.clone(originalBook)\n\t * // duplicatedBook has same properties but different id\n\t * ```\n\t *\n\t * @param record - The record to clone\n\t * @returns A new record with the same properties but a different id\n\t * @public\n\t */\n\tclone(record: R): R {\n\t\treturn { ...structuredClone(record), id: this.createId() }\n\t}\n\n\t/**\n\t * Create a new ID for this record type.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const id = recordType.createId()\n\t * ```\n\t *\n\t * @returns The new ID.\n\t * @public\n\t */\n\tcreateId(customUniquePart?: string): IdOf<R> {\n\t\treturn (this.typeName + ':' + (customUniquePart ?? uniqueId())) as IdOf<R>\n\t}\n\n\t/**\n\t * Extracts the unique identifier part from a full record id.\n\t *\n\t * Record ids have the format `typeName:uniquePart`. This method returns just the unique part.\n\t *\n\t * @example\n\t * ```ts\n\t * const bookId = Book.createId() // 'book:abc123'\n\t * const uniquePart = Book.parseId(bookId) // 'abc123'\n\t * ```\n\t *\n\t * @param id - The full record id to parse\n\t * @returns The unique identifier portion after the colon\n\t * @throws Error if the id is not valid for this record type\n\t * @public\n\t */\n\tparseId(id: IdOf<R>): string {\n\t\tif (!this.isId(id)) {\n\t\t\tthrow new Error(`ID \"${id}\" is not a valid ID for type \"${this.typeName}\"`)\n\t\t}\n\n\t\treturn id.slice(this.typeName.length + 1)\n\t}\n\n\t/**\n\t * Type guard that checks whether a record belongs to this RecordType.\n\t *\n\t * This method performs a runtime check by comparing the record's typeName\n\t * against this RecordType's typeName.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isInstance(someRecord)) {\n\t * // someRecord is now typed as a book record\n\t * console.log(someRecord.title)\n\t * }\n\t * ```\n\t *\n\t * @param record - The record to check, may be undefined\n\t * @returns True if the record is an instance of this record type\n\t * @public\n\t */\n\tisInstance(record?: UnknownRecord): record is R {\n\t\treturn record?.typeName === this.typeName\n\t}\n\n\t/**\n\t * Type guard that checks whether an id string belongs to this RecordType.\n\t *\n\t * Validates that the id starts with this RecordType's typeName followed by a colon.\n\t * This is more efficient than parsing the full id when you only need to verify the type.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isId(someId)) {\n\t * // someId is now typed as IdOf<BookRecord>\n\t * const book = store.get(someId)\n\t * }\n\t * ```\n\t *\n\t * @param id - The id string to check, may be undefined\n\t * @returns True if the id belongs to this record type\n\t * @public\n\t */\n\tisId(id?: string): id is IdOf<R> {\n\t\tif (!id) return false\n\t\tfor (let i = 0; i < this.typeName.length; i++) {\n\t\t\tif (id[i] !== this.typeName[i]) return false\n\t\t}\n\n\t\treturn id[this.typeName.length] === ':'\n\t}\n\n\t/**\n\t * Create a new RecordType that has the same type name as this RecordType and includes the given\n\t * default properties.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const authorType = createRecordType('author', () => ({ living: true }))\n\t * const deadAuthorType = authorType.withDefaultProperties({ living: false })\n\t * ```\n\t *\n\t * @param createDefaultProperties - A function that returns the default properties of the new RecordType.\n\t * @returns The new RecordType.\n\t */\n\twithDefaultProperties<DefaultProps extends Omit<Partial<R>, 'typeName' | 'id'>>(\n\t\tcreateDefaultProperties: () => DefaultProps\n\t): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>> {\n\t\treturn new RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>(this.typeName, {\n\t\t\tcreateDefaultProperties: createDefaultProperties as any,\n\t\t\tvalidator: this.validator,\n\t\t\tscope: this.scope,\n\t\t\tephemeralKeys: this.ephemeralKeys,\n\t\t})\n\t}\n\n\t/**\n\t * Validates a record against this RecordType's validator and returns it with proper typing.\n\t *\n\t * This method runs the configured validator function and throws an error if validation fails.\n\t * If a previous version of the record is provided, it may use optimized validation.\n\t *\n\t * @example\n\t * ```ts\n\t * try {\n\t * const validBook = Book.validate(untrustedData)\n\t * // validBook is now properly typed and validated\n\t * } catch (error) {\n\t * console.log('Validation failed:', error.message)\n\t * }\n\t * ```\n\t *\n\t * @param record - The unknown record data to validate\n\t * @param recordBefore - Optional previous version for optimized validation\n\t * @returns The validated and properly typed record\n\t * @throws Error if validation fails\n\t * @public\n\t */\n\tvalidate(record: unknown, recordBefore?: R): R {\n\t\tif (recordBefore && this.validator.validateUsingKnownGoodVersion) {\n\t\t\treturn this.validator.validateUsingKnownGoodVersion(recordBefore, record)\n\t\t}\n\t\treturn this.validator.validate(record)\n\t}\n}\n\n/**\n * Creates a new RecordType with the specified configuration.\n *\n * This factory function creates a RecordType that can be used to create, validate, and manage\n * records of a specific type within a store. The resulting RecordType can be extended with\n * default properties using the withDefaultProperties method.\n *\n * @example\n * ```ts\n * interface BookRecord extends BaseRecord<'book', RecordId<BookRecord>> {\n * title: string\n * author: string\n * inStock: boolean\n * }\n *\n * const Book = createRecordType<BookRecord>('book', {\n * scope: 'document',\n * validator: bookValidator\n * })\n * ```\n *\n * @param typeName - The unique type name for this record type\n * @param config - Configuration object containing validator, scope, and ephemeral keys\n * @returns A new RecordType instance for creating and managing records\n * @public\n */\nexport function createRecordType<R extends UnknownRecord>(\n\ttypeName: R['typeName'],\n\tconfig: {\n\t\tvalidator?: StoreValidator<R>\n\t\tscope: RecordScope\n\t\tephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t}\n): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {\n\treturn new RecordType<R, keyof Omit<R, 'id' | 'typeName'>>(typeName, {\n\t\tcreateDefaultProperties: () => ({}) as any,\n\t\tvalidator: config.validator,\n\t\tscope: config.scope,\n\t\tephemeralKeys: config.ephemeralKeys,\n\t})\n}\n\n/**\n * Assert whether an id correspond to a record type.\n *\n * @example\n *\n * ```ts\n * assertIdType(myId, \"shape\")\n * ```\n *\n * @param id - The id to check.\n * @param type - The type of the record.\n * @public\n */\nexport function assertIdType<R extends UnknownRecord>(\n\tid: string | undefined,\n\ttype: RecordType<R, any>\n): asserts id is IdOf<R> {\n\tif (!id || !type.isId(id)) {\n\t\tthrow new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`)\n\t}\n}\n"],
4
+ "sourcesContent": ["import { Expand, objectMapEntries, structuredClone, uniqueId } from '@tldraw/utils'\nimport { IdOf, UnknownRecord } from './BaseRecord'\nimport { StoreValidator } from './Store'\n\n/**\n * Utility type that extracts the record type from a RecordType instance.\n *\n * @example\n * ```ts\n * const Book = createRecordType<BookRecord>('book', { scope: 'document' })\n * type BookFromType = RecordTypeRecord<typeof Book> // BookRecord\n * ```\n *\n * @public\n */\nexport type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['create']>\n\n/**\n * Defines the scope of the record\n *\n * session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.\n * document: The record is persisted and synced. It is available to all store instances.\n * presence: The record belongs to a single instance of the store. It may be synced to other instances, but other instances should not make changes to it. It should not be persisted.\n *\n * @public\n * */\nexport type RecordScope = 'session' | 'document' | 'presence'\n\n/**\n * A record type is a type that can be stored in a record store. It is created with\n * `createRecordType`.\n *\n * @public\n */\nexport class RecordType<\n\tR extends UnknownRecord,\n\tRequiredProperties extends keyof Omit<R, 'id' | 'typeName'>,\n> {\n\t/**\n\t * Factory function that creates default properties for new records.\n\t * @public\n\t */\n\treadonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>\n\n\t/**\n\t * Validator function used to validate records of this type.\n\t * @public\n\t */\n\treadonly validator: StoreValidator<R>\n\n\t/**\n\t * Optional configuration specifying which record properties are ephemeral.\n\t * Ephemeral properties are not included in snapshots or synchronization.\n\t * @public\n\t */\n\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\n\t/**\n\t * Set of property names that are marked as ephemeral for efficient lookup.\n\t * @public\n\t */\n\treadonly ephemeralKeySet: ReadonlySet<string>\n\n\t/**\n\t * The scope that determines how records of this type are persisted and synchronized.\n\t * @public\n\t */\n\treadonly scope: RecordScope\n\n\t/**\n\t * Creates a new RecordType instance.\n\t *\n\t * typeName - The unique type name for records created by this RecordType\n\t * config - Configuration object for the RecordType\n\t * - createDefaultProperties - Function that returns default properties for new records\n\t * - validator - Optional validator function for record validation\n\t * - scope - Optional scope determining persistence behavior (defaults to 'document')\n\t * - ephemeralKeys - Optional mapping of property names to ephemeral status\n\t * @public\n\t */\n\tconstructor(\n\t\t/**\n\t\t * The unique type associated with this record.\n\t\t *\n\t\t * @public\n\t\t * @readonly\n\t\t */\n\t\tpublic readonly typeName: R['typeName'],\n\t\tconfig: {\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly createDefaultProperties: () => Exclude<\n\t\t\t\tOmit<R, 'id' | 'typeName'>,\n\t\t\t\tRequiredProperties\n\t\t\t>\n\t\t\treadonly validator?: StoreValidator<R>\n\t\t\treadonly scope?: RecordScope\n\t\t\treadonly ephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t\t}\n\t) {\n\t\tthis.createDefaultProperties = config.createDefaultProperties\n\t\tthis.validator = config.validator ?? { validate: (r: unknown) => r as R }\n\t\tthis.scope = config.scope ?? 'document'\n\t\tthis.ephemeralKeys = config.ephemeralKeys\n\n\t\tconst ephemeralKeySet = new Set<string>()\n\t\tif (config.ephemeralKeys) {\n\t\t\tfor (const [key, isEphemeral] of objectMapEntries(config.ephemeralKeys)) {\n\t\t\t\tif (isEphemeral) ephemeralKeySet.add(key as string)\n\t\t\t}\n\t\t}\n\t\tthis.ephemeralKeySet = ephemeralKeySet\n\t}\n\n\t/**\n\t * Creates a new record of this type with the given properties.\n\t *\n\t * Properties are merged with default properties from the RecordType configuration.\n\t * If no id is provided, a unique id will be generated automatically.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = Book.create({\n\t * title: 'The Great Gatsby',\n\t * author: 'F. Scott Fitzgerald'\n\t * })\n\t * // Result: { id: 'book:abc123', typeName: 'book', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', inStock: true }\n\t * ```\n\t *\n\t * @param properties - The properties for the new record, including both required and optional fields\n\t * @returns The newly created record with generated id and typeName\n\t * @public\n\t */\n\tcreate(\n\t\tproperties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>\n\t): R {\n\t\tconst result = {\n\t\t\t...this.createDefaultProperties(),\n\t\t\tid: 'id' in properties ? properties.id : this.createId(),\n\t\t} as any\n\n\t\tfor (const [k, v] of Object.entries(properties)) {\n\t\t\tif (v !== undefined) {\n\t\t\t\tresult[k] = v\n\t\t\t}\n\t\t}\n\n\t\tresult.typeName = this.typeName\n\n\t\treturn result as R\n\t}\n\n\t/**\n\t * Creates a deep copy of an existing record with a new unique id.\n\t *\n\t * This method performs a deep clone of all properties while generating a fresh id,\n\t * making it useful for duplicating records without id conflicts.\n\t *\n\t * @example\n\t * ```ts\n\t * const originalBook = Book.create({ title: '1984', author: 'George Orwell' })\n\t * const duplicatedBook = Book.clone(originalBook)\n\t * // duplicatedBook has same properties but different id\n\t * ```\n\t *\n\t * @param record - The record to clone\n\t * @returns A new record with the same properties but a different id\n\t * @public\n\t */\n\tclone(record: R): R {\n\t\treturn { ...structuredClone(record), id: this.createId() }\n\t}\n\n\t/**\n\t * Create a new ID for this record type.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const id = recordType.createId()\n\t * ```\n\t *\n\t * @returns The new ID.\n\t * @public\n\t */\n\tcreateId(customUniquePart?: string): IdOf<R> {\n\t\treturn (this.typeName + ':' + (customUniquePart ?? uniqueId())) as IdOf<R>\n\t}\n\n\t/**\n\t * Extracts the unique identifier part from a full record id.\n\t *\n\t * Record ids have the format `typeName:uniquePart`. This method returns just the unique part.\n\t *\n\t * @example\n\t * ```ts\n\t * const bookId = Book.createId() // 'book:abc123'\n\t * const uniquePart = Book.parseId(bookId) // 'abc123'\n\t * ```\n\t *\n\t * @param id - The full record id to parse\n\t * @returns The unique identifier portion after the colon\n\t * @throws Error if the id is not valid for this record type\n\t * @public\n\t */\n\tparseId(id: IdOf<R>): string {\n\t\tif (!this.isId(id)) {\n\t\t\tthrow new Error(`ID \"${id}\" is not a valid ID for type \"${this.typeName}\"`)\n\t\t}\n\n\t\treturn id.slice(this.typeName.length + 1)\n\t}\n\n\t/**\n\t * Type guard that checks whether a record belongs to this RecordType.\n\t *\n\t * This method performs a runtime check by comparing the record's typeName\n\t * against this RecordType's typeName.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isInstance(someRecord)) {\n\t * // someRecord is now typed as a book record\n\t * console.log(someRecord.title)\n\t * }\n\t * ```\n\t *\n\t * @param record - The record to check, may be undefined\n\t * @returns True if the record is an instance of this record type\n\t * @public\n\t */\n\tisInstance(record?: UnknownRecord): record is R {\n\t\treturn record?.typeName === this.typeName\n\t}\n\n\t/**\n\t * Type guard that checks whether an id string belongs to this RecordType.\n\t *\n\t * Validates that the id starts with this RecordType's typeName followed by a colon.\n\t * This is more efficient than parsing the full id when you only need to verify the type.\n\t *\n\t * @example\n\t * ```ts\n\t * if (Book.isId(someId)) {\n\t * // someId is now typed as IdOf<BookRecord>\n\t * const book = store.get(someId)\n\t * }\n\t * ```\n\t *\n\t * @param id - The id string to check, may be undefined\n\t * @returns True if the id belongs to this record type\n\t * @public\n\t */\n\tisId(id?: string): id is IdOf<R> {\n\t\tif (!id) return false\n\t\tfor (let i = 0; i < this.typeName.length; i++) {\n\t\t\tif (id[i] !== this.typeName[i]) return false\n\t\t}\n\n\t\treturn id[this.typeName.length] === ':'\n\t}\n\n\t/**\n\t * Create a new RecordType that has the same type name as this RecordType and includes the given\n\t * default properties.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * const authorType = createRecordType('author', () => ({ living: true }))\n\t * const deadAuthorType = authorType.withDefaultProperties({ living: false })\n\t * ```\n\t *\n\t * @param createDefaultProperties - A function that returns the default properties of the new RecordType.\n\t * @returns The new RecordType.\n\t */\n\twithDefaultProperties<DefaultProps extends Omit<Partial<R>, 'typeName' | 'id'>>(\n\t\tcreateDefaultProperties: () => DefaultProps\n\t): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>> {\n\t\treturn new RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>(this.typeName, {\n\t\t\tcreateDefaultProperties: createDefaultProperties as any,\n\t\t\tvalidator: this.validator,\n\t\t\tscope: this.scope,\n\t\t\tephemeralKeys: this.ephemeralKeys,\n\t\t})\n\t}\n\n\t/**\n\t * Validates a record against this RecordType's validator and returns it with proper typing.\n\t *\n\t * This method runs the configured validator function and throws an error if validation fails.\n\t * If a previous version of the record is provided, it may use optimized validation.\n\t *\n\t * @example\n\t * ```ts\n\t * try {\n\t * const validBook = Book.validate(untrustedData)\n\t * // validBook is now properly typed and validated\n\t * } catch (error) {\n\t * console.log('Validation failed:', error.message)\n\t * }\n\t * ```\n\t *\n\t * @param record - The unknown record data to validate\n\t * @param recordBefore - Optional previous version for optimized validation\n\t * @returns The validated and properly typed record\n\t * @throws Error if validation fails\n\t * @public\n\t */\n\tvalidate(record: unknown, recordBefore?: R): R {\n\t\tif (recordBefore && this.validator.validateUsingKnownGoodVersion) {\n\t\t\treturn this.validator.validateUsingKnownGoodVersion(recordBefore, record)\n\t\t}\n\t\treturn this.validator.validate(record)\n\t}\n}\n\n/**\n * Creates a new RecordType with the specified configuration.\n *\n * This factory function creates a RecordType that can be used to create, validate, and manage\n * records of a specific type within a store. The resulting RecordType can be extended with\n * default properties using the withDefaultProperties method.\n *\n * @example\n * ```ts\n * interface BookRecord extends BaseRecord<'book', RecordId<BookRecord>> {\n * title: string\n * author: string\n * inStock: boolean\n * }\n *\n * const Book = createRecordType<BookRecord>('book', {\n * scope: 'document',\n * validator: bookValidator\n * })\n * ```\n *\n * @param typeName - The unique type name for this record type\n * @param config - Configuration object containing validator, scope, and ephemeral keys\n * @returns A new RecordType instance for creating and managing records\n * @public\n */\nexport function createRecordType<R extends UnknownRecord>(\n\ttypeName: R['typeName'],\n\tconfig: {\n\t\tvalidator?: StoreValidator<R>\n\t\tscope: RecordScope\n\t\tephemeralKeys?: { readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean }\n\t}\n): RecordType<R, keyof Omit<R, 'id' | 'typeName'>> {\n\treturn new RecordType<R, keyof Omit<R, 'id' | 'typeName'>>(typeName, {\n\t\tcreateDefaultProperties: () => ({}) as any,\n\t\tvalidator: config.validator,\n\t\tscope: config.scope,\n\t\tephemeralKeys: config.ephemeralKeys,\n\t})\n}\n\n/**\n * Assert whether an id correspond to a record type.\n *\n * @example\n *\n * ```ts\n * assertIdType(myId, \"shape\")\n * ```\n *\n * @param id - The id to check.\n * @param type - The type of the record.\n * @public\n */\nexport function assertIdType<R extends UnknownRecord>(\n\tid: string | undefined,\n\ttype: RecordType<R, any>\n): asserts id is IdOf<R> {\n\tif (!id || !type.isId(id)) {\n\t\tthrow new Error(`string ${JSON.stringify(id)} is not a valid ${type.typeName} id`)\n\t}\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/migrate.ts"],
4
- "sourcesContent": ["import { assert, objectMapEntries } from '@tldraw/utils'\nimport { UnknownRecord } from './BaseRecord'\nimport { SerializedStore } from './Store'\nimport { SerializedSchema } from './StoreSchema'\n\nfunction squashDependsOn(sequence: Array<Migration | StandaloneDependsOn>): Migration[] {\n\tconst result: Migration[] = []\n\tfor (let i = sequence.length - 1; i >= 0; i--) {\n\t\tconst elem = sequence[i]\n\t\tif (!('id' in elem)) {\n\t\t\tconst dependsOn = elem.dependsOn\n\t\t\tconst prev = result[0]\n\t\t\tif (prev) {\n\t\t\t\tresult[0] = {\n\t\t\t\t\t...prev,\n\t\t\t\t\tdependsOn: dependsOn.concat(prev.dependsOn ?? []),\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tresult.unshift(elem)\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Creates a migration sequence that defines how to transform data as your schema evolves.\n *\n * A migration sequence contains a series of migrations that are applied in order to transform\n * data from older versions to newer versions. Each migration is identified by a unique ID\n * and can operate at either the record level (transforming individual records) or store level\n * (transforming the entire store structure).\n *\n * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.\n * @param options - Configuration for the migration sequence\n * - sequenceId - Unique identifier for this migration sequence (e.g., 'com.myapp.book')\n * - sequence - Array of migrations or dependency declarations to include in the sequence\n * - retroactive - Whether migrations should apply to snapshots created before this sequence was added (defaults to true)\n * @returns A validated migration sequence that can be included in a store schema\n * @example\n * ```ts\n * const bookMigrations = createMigrationSequence({\n * sequenceId: 'com.myapp.book',\n * sequence: [\n * {\n * id: 'com.myapp.book/1',\n * scope: 'record',\n * up: (record) => ({ ...record, newField: 'default' })\n * }\n * ]\n * })\n * ```\n * @public\n */\nexport function createMigrationSequence({\n\tsequence,\n\tsequenceId,\n\tretroactive = true,\n}: {\n\tsequenceId: string\n\tretroactive?: boolean\n\tsequence: Array<Migration | StandaloneDependsOn>\n}): MigrationSequence {\n\tconst migrations: MigrationSequence = {\n\t\tsequenceId,\n\t\tretroactive,\n\t\tsequence: squashDependsOn(sequence),\n\t}\n\tvalidateMigrations(migrations)\n\treturn migrations\n}\n\n/**\n * Creates a named set of migration IDs from version numbers and a sequence ID.\n *\n * This utility function helps generate properly formatted migration IDs that follow\n * the required `sequenceId/version` pattern. It takes a sequence ID and a record\n * of named versions, returning migration IDs that can be used in migration definitions.\n *\n * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.\n * @param sequenceId - The sequence identifier (e.g., 'com.myapp.book')\n * @param versions - Record mapping version names to numbers\n * @returns Record mapping version names to properly formatted migration IDs\n * @example\n * ```ts\n * const migrationIds = createMigrationIds('com.myapp.book', {\n * addGenre: 1,\n * addPublisher: 2,\n * removeOldField: 3\n * })\n * // Result: {\n * // addGenre: 'com.myapp.book/1',\n * // addPublisher: 'com.myapp.book/2',\n * // removeOldField: 'com.myapp.book/3'\n * // }\n * ```\n * @public\n */\nexport function createMigrationIds<\n\tconst ID extends string,\n\tconst Versions extends Record<string, number>,\n>(sequenceId: ID, versions: Versions): { [K in keyof Versions]: `${ID}/${Versions[K]}` } {\n\treturn Object.fromEntries(\n\t\tobjectMapEntries(versions).map(([key, version]) => [key, `${sequenceId}/${version}`] as const)\n\t) as any\n}\n\n/**\n * Creates a migration sequence specifically for record-level migrations.\n *\n * This is a convenience function that creates a migration sequence where all migrations\n * operate at the record scope and are automatically filtered to apply only to records\n * of a specific type. Each migration in the sequence will be enhanced with the record\n * scope and appropriate filtering logic.\n * @param opts - Configuration for the record migration sequence\n * - recordType - The record type name these migrations should apply to\n * - filter - Optional additional filter function to determine which records to migrate\n * - retroactive - Whether migrations should apply to snapshots created before this sequence was added\n * - sequenceId - Unique identifier for this migration sequence\n * - sequence - Array of record migration definitions (scope will be added automatically)\n * @returns A migration sequence configured for record-level operations\n * @internal\n */\nexport function createRecordMigrationSequence(opts: {\n\trecordType: string\n\tfilter?(record: UnknownRecord): boolean\n\tretroactive?: boolean\n\tsequenceId: string\n\tsequence: Omit<Extract<Migration, { scope: 'record' }>, 'scope'>[]\n}): MigrationSequence {\n\tconst sequenceId = opts.sequenceId\n\treturn createMigrationSequence({\n\t\tsequenceId,\n\t\tretroactive: opts.retroactive ?? true,\n\t\tsequence: opts.sequence.map((m) =>\n\t\t\t'id' in m\n\t\t\t\t? {\n\t\t\t\t\t\t...m,\n\t\t\t\t\t\tscope: 'record',\n\t\t\t\t\t\tfilter: (r: UnknownRecord) =>\n\t\t\t\t\t\t\tr.typeName === opts.recordType &&\n\t\t\t\t\t\t\t(m.filter?.(r) ?? true) &&\n\t\t\t\t\t\t\t(opts.filter?.(r) ?? true),\n\t\t\t\t\t}\n\t\t\t\t: m\n\t\t),\n\t})\n}\n\n/**\n * Legacy migration interface for backward compatibility.\n *\n * This interface represents the old migration format that included both `up` and `down`\n * transformation functions. While still supported, new code should use the `Migration`\n * type which provides more flexibility and better integration with the current system.\n * @public\n */\nexport interface LegacyMigration<Before = any, After = any> {\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tup: (oldState: Before) => After\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tdown: (newState: After) => Before\n}\n\n/**\n * Unique identifier for a migration in the format `sequenceId/version`.\n *\n * Migration IDs follow a specific pattern where the sequence ID identifies the migration\n * sequence and the version number indicates the order within that sequence. For example:\n * 'com.myapp.book/1', 'com.myapp.book/2', etc.\n * @public\n */\nexport type MigrationId = `${string}/${number}`\n\n/**\n * Declares dependencies for migrations without being a migration itself.\n *\n * This interface allows you to specify that future migrations in a sequence depend on\n * migrations from other sequences, without defining an actual migration transformation.\n * It's used to establish cross-sequence dependencies in the migration graph.\n * @public\n */\nexport interface StandaloneDependsOn {\n\treadonly dependsOn: readonly MigrationId[]\n}\n\n/**\n * Defines a single migration that transforms data from one schema version to another.\n *\n * A migration can operate at two different scopes:\n * - `record`: Transforms individual records, with optional filtering to target specific records\n * - `store`: Transforms the entire serialized store structure\n *\n * Each migration has a unique ID and can declare dependencies on other migrations that must\n * be applied first. The `up` function performs the forward transformation, while the optional\n * `down` function can reverse the migration if needed.\n * @public\n */\nexport type Migration = {\n\treadonly id: MigrationId\n\treadonly dependsOn?: readonly MigrationId[] | undefined\n} & (\n\t| {\n\t\t\treadonly scope: 'record'\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly filter?: (record: UnknownRecord) => boolean\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly up: (oldState: UnknownRecord) => void | UnknownRecord\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly down?: (newState: UnknownRecord) => void | UnknownRecord\n\t }\n\t| {\n\t\t\treadonly scope: 'store'\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly up: (\n\t\t\t\toldState: SerializedStore<UnknownRecord>\n\t\t\t) => void | SerializedStore<UnknownRecord>\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly down?: (\n\t\t\t\tnewState: SerializedStore<UnknownRecord>\n\t\t\t) => void | SerializedStore<UnknownRecord>\n\t }\n\t| {\n\t\t\treadonly scope: 'storage'\n\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\treadonly up: (storage: SynchronousRecordStorage<UnknownRecord>) => void\n\t\t\treadonly down?: never\n\t }\n)\n\n/**\n * Abstraction over the store that can be used to perform migrations.\n * @public\n */\nexport interface SynchronousRecordStorage<R extends UnknownRecord> {\n\tget(id: string): R | undefined\n\tset(id: string, record: R): void\n\tdelete(id: string): void\n\tkeys(): Iterable<string>\n\tvalues(): Iterable<R>\n\tentries(): Iterable<[string, R]>\n}\n\n/**\n * Abstraction over the storage that can be used to perform migrations.\n * @public\n */\nexport interface SynchronousStorage<R extends UnknownRecord> extends SynchronousRecordStorage<R> {\n\tgetSchema(): SerializedSchema\n\tsetSchema(schema: SerializedSchema): void\n}\n\n/**\n * Base interface for legacy migration information.\n *\n * Contains the basic structure used by the legacy migration system, including version\n * range information and the migration functions indexed by version number. This is\n * maintained for backward compatibility with older migration definitions.\n * @public\n */\nexport interface LegacyBaseMigrationsInfo {\n\tfirstVersion: number\n\tcurrentVersion: number\n\tmigrators: { [version: number]: LegacyMigration }\n}\n\n/**\n * Legacy migration configuration with support for sub-type migrations.\n *\n * This interface extends the base legacy migration info to support migrations that\n * vary based on a sub-type key within records. This allows different migration paths\n * for different variants of the same record type, which was useful in older migration\n * systems but is now handled more elegantly by the current Migration system.\n * @public\n */\nexport interface LegacyMigrations extends LegacyBaseMigrationsInfo {\n\tsubTypeKey?: string\n\tsubTypeMigrations?: Record<string, LegacyBaseMigrationsInfo>\n}\n\n/**\n * A complete sequence of migrations that can be applied to transform data.\n *\n * A migration sequence represents a series of ordered migrations that belong together,\n * typically for a specific part of your schema. The sequence includes metadata about\n * whether it should be applied retroactively to existing data and contains the actual\n * migration definitions in execution order.\n * @public\n */\nexport interface MigrationSequence {\n\tsequenceId: string\n\t/**\n\t * retroactive should be true if the migrations should be applied to snapshots that were created before\n\t * this migration sequence was added to the schema.\n\t *\n\t * In general:\n\t *\n\t * - retroactive should be true when app developers create their own new migration sequences.\n\t * - retroactive should be false when library developers ship a migration sequence. When you install a library for the first time, any migrations that were added in the library before that point should generally _not_ be applied to your existing data.\n\t */\n\tretroactive: boolean\n\tsequence: Migration[]\n}\n\n/**\n * Sorts migrations using a distance-minimizing topological sort.\n *\n * This function respects two types of dependencies:\n * 1. Implicit sequence dependencies (foo/1 must come before foo/2)\n * 2. Explicit dependencies via `dependsOn` property\n *\n * The algorithm minimizes the total distance between migrations and their explicit\n * dependencies in the final ordering, while maintaining topological correctness.\n * This means when migration A depends on migration B, A will be scheduled as close\n * as possible to B (while respecting all constraints).\n *\n * Implementation uses Kahn's algorithm with priority scoring:\n * - Builds dependency graph and calculates in-degrees\n * - Uses priority queue that prioritizes migrations which unblock explicit dependencies\n * - Processes migrations in urgency order while maintaining topological constraints\n * - Detects cycles by ensuring all migrations are processed\n *\n * @param migrations - Array of migrations to sort\n * @returns Sorted array of migrations in execution order\n * @throws Assertion error if circular dependencies are detected\n * @example\n * ```ts\n * const sorted = sortMigrations([\n * { id: 'app/2', scope: 'record', up: (r) => r },\n * { id: 'app/1', scope: 'record', up: (r) => r },\n * { id: 'lib/1', scope: 'record', up: (r) => r, dependsOn: ['app/1'] }\n * ])\n * // Result: [app/1, app/2, lib/1] (respects both sequence and explicit deps)\n * ```\n * @public\n */\nexport function sortMigrations(migrations: Migration[]): Migration[] {\n\tif (migrations.length === 0) return []\n\n\t// Build dependency graph and calculate in-degrees\n\tconst byId = new Map(migrations.map((m) => [m.id, m]))\n\tconst dependents = new Map<MigrationId, Set<MigrationId>>() // who depends on this\n\tconst inDegree = new Map<MigrationId, number>()\n\tconst explicitDeps = new Map<MigrationId, Set<MigrationId>>() // explicit dependsOn relationships\n\n\t// Initialize\n\tfor (const m of migrations) {\n\t\tinDegree.set(m.id, 0)\n\t\tdependents.set(m.id, new Set())\n\t\texplicitDeps.set(m.id, new Set())\n\t}\n\n\t// Add implicit sequence dependencies and explicit dependencies\n\tfor (const m of migrations) {\n\t\tconst { version, sequenceId } = parseMigrationId(m.id)\n\n\t\t// Implicit dependency on previous in sequence\n\t\tconst prevId = `${sequenceId}/${version - 1}` as MigrationId\n\t\tif (byId.has(prevId)) {\n\t\t\tdependents.get(prevId)!.add(m.id)\n\t\t\tinDegree.set(m.id, inDegree.get(m.id)! + 1)\n\t\t}\n\n\t\t// Explicit dependencies\n\t\tif (m.dependsOn) {\n\t\t\tfor (const depId of m.dependsOn) {\n\t\t\t\tif (byId.has(depId)) {\n\t\t\t\t\tdependents.get(depId)!.add(m.id)\n\t\t\t\t\texplicitDeps.get(m.id)!.add(depId)\n\t\t\t\t\tinDegree.set(m.id, inDegree.get(m.id)! + 1)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Priority queue: migrations ready to process (in-degree 0)\n\tconst ready = migrations.filter((m) => inDegree.get(m.id) === 0)\n\tconst result: Migration[] = []\n\tconst processed = new Set<MigrationId>()\n\n\twhile (ready.length > 0) {\n\t\t// Calculate urgency scores for ready migrations and pick the best one\n\t\tlet bestCandidate: Migration | undefined\n\t\tlet bestCandidateScore = -Infinity\n\n\t\tfor (const m of ready) {\n\t\t\tlet urgencyScore = 0\n\n\t\t\tfor (const depId of dependents.get(m.id) || []) {\n\t\t\t\tif (!processed.has(depId)) {\n\t\t\t\t\t// Priority 1: Count all unprocessed dependents (to break ties)\n\t\t\t\t\turgencyScore += 1\n\n\t\t\t\t\t// Priority 2: If this migration is explicitly depended on by others, boost priority\n\t\t\t\t\tif (explicitDeps.get(depId)!.has(m.id)) {\n\t\t\t\t\t\turgencyScore += 100\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\turgencyScore > bestCandidateScore ||\n\t\t\t\t// Tiebreaker: prefer lower sequence/version\n\t\t\t\t(urgencyScore === bestCandidateScore && m.id.localeCompare(bestCandidate?.id ?? '') < 0)\n\t\t\t) {\n\t\t\t\tbestCandidate = m\n\t\t\t\tbestCandidateScore = urgencyScore\n\t\t\t}\n\t\t}\n\n\t\tconst nextMigration = bestCandidate!\n\t\tready.splice(ready.indexOf(nextMigration), 1)\n\n\t\t// Cycle detection - if we have processed everything and still have items left, there's a cycle\n\t\t// This is handled by Kahn's algorithm naturally - if we finish with items unprocessed, there's a cycle\n\n\t\t// Process this migration\n\t\tresult.push(nextMigration)\n\t\tprocessed.add(nextMigration.id)\n\n\t\t// Update in-degrees and add newly ready migrations\n\t\tfor (const depId of dependents.get(nextMigration.id) || []) {\n\t\t\tif (!processed.has(depId)) {\n\t\t\t\tinDegree.set(depId, inDegree.get(depId)! - 1)\n\t\t\t\tif (inDegree.get(depId) === 0) {\n\t\t\t\t\tready.push(byId.get(depId)!)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check for cycles - if we didn't process all migrations, there's a cycle\n\tif (result.length !== migrations.length) {\n\t\tconst unprocessed = migrations.filter((m) => !processed.has(m.id))\n\t\tassert(false, `Circular dependency in migrations: ${unprocessed[0].id}`)\n\t}\n\n\treturn result\n}\n\n/**\n * Parses a migration ID to extract the sequence ID and version number.\n *\n * Migration IDs follow the format `sequenceId/version`, and this function splits\n * them into their component parts. This is used internally for sorting migrations\n * and understanding their relationships.\n * @param id - The migration ID to parse\n * @returns Object containing the sequence ID and numeric version\n * @example\n * ```ts\n * const { sequenceId, version } = parseMigrationId('com.myapp.book/5')\n * // sequenceId: 'com.myapp.book', version: 5\n * ```\n * @internal\n */\nexport function parseMigrationId(id: MigrationId): { sequenceId: string; version: number } {\n\tconst [sequenceId, version] = id.split('/')\n\treturn { sequenceId, version: parseInt(version) }\n}\n\nfunction validateMigrationId(id: string, expectedSequenceId?: string) {\n\tif (expectedSequenceId) {\n\t\tassert(\n\t\t\tid.startsWith(expectedSequenceId + '/'),\n\t\t\t`Every migration in sequence '${expectedSequenceId}' must have an id starting with '${expectedSequenceId}/'. Got invalid id: '${id}'`\n\t\t)\n\t}\n\n\tassert(id.match(/^(.*?)\\/(0|[1-9]\\d*)$/), `Invalid migration id: '${id}'`)\n}\n\n/**\n * Validates that a migration sequence is correctly structured.\n *\n * Performs several validation checks to ensure the migration sequence is valid:\n * - Sequence ID doesn't contain invalid characters\n * - All migration IDs belong to the expected sequence\n * - Migration versions start at 1 and increment by 1\n * - Migration IDs follow the correct format\n * @param migrations - The migration sequence to validate\n * @throws Assertion error if any validation checks fail\n * @example\n * ```ts\n * const sequence = createMigrationSequence({\n * sequenceId: 'com.myapp.book',\n * sequence: [{ id: 'com.myapp.book/1', scope: 'record', up: (r) => r }]\n * })\n * validateMigrations(sequence) // Passes validation\n * ```\n * @public\n */\nexport function validateMigrations(migrations: MigrationSequence) {\n\tassert(\n\t\t!migrations.sequenceId.includes('/'),\n\t\t`sequenceId cannot contain a '/', got ${migrations.sequenceId}`\n\t)\n\tassert(migrations.sequenceId.length, 'sequenceId must be a non-empty string')\n\n\tif (migrations.sequence.length === 0) {\n\t\treturn\n\t}\n\n\tvalidateMigrationId(migrations.sequence[0].id, migrations.sequenceId)\n\tlet n = parseMigrationId(migrations.sequence[0].id).version\n\tassert(\n\t\tn === 1,\n\t\t`Expected the first migrationId to be '${migrations.sequenceId}/1' but got '${migrations.sequence[0].id}'`\n\t)\n\tfor (let i = 1; i < migrations.sequence.length; i++) {\n\t\tconst id = migrations.sequence[i].id\n\t\tvalidateMigrationId(id, migrations.sequenceId)\n\t\tconst m = parseMigrationId(id).version\n\t\tassert(\n\t\t\tm === n + 1,\n\t\t\t`Migration id numbers must increase in increments of 1, expected ${migrations.sequenceId}/${n + 1} but got '${migrations.sequence[i].id}'`\n\t\t)\n\t\tn = m\n\t}\n}\n\n/**\n * Result type returned by migration operations.\n *\n * Migration operations can either succeed and return the transformed value,\n * or fail with a specific reason. This discriminated union type allows for\n * safe handling of both success and error cases when applying migrations.\n * @public\n */\nexport type MigrationResult<T> =\n\t| { type: 'success'; value: T }\n\t| { type: 'error'; reason: MigrationFailureReason }\n\n/** @public */\nexport const MigrationFailureReason = {\n\tIncompatibleSubtype: 'incompatible-subtype',\n\tUnknownType: 'unknown-type',\n\tTargetVersionTooNew: 'target-version-too-new',\n\tTargetVersionTooOld: 'target-version-too-old',\n\tMigrationError: 'migration-error',\n\tUnrecognizedSubtype: 'unrecognized-subtype',\n} as const\n\n/** @public */\nexport type MigrationFailureReason =\n\t(typeof MigrationFailureReason)[keyof typeof MigrationFailureReason]\n\n// This is the magic part for backward compat:\n/** @public */\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport declare namespace MigrationFailureReason {\n\texport type IncompatibleSubtype = typeof MigrationFailureReason.IncompatibleSubtype\n\texport type UnknownType = typeof MigrationFailureReason.UnknownType\n\texport type TargetVersionTooNew = typeof MigrationFailureReason.TargetVersionTooNew\n\texport type TargetVersionTooOld = typeof MigrationFailureReason.TargetVersionTooOld\n\texport type MigrationError = typeof MigrationFailureReason.MigrationError\n\texport type UnrecognizedSubtype = typeof MigrationFailureReason.UnrecognizedSubtype\n}\n"],
4
+ "sourcesContent": ["import { assert, objectMapEntries } from '@tldraw/utils'\nimport { UnknownRecord } from './BaseRecord'\nimport { SerializedStore } from './Store'\nimport { SerializedSchema } from './StoreSchema'\n\nfunction squashDependsOn(sequence: Array<Migration | StandaloneDependsOn>): Migration[] {\n\tconst result: Migration[] = []\n\tfor (let i = sequence.length - 1; i >= 0; i--) {\n\t\tconst elem = sequence[i]\n\t\tif (!('id' in elem)) {\n\t\t\tconst dependsOn = elem.dependsOn\n\t\t\tconst prev = result[0]\n\t\t\tif (prev) {\n\t\t\t\tresult[0] = {\n\t\t\t\t\t...prev,\n\t\t\t\t\tdependsOn: dependsOn.concat(prev.dependsOn ?? []),\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tresult.unshift(elem)\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Creates a migration sequence that defines how to transform data as your schema evolves.\n *\n * A migration sequence contains a series of migrations that are applied in order to transform\n * data from older versions to newer versions. Each migration is identified by a unique ID\n * and can operate at either the record level (transforming individual records) or store level\n * (transforming the entire store structure).\n *\n * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.\n * @param options - Configuration for the migration sequence\n * - sequenceId - Unique identifier for this migration sequence (e.g., 'com.myapp.book')\n * - sequence - Array of migrations or dependency declarations to include in the sequence\n * - retroactive - Whether migrations should apply to snapshots created before this sequence was added (defaults to true)\n * @returns A validated migration sequence that can be included in a store schema\n * @example\n * ```ts\n * const bookMigrations = createMigrationSequence({\n * sequenceId: 'com.myapp.book',\n * sequence: [\n * {\n * id: 'com.myapp.book/1',\n * scope: 'record',\n * up: (record) => ({ ...record, newField: 'default' })\n * }\n * ]\n * })\n * ```\n * @public\n */\nexport function createMigrationSequence({\n\tsequence,\n\tsequenceId,\n\tretroactive = true,\n}: {\n\tsequenceId: string\n\tretroactive?: boolean\n\tsequence: Array<Migration | StandaloneDependsOn>\n}): MigrationSequence {\n\tconst migrations: MigrationSequence = {\n\t\tsequenceId,\n\t\tretroactive,\n\t\tsequence: squashDependsOn(sequence),\n\t}\n\tvalidateMigrations(migrations)\n\treturn migrations\n}\n\n/**\n * Creates a named set of migration IDs from version numbers and a sequence ID.\n *\n * This utility function helps generate properly formatted migration IDs that follow\n * the required `sequenceId/version` pattern. It takes a sequence ID and a record\n * of named versions, returning migration IDs that can be used in migration definitions.\n *\n * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.\n * @param sequenceId - The sequence identifier (e.g., 'com.myapp.book')\n * @param versions - Record mapping version names to numbers\n * @returns Record mapping version names to properly formatted migration IDs\n * @example\n * ```ts\n * const migrationIds = createMigrationIds('com.myapp.book', {\n * addGenre: 1,\n * addPublisher: 2,\n * removeOldField: 3\n * })\n * // Result: {\n * // addGenre: 'com.myapp.book/1',\n * // addPublisher: 'com.myapp.book/2',\n * // removeOldField: 'com.myapp.book/3'\n * // }\n * ```\n * @public\n */\nexport function createMigrationIds<\n\tconst ID extends string,\n\tconst Versions extends Record<string, number>,\n>(sequenceId: ID, versions: Versions): { [K in keyof Versions]: `${ID}/${Versions[K]}` } {\n\treturn Object.fromEntries(\n\t\tobjectMapEntries(versions).map(([key, version]) => [key, `${sequenceId}/${version}`] as const)\n\t) as any\n}\n\n/**\n * Creates a migration sequence specifically for record-level migrations.\n *\n * This is a convenience function that creates a migration sequence where all migrations\n * operate at the record scope and are automatically filtered to apply only to records\n * of a specific type. Each migration in the sequence will be enhanced with the record\n * scope and appropriate filtering logic.\n * @param opts - Configuration for the record migration sequence\n * - recordType - The record type name these migrations should apply to\n * - filter - Optional additional filter function to determine which records to migrate\n * - retroactive - Whether migrations should apply to snapshots created before this sequence was added\n * - sequenceId - Unique identifier for this migration sequence\n * - sequence - Array of record migration definitions (scope will be added automatically)\n * @returns A migration sequence configured for record-level operations\n * @internal\n */\nexport function createRecordMigrationSequence(opts: {\n\trecordType: string\n\tfilter?(record: UnknownRecord): boolean\n\tretroactive?: boolean\n\tsequenceId: string\n\tsequence: Omit<Extract<Migration, { scope: 'record' }>, 'scope'>[]\n}): MigrationSequence {\n\tconst sequenceId = opts.sequenceId\n\treturn createMigrationSequence({\n\t\tsequenceId,\n\t\tretroactive: opts.retroactive ?? true,\n\t\tsequence: opts.sequence.map((m) =>\n\t\t\t'id' in m\n\t\t\t\t? {\n\t\t\t\t\t\t...m,\n\t\t\t\t\t\tscope: 'record',\n\t\t\t\t\t\tfilter: (r: UnknownRecord) =>\n\t\t\t\t\t\t\tr.typeName === opts.recordType &&\n\t\t\t\t\t\t\t(m.filter?.(r) ?? true) &&\n\t\t\t\t\t\t\t(opts.filter?.(r) ?? true),\n\t\t\t\t\t}\n\t\t\t\t: m\n\t\t),\n\t})\n}\n\n/**\n * Legacy migration interface for backward compatibility.\n *\n * This interface represents the old migration format that included both `up` and `down`\n * transformation functions. While still supported, new code should use the `Migration`\n * type which provides more flexibility and better integration with the current system.\n * @public\n */\nexport interface LegacyMigration<Before = any, After = any> {\n\t// eslint-disable-next-line tldraw/method-signature-style\n\tup: (oldState: Before) => After\n\t// eslint-disable-next-line tldraw/method-signature-style\n\tdown: (newState: After) => Before\n}\n\n/**\n * Unique identifier for a migration in the format `sequenceId/version`.\n *\n * Migration IDs follow a specific pattern where the sequence ID identifies the migration\n * sequence and the version number indicates the order within that sequence. For example:\n * 'com.myapp.book/1', 'com.myapp.book/2', etc.\n * @public\n */\nexport type MigrationId = `${string}/${number}`\n\n/**\n * Declares dependencies for migrations without being a migration itself.\n *\n * This interface allows you to specify that future migrations in a sequence depend on\n * migrations from other sequences, without defining an actual migration transformation.\n * It's used to establish cross-sequence dependencies in the migration graph.\n * @public\n */\nexport interface StandaloneDependsOn {\n\treadonly dependsOn: readonly MigrationId[]\n}\n\n/**\n * Defines a single migration that transforms data from one schema version to another.\n *\n * A migration can operate at two different scopes:\n * - `record`: Transforms individual records, with optional filtering to target specific records\n * - `store`: Transforms the entire serialized store structure\n *\n * Each migration has a unique ID and can declare dependencies on other migrations that must\n * be applied first. The `up` function performs the forward transformation, while the optional\n * `down` function can reverse the migration if needed.\n * @public\n */\nexport type Migration = {\n\treadonly id: MigrationId\n\treadonly dependsOn?: readonly MigrationId[] | undefined\n} & (\n\t| {\n\t\t\treadonly scope: 'record'\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly filter?: (record: UnknownRecord) => boolean\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly up: (oldState: UnknownRecord) => void | UnknownRecord\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly down?: (newState: UnknownRecord) => void | UnknownRecord\n\t }\n\t| {\n\t\t\treadonly scope: 'store'\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly up: (\n\t\t\t\toldState: SerializedStore<UnknownRecord>\n\t\t\t) => void | SerializedStore<UnknownRecord>\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly down?: (\n\t\t\t\tnewState: SerializedStore<UnknownRecord>\n\t\t\t) => void | SerializedStore<UnknownRecord>\n\t }\n\t| {\n\t\t\treadonly scope: 'storage'\n\t\t\t// eslint-disable-next-line tldraw/method-signature-style\n\t\t\treadonly up: (storage: SynchronousRecordStorage<UnknownRecord>) => void\n\t\t\treadonly down?: never\n\t }\n)\n\n/**\n * Abstraction over the store that can be used to perform migrations.\n * @public\n */\nexport interface SynchronousRecordStorage<R extends UnknownRecord> {\n\tget(id: string): R | undefined\n\tset(id: string, record: R): void\n\tdelete(id: string): void\n\tkeys(): Iterable<string>\n\tvalues(): Iterable<R>\n\tentries(): Iterable<[string, R]>\n}\n\n/**\n * Abstraction over the storage that can be used to perform migrations.\n * @public\n */\nexport interface SynchronousStorage<R extends UnknownRecord> extends SynchronousRecordStorage<R> {\n\tgetSchema(): SerializedSchema\n\tsetSchema(schema: SerializedSchema): void\n}\n\n/**\n * Base interface for legacy migration information.\n *\n * Contains the basic structure used by the legacy migration system, including version\n * range information and the migration functions indexed by version number. This is\n * maintained for backward compatibility with older migration definitions.\n * @public\n */\nexport interface LegacyBaseMigrationsInfo {\n\tfirstVersion: number\n\tcurrentVersion: number\n\tmigrators: { [version: number]: LegacyMigration }\n}\n\n/**\n * Legacy migration configuration with support for sub-type migrations.\n *\n * This interface extends the base legacy migration info to support migrations that\n * vary based on a sub-type key within records. This allows different migration paths\n * for different variants of the same record type, which was useful in older migration\n * systems but is now handled more elegantly by the current Migration system.\n * @public\n */\nexport interface LegacyMigrations extends LegacyBaseMigrationsInfo {\n\tsubTypeKey?: string\n\tsubTypeMigrations?: Record<string, LegacyBaseMigrationsInfo>\n}\n\n/**\n * A complete sequence of migrations that can be applied to transform data.\n *\n * A migration sequence represents a series of ordered migrations that belong together,\n * typically for a specific part of your schema. The sequence includes metadata about\n * whether it should be applied retroactively to existing data and contains the actual\n * migration definitions in execution order.\n * @public\n */\nexport interface MigrationSequence {\n\tsequenceId: string\n\t/**\n\t * retroactive should be true if the migrations should be applied to snapshots that were created before\n\t * this migration sequence was added to the schema.\n\t *\n\t * In general:\n\t *\n\t * - retroactive should be true when app developers create their own new migration sequences.\n\t * - retroactive should be false when library developers ship a migration sequence. When you install a library for the first time, any migrations that were added in the library before that point should generally _not_ be applied to your existing data.\n\t */\n\tretroactive: boolean\n\tsequence: Migration[]\n}\n\n/**\n * Sorts migrations using a distance-minimizing topological sort.\n *\n * This function respects two types of dependencies:\n * 1. Implicit sequence dependencies (foo/1 must come before foo/2)\n * 2. Explicit dependencies via `dependsOn` property\n *\n * The algorithm minimizes the total distance between migrations and their explicit\n * dependencies in the final ordering, while maintaining topological correctness.\n * This means when migration A depends on migration B, A will be scheduled as close\n * as possible to B (while respecting all constraints).\n *\n * Implementation uses Kahn's algorithm with priority scoring:\n * - Builds dependency graph and calculates in-degrees\n * - Uses priority queue that prioritizes migrations which unblock explicit dependencies\n * - Processes migrations in urgency order while maintaining topological constraints\n * - Detects cycles by ensuring all migrations are processed\n *\n * @param migrations - Array of migrations to sort\n * @returns Sorted array of migrations in execution order\n * @throws Assertion error if circular dependencies are detected\n * @example\n * ```ts\n * const sorted = sortMigrations([\n * { id: 'app/2', scope: 'record', up: (r) => r },\n * { id: 'app/1', scope: 'record', up: (r) => r },\n * { id: 'lib/1', scope: 'record', up: (r) => r, dependsOn: ['app/1'] }\n * ])\n * // Result: [app/1, app/2, lib/1] (respects both sequence and explicit deps)\n * ```\n * @public\n */\nexport function sortMigrations(migrations: Migration[]): Migration[] {\n\tif (migrations.length === 0) return []\n\n\t// Build dependency graph and calculate in-degrees\n\tconst byId = new Map(migrations.map((m) => [m.id, m]))\n\tconst dependents = new Map<MigrationId, Set<MigrationId>>() // who depends on this\n\tconst inDegree = new Map<MigrationId, number>()\n\tconst explicitDeps = new Map<MigrationId, Set<MigrationId>>() // explicit dependsOn relationships\n\n\t// Initialize\n\tfor (const m of migrations) {\n\t\tinDegree.set(m.id, 0)\n\t\tdependents.set(m.id, new Set())\n\t\texplicitDeps.set(m.id, new Set())\n\t}\n\n\t// Add implicit sequence dependencies and explicit dependencies\n\tfor (const m of migrations) {\n\t\tconst { version, sequenceId } = parseMigrationId(m.id)\n\n\t\t// Implicit dependency on previous in sequence\n\t\tconst prevId = `${sequenceId}/${version - 1}` as MigrationId\n\t\tif (byId.has(prevId)) {\n\t\t\tdependents.get(prevId)!.add(m.id)\n\t\t\tinDegree.set(m.id, inDegree.get(m.id)! + 1)\n\t\t}\n\n\t\t// Explicit dependencies\n\t\tif (m.dependsOn) {\n\t\t\tfor (const depId of m.dependsOn) {\n\t\t\t\tif (byId.has(depId)) {\n\t\t\t\t\tdependents.get(depId)!.add(m.id)\n\t\t\t\t\texplicitDeps.get(m.id)!.add(depId)\n\t\t\t\t\tinDegree.set(m.id, inDegree.get(m.id)! + 1)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Priority queue: migrations ready to process (in-degree 0)\n\tconst ready = migrations.filter((m) => inDegree.get(m.id) === 0)\n\tconst result: Migration[] = []\n\tconst processed = new Set<MigrationId>()\n\n\twhile (ready.length > 0) {\n\t\t// Calculate urgency scores for ready migrations and pick the best one\n\t\tlet bestCandidate: Migration | undefined\n\t\tlet bestCandidateScore = -Infinity\n\n\t\tfor (const m of ready) {\n\t\t\tlet urgencyScore = 0\n\n\t\t\tfor (const depId of dependents.get(m.id) || []) {\n\t\t\t\tif (!processed.has(depId)) {\n\t\t\t\t\t// Priority 1: Count all unprocessed dependents (to break ties)\n\t\t\t\t\turgencyScore += 1\n\n\t\t\t\t\t// Priority 2: If this migration is explicitly depended on by others, boost priority\n\t\t\t\t\tif (explicitDeps.get(depId)!.has(m.id)) {\n\t\t\t\t\t\turgencyScore += 100\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\turgencyScore > bestCandidateScore ||\n\t\t\t\t// Tiebreaker: prefer lower sequence/version\n\t\t\t\t(urgencyScore === bestCandidateScore && m.id.localeCompare(bestCandidate?.id ?? '') < 0)\n\t\t\t) {\n\t\t\t\tbestCandidate = m\n\t\t\t\tbestCandidateScore = urgencyScore\n\t\t\t}\n\t\t}\n\n\t\tconst nextMigration = bestCandidate!\n\t\tready.splice(ready.indexOf(nextMigration), 1)\n\n\t\t// Cycle detection - if we have processed everything and still have items left, there's a cycle\n\t\t// This is handled by Kahn's algorithm naturally - if we finish with items unprocessed, there's a cycle\n\n\t\t// Process this migration\n\t\tresult.push(nextMigration)\n\t\tprocessed.add(nextMigration.id)\n\n\t\t// Update in-degrees and add newly ready migrations\n\t\tfor (const depId of dependents.get(nextMigration.id) || []) {\n\t\t\tif (!processed.has(depId)) {\n\t\t\t\tinDegree.set(depId, inDegree.get(depId)! - 1)\n\t\t\t\tif (inDegree.get(depId) === 0) {\n\t\t\t\t\tready.push(byId.get(depId)!)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check for cycles - if we didn't process all migrations, there's a cycle\n\tif (result.length !== migrations.length) {\n\t\tconst unprocessed = migrations.filter((m) => !processed.has(m.id))\n\t\tassert(false, `Circular dependency in migrations: ${unprocessed[0].id}`)\n\t}\n\n\treturn result\n}\n\n/**\n * Parses a migration ID to extract the sequence ID and version number.\n *\n * Migration IDs follow the format `sequenceId/version`, and this function splits\n * them into their component parts. This is used internally for sorting migrations\n * and understanding their relationships.\n * @param id - The migration ID to parse\n * @returns Object containing the sequence ID and numeric version\n * @example\n * ```ts\n * const { sequenceId, version } = parseMigrationId('com.myapp.book/5')\n * // sequenceId: 'com.myapp.book', version: 5\n * ```\n * @internal\n */\nexport function parseMigrationId(id: MigrationId): { sequenceId: string; version: number } {\n\tconst [sequenceId, version] = id.split('/')\n\treturn { sequenceId, version: parseInt(version) }\n}\n\nfunction validateMigrationId(id: string, expectedSequenceId?: string) {\n\tif (expectedSequenceId) {\n\t\tassert(\n\t\t\tid.startsWith(expectedSequenceId + '/'),\n\t\t\t`Every migration in sequence '${expectedSequenceId}' must have an id starting with '${expectedSequenceId}/'. Got invalid id: '${id}'`\n\t\t)\n\t}\n\n\tassert(id.match(/^(.*?)\\/(0|[1-9]\\d*)$/), `Invalid migration id: '${id}'`)\n}\n\n/**\n * Validates that a migration sequence is correctly structured.\n *\n * Performs several validation checks to ensure the migration sequence is valid:\n * - Sequence ID doesn't contain invalid characters\n * - All migration IDs belong to the expected sequence\n * - Migration versions start at 1 and increment by 1\n * - Migration IDs follow the correct format\n * @param migrations - The migration sequence to validate\n * @throws Assertion error if any validation checks fail\n * @example\n * ```ts\n * const sequence = createMigrationSequence({\n * sequenceId: 'com.myapp.book',\n * sequence: [{ id: 'com.myapp.book/1', scope: 'record', up: (r) => r }]\n * })\n * validateMigrations(sequence) // Passes validation\n * ```\n * @public\n */\nexport function validateMigrations(migrations: MigrationSequence) {\n\tassert(\n\t\t!migrations.sequenceId.includes('/'),\n\t\t`sequenceId cannot contain a '/', got ${migrations.sequenceId}`\n\t)\n\tassert(migrations.sequenceId.length, 'sequenceId must be a non-empty string')\n\n\tif (migrations.sequence.length === 0) {\n\t\treturn\n\t}\n\n\tvalidateMigrationId(migrations.sequence[0].id, migrations.sequenceId)\n\tlet n = parseMigrationId(migrations.sequence[0].id).version\n\tassert(\n\t\tn === 1,\n\t\t`Expected the first migrationId to be '${migrations.sequenceId}/1' but got '${migrations.sequence[0].id}'`\n\t)\n\tfor (let i = 1; i < migrations.sequence.length; i++) {\n\t\tconst id = migrations.sequence[i].id\n\t\tvalidateMigrationId(id, migrations.sequenceId)\n\t\tconst m = parseMigrationId(id).version\n\t\tassert(\n\t\t\tm === n + 1,\n\t\t\t`Migration id numbers must increase in increments of 1, expected ${migrations.sequenceId}/${n + 1} but got '${migrations.sequence[i].id}'`\n\t\t)\n\t\tn = m\n\t}\n}\n\n/**\n * Result type returned by migration operations.\n *\n * Migration operations can either succeed and return the transformed value,\n * or fail with a specific reason. This discriminated union type allows for\n * safe handling of both success and error cases when applying migrations.\n * @public\n */\nexport type MigrationResult<T> =\n\t| { type: 'success'; value: T }\n\t| { type: 'error'; reason: MigrationFailureReason }\n\n/** @public */\nexport const MigrationFailureReason = {\n\tIncompatibleSubtype: 'incompatible-subtype',\n\tUnknownType: 'unknown-type',\n\tTargetVersionTooNew: 'target-version-too-new',\n\tTargetVersionTooOld: 'target-version-too-old',\n\tMigrationError: 'migration-error',\n\tUnrecognizedSubtype: 'unrecognized-subtype',\n} as const\n\n/** @public */\nexport type MigrationFailureReason =\n\t(typeof MigrationFailureReason)[keyof typeof MigrationFailureReason]\n\n// This is the magic part for backward compat:\n/** @public */\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport declare namespace MigrationFailureReason {\n\texport type IncompatibleSubtype = typeof MigrationFailureReason.IncompatibleSubtype\n\texport type UnknownType = typeof MigrationFailureReason.UnknownType\n\texport type TargetVersionTooNew = typeof MigrationFailureReason.TargetVersionTooNew\n\texport type TargetVersionTooOld = typeof MigrationFailureReason.TargetVersionTooOld\n\texport type MigrationError = typeof MigrationFailureReason.MigrationError\n\texport type UnrecognizedSubtype = typeof MigrationFailureReason.UnrecognizedSubtype\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/store",
3
3
  "description": "tldraw infinite canvas SDK (store).",
4
- "version": "4.5.2",
4
+ "version": "4.6.0-canary.00a8c03b5687",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -40,11 +40,11 @@
40
40
  "prepack": "yarn run -T tsx ../../internal/scripts/prepack.ts",
41
41
  "postpack": "../../internal/scripts/postpack.sh",
42
42
  "pack-tarball": "yarn pack",
43
- "lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
43
+ "lint": "cd ../.. && yarn run -T oxlint packages/store"
44
44
  },
45
45
  "dependencies": {
46
- "@tldraw/state": "4.5.2",
47
- "@tldraw/utils": "4.5.2"
46
+ "@tldraw/state": "4.6.0-canary.00a8c03b5687",
47
+ "@tldraw/utils": "4.6.0-canary.00a8c03b5687"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "react": "^18.2.0 || ^19.2.1"
@@ -350,7 +350,7 @@ export class AtomMap<K, V> implements Map<K, V> {
350
350
  * console.log(map.size) // 1
351
351
  * ```
352
352
  */
353
- // eslint-disable-next-line no-restricted-syntax
353
+ // eslint-disable-next-line tldraw/no-setter-getter
354
354
  get size() {
355
355
  return this.atoms.get().size
356
356
  }
@@ -32,7 +32,7 @@ export class AtomSet<T> {
32
32
  has(value: T): boolean {
33
33
  return this.map.has(value)
34
34
  }
35
- // eslint-disable-next-line no-restricted-syntax
35
+ // eslint-disable-next-line tldraw/no-setter-getter
36
36
  get size(): number {
37
37
  return this.map.size
38
38
  }
@@ -87,7 +87,7 @@ export class RecordType<
87
87
  */
88
88
  public readonly typeName: R['typeName'],
89
89
  config: {
90
- // eslint-disable-next-line @typescript-eslint/method-signature-style
90
+ // eslint-disable-next-line tldraw/method-signature-style
91
91
  readonly createDefaultProperties: () => Exclude<
92
92
  Omit<R, 'id' | 'typeName'>,
93
93
  RequiredProperties
@@ -156,9 +156,9 @@ export function createRecordMigrationSequence(opts: {
156
156
  * @public
157
157
  */
158
158
  export interface LegacyMigration<Before = any, After = any> {
159
- // eslint-disable-next-line @typescript-eslint/method-signature-style
159
+ // eslint-disable-next-line tldraw/method-signature-style
160
160
  up: (oldState: Before) => After
161
- // eslint-disable-next-line @typescript-eslint/method-signature-style
161
+ // eslint-disable-next-line tldraw/method-signature-style
162
162
  down: (newState: After) => Before
163
163
  }
164
164
 
@@ -202,27 +202,27 @@ export type Migration = {
202
202
  } & (
203
203
  | {
204
204
  readonly scope: 'record'
205
- // eslint-disable-next-line @typescript-eslint/method-signature-style
205
+ // eslint-disable-next-line tldraw/method-signature-style
206
206
  readonly filter?: (record: UnknownRecord) => boolean
207
- // eslint-disable-next-line @typescript-eslint/method-signature-style
207
+ // eslint-disable-next-line tldraw/method-signature-style
208
208
  readonly up: (oldState: UnknownRecord) => void | UnknownRecord
209
- // eslint-disable-next-line @typescript-eslint/method-signature-style
209
+ // eslint-disable-next-line tldraw/method-signature-style
210
210
  readonly down?: (newState: UnknownRecord) => void | UnknownRecord
211
211
  }
212
212
  | {
213
213
  readonly scope: 'store'
214
- // eslint-disable-next-line @typescript-eslint/method-signature-style
214
+ // eslint-disable-next-line tldraw/method-signature-style
215
215
  readonly up: (
216
216
  oldState: SerializedStore<UnknownRecord>
217
217
  ) => void | SerializedStore<UnknownRecord>
218
- // eslint-disable-next-line @typescript-eslint/method-signature-style
218
+ // eslint-disable-next-line tldraw/method-signature-style
219
219
  readonly down?: (
220
220
  newState: SerializedStore<UnknownRecord>
221
221
  ) => void | SerializedStore<UnknownRecord>
222
222
  }
223
223
  | {
224
224
  readonly scope: 'storage'
225
- // eslint-disable-next-line @typescript-eslint/method-signature-style
225
+ // eslint-disable-next-line tldraw/method-signature-style
226
226
  readonly up: (storage: SynchronousRecordStorage<UnknownRecord>) => void
227
227
  readonly down?: never
228
228
  }