@tldraw/editor 5.5.0-next.f9308d324e1c → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/dist-cjs/index.d.ts +49 -23
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +16 -12
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/MenuClickCapture.js +42 -3
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +5 -1
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +2 -5
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +3 -3
  12. package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +2 -2
  13. package/dist-cjs/lib/editor/Editor.js +168 -181
  14. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  15. package/dist-cjs/lib/editor/derivations/bindingsIndex.js +2 -0
  16. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  17. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +3 -2
  18. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/EditorManager.js +11 -2
  20. package/dist-cjs/lib/editor/managers/EditorManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js +49 -63
  22. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +1 -1
  24. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
  25. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +24 -24
  26. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
  27. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js +7 -1
  28. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +2 -2
  29. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js +4 -3
  30. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js.map +2 -2
  31. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +4 -4
  32. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  33. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +14 -6
  34. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +3 -3
  35. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  36. package/dist-cjs/lib/exports/ExportDelay.js +10 -2
  37. package/dist-cjs/lib/exports/ExportDelay.js.map +2 -2
  38. package/dist-cjs/lib/exports/FontEmbedder.js +25 -11
  39. package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
  40. package/dist-cjs/lib/exports/StyleEmbedder.js +9 -4
  41. package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
  42. package/dist-cjs/lib/exports/fetchCache.js +3 -0
  43. package/dist-cjs/lib/exports/fetchCache.js.map +2 -2
  44. package/dist-cjs/lib/exports/getSvgAsImage.js +10 -4
  45. package/dist-cjs/lib/exports/getSvgAsImage.js.map +2 -2
  46. package/dist-cjs/lib/exports/getSvgJsx.js +18 -9
  47. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  48. package/dist-cjs/lib/exports/parseCss.js +1 -1
  49. package/dist-cjs/lib/exports/parseCss.js.map +1 -1
  50. package/dist-cjs/lib/hooks/EditorComponentsContext.js.map +2 -2
  51. package/dist-cjs/lib/hooks/useCanvasEvents.js +24 -0
  52. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  53. package/dist-cjs/lib/hooks/useIdentity.js +6 -2
  54. package/dist-cjs/lib/hooks/useIdentity.js.map +3 -3
  55. package/dist-cjs/lib/license/LicenseManager.js +9 -1
  56. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  57. package/dist-cjs/lib/license/LicenseProvider.js +12 -4
  58. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  59. package/dist-cjs/lib/utils/debug-flags.js +0 -6
  60. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  61. package/dist-cjs/lib/utils/deepLinks.js +2 -1
  62. package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
  63. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js +32 -25
  64. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js.map +2 -2
  65. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +81 -40
  66. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  67. package/dist-cjs/version.js +3 -3
  68. package/dist-cjs/version.js.map +1 -1
  69. package/dist-esm/index.d.mts +49 -23
  70. package/dist-esm/index.mjs +4 -2
  71. package/dist-esm/index.mjs.map +2 -2
  72. package/dist-esm/lib/TldrawEditor.mjs +17 -13
  73. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  74. package/dist-esm/lib/components/MenuClickCapture.mjs +42 -3
  75. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  76. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +5 -1
  77. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  78. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +2 -5
  79. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +3 -3
  80. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  81. package/dist-esm/lib/editor/Editor.mjs +168 -181
  82. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  83. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +2 -0
  84. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  85. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +3 -2
  86. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  87. package/dist-esm/lib/editor/managers/EditorManager.mjs +11 -2
  88. package/dist-esm/lib/editor/managers/EditorManager.mjs.map +2 -2
  89. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs +49 -63
  90. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs.map +2 -2
  91. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +1 -1
  92. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
  93. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +24 -24
  94. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
  95. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs +7 -1
  96. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +2 -2
  97. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs +4 -3
  98. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +2 -2
  99. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +4 -4
  100. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  101. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +14 -6
  102. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +3 -3
  103. package/dist-esm/lib/exports/ExportDelay.mjs +11 -3
  104. package/dist-esm/lib/exports/ExportDelay.mjs.map +2 -2
  105. package/dist-esm/lib/exports/FontEmbedder.mjs +25 -11
  106. package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
  107. package/dist-esm/lib/exports/StyleEmbedder.mjs +9 -4
  108. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
  109. package/dist-esm/lib/exports/fetchCache.mjs +3 -0
  110. package/dist-esm/lib/exports/fetchCache.mjs.map +2 -2
  111. package/dist-esm/lib/exports/getSvgAsImage.mjs +10 -4
  112. package/dist-esm/lib/exports/getSvgAsImage.mjs.map +2 -2
  113. package/dist-esm/lib/exports/getSvgJsx.mjs +18 -9
  114. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  115. package/dist-esm/lib/exports/parseCss.mjs +1 -1
  116. package/dist-esm/lib/exports/parseCss.mjs.map +1 -1
  117. package/dist-esm/lib/hooks/EditorComponentsContext.mjs.map +2 -2
  118. package/dist-esm/lib/hooks/useCanvasEvents.mjs +24 -0
  119. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  120. package/dist-esm/lib/hooks/useIdentity.mjs +7 -3
  121. package/dist-esm/lib/hooks/useIdentity.mjs.map +3 -3
  122. package/dist-esm/lib/license/LicenseManager.mjs +9 -1
  123. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  124. package/dist-esm/lib/license/LicenseProvider.mjs +13 -5
  125. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  126. package/dist-esm/lib/utils/debug-flags.mjs +0 -6
  127. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  128. package/dist-esm/lib/utils/deepLinks.mjs +2 -1
  129. package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
  130. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs +32 -25
  131. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs.map +2 -2
  132. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +81 -40
  133. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  134. package/dist-esm/version.mjs +3 -3
  135. package/dist-esm/version.mjs.map +1 -1
  136. package/editor.css +9 -1
  137. package/package.json +8 -11
  138. package/src/index.ts +5 -1
  139. package/src/lib/TldrawEditor.tsx +35 -21
  140. package/src/lib/components/MenuClickCapture.tsx +55 -4
  141. package/src/lib/components/default-components/DefaultCanvas.tsx +7 -1
  142. package/src/lib/components/default-components/DefaultErrorFallback.tsx +13 -5
  143. package/src/lib/components/default-components/DefaultSpinner.tsx +4 -1
  144. package/src/lib/editor/Editor.test.ts +65 -0
  145. package/src/lib/editor/Editor.ts +261 -294
  146. package/src/lib/editor/derivations/bindingsIndex.ts +3 -0
  147. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +1 -0
  148. package/src/lib/editor/managers/ClickManager/ClickManager.ts +3 -2
  149. package/src/lib/editor/managers/EditorManager.test.ts +32 -1
  150. package/src/lib/editor/managers/EditorManager.ts +15 -3
  151. package/src/lib/editor/managers/PerformanceManager/PerformanceManager.test.ts +80 -0
  152. package/src/lib/editor/managers/PerformanceManager/PerformanceManager.ts +54 -81
  153. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +1 -1
  154. package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +32 -36
  155. package/src/lib/editor/managers/SnapManager/HandleSnaps.ts +13 -2
  156. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +13 -0
  157. package/src/lib/editor/managers/SnapManager/SnapManager.ts +6 -4
  158. package/src/lib/editor/managers/TextManager/TextManager.test.ts +74 -0
  159. package/src/lib/editor/managers/TextManager/TextManager.ts +6 -5
  160. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +23 -8
  161. package/src/lib/editor/types/external-content.ts +5 -0
  162. package/src/lib/exports/ExportDelay.tsx +16 -4
  163. package/src/lib/exports/FontEmbedder.test.ts +99 -0
  164. package/src/lib/exports/FontEmbedder.ts +32 -11
  165. package/src/lib/exports/StyleEmbedder.ts +13 -4
  166. package/src/lib/exports/fetchCache.test.ts +22 -0
  167. package/src/lib/exports/fetchCache.ts +4 -0
  168. package/src/lib/exports/getSvgAsImage.ts +13 -5
  169. package/src/lib/exports/getSvgJsx.test.ts +43 -1
  170. package/src/lib/exports/getSvgJsx.tsx +28 -16
  171. package/src/lib/exports/parseCss.test.ts +4 -0
  172. package/src/lib/exports/parseCss.ts +2 -2
  173. package/src/lib/hooks/EditorComponentsContext.tsx +2 -1
  174. package/src/lib/hooks/useCanvasEvents.ts +42 -0
  175. package/src/lib/hooks/useIdentity.tsx +12 -1
  176. package/src/lib/license/LicenseManager.ts +13 -1
  177. package/src/lib/license/LicenseProvider.test.tsx +76 -0
  178. package/src/lib/license/LicenseProvider.tsx +19 -5
  179. package/src/lib/test/preserveFocus.test.tsx +81 -0
  180. package/src/lib/utils/debug-flags.ts +0 -6
  181. package/src/lib/utils/deepLinks.test.ts +15 -0
  182. package/src/lib/utils/deepLinks.ts +8 -3
  183. package/src/lib/utils/sync/LocalIndexedDb.test.ts +121 -1
  184. package/src/lib/utils/sync/LocalIndexedDb.ts +41 -28
  185. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +257 -1
  186. package/src/lib/utils/sync/TLLocalSyncClient.ts +112 -54
  187. package/src/version.ts +3 -3
  188. package/dist-cjs/lib/primitives/geometry/geometry.bench.js +0 -133
  189. package/dist-cjs/lib/primitives/geometry/geometry.bench.js.map +0 -7
  190. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs +0 -132
  191. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs.map +0 -7
  192. package/src/lib/primitives/geometry/geometry.bench.ts +0 -179
@@ -3,6 +3,7 @@ import { deleteDB, openDB } from "idb";
3
3
  const STORE_PREFIX = "TLDRAW_DOCUMENT_v2";
4
4
  const LEGACY_ASSET_STORE_PREFIX = "TLDRAW_ASSET_STORE_v1";
5
5
  const dbNameIndexKey = "TLDRAW_DB_NAME_INDEX_v2";
6
+ const MAX_SESSION_STATE_ROWS = 10;
6
7
  const Table = {
7
8
  Records: "records",
8
9
  Schema: "schema",
@@ -12,7 +13,7 @@ const Table = {
12
13
  async function openLocalDb(persistenceKey) {
13
14
  const storeId = STORE_PREFIX + persistenceKey;
14
15
  addDbName(storeId);
15
- return await openDB(storeId, 4, {
16
+ const db = await openDB(storeId, 4, {
16
17
  upgrade(database) {
17
18
  if (!database.objectStoreNames.contains(Table.Records)) {
18
19
  database.createObjectStore(Table.Records);
@@ -26,8 +27,17 @@ async function openLocalDb(persistenceKey) {
26
27
  if (!database.objectStoreNames.contains(Table.Assets)) {
27
28
  database.createObjectStore(Table.Assets);
28
29
  }
30
+ },
31
+ // Another tab is deleting (hard reset) or upgrading this database. Its request waits
32
+ // until every open connection closes, so holding ours would stall it indefinitely.
33
+ // Closing here lets it proceed; this tab's next write then fails and goes through
34
+ // the write-failure alert-and-reload path.
35
+ blocking() {
36
+ console.warn(`Closing ${storeId} so another tab can delete or upgrade it`);
37
+ db.close();
29
38
  }
30
39
  });
40
+ return db;
31
41
  }
32
42
  async function migrateLegacyAssetDbIfNeeded(persistenceKey) {
33
43
  const databases = window.indexedDB.databases ? (await window.indexedDB.databases()).map((db) => db.name) : getAllIndexDbNames();
@@ -96,23 +106,17 @@ class LocalIndexedDb {
96
106
  assert(!this.isClosed, "db is closed");
97
107
  const db = await this.getDb();
98
108
  const tx = db.transaction(names, mode);
99
- const done = tx.done.catch((e) => {
100
- if (!this.isClosed) {
101
- throw e;
102
- }
103
- });
109
+ const done = tx.done;
110
+ done.catch(noop);
104
111
  try {
105
112
  return await cb(tx);
106
113
  } finally {
107
- if (!this.isClosed) {
108
- await done;
109
- } else {
110
- tx.abort();
111
- }
114
+ await done;
112
115
  }
113
116
  })();
114
117
  this.pendingTransactionSet.add(txPromise);
115
- txPromise.finally(() => this.pendingTransactionSet.delete(txPromise));
118
+ const cleanup = () => this.pendingTransactionSet.delete(txPromise);
119
+ txPromise.then(cleanup, cleanup);
116
120
  return txPromise;
117
121
  }
118
122
  async load({ sessionId } = {}) {
@@ -147,15 +151,17 @@ class LocalIndexedDb {
147
151
  const recordsStore = tx.objectStore(Table.Records);
148
152
  const schemaStore = tx.objectStore(Table.Schema);
149
153
  const sessionStateStore = tx.objectStore(Table.SessionState);
154
+ const requests = [];
150
155
  for (const [id, record] of Object.entries(changes.added)) {
151
- await recordsStore.put(record, id);
156
+ requests.push(recordsStore.put(record, id));
152
157
  }
153
158
  for (const [_prev, updated] of Object.values(changes.updated)) {
154
- await recordsStore.put(updated, updated.id);
159
+ requests.push(recordsStore.put(updated, updated.id));
155
160
  }
156
161
  for (const id of Object.keys(changes.removed)) {
157
- await recordsStore.delete(id);
162
+ requests.push(recordsStore.delete(id));
158
163
  }
164
+ await Promise.all(requests);
159
165
  schemaStore.put(schema.serialize(), Table.Schema);
160
166
  if (sessionStateSnapshot && sessionId) {
161
167
  sessionStateStore.put(
@@ -182,9 +188,9 @@ class LocalIndexedDb {
182
188
  const schemaStore = tx.objectStore(Table.Schema);
183
189
  const sessionStateStore = tx.objectStore(Table.SessionState);
184
190
  await recordsStore.clear();
185
- for (const [id, record] of Object.entries(snapshot)) {
186
- await recordsStore.put(record, id);
187
- }
191
+ await Promise.all(
192
+ Object.entries(snapshot).map(([id, record]) => recordsStore.put(record, id))
193
+ );
188
194
  schemaStore.put(schema.serialize(), Table.Schema);
189
195
  if (sessionStateSnapshot && sessionId) {
190
196
  sessionStateStore.put(
@@ -200,15 +206,16 @@ class LocalIndexedDb {
200
206
  }
201
207
  });
202
208
  }
203
- async pruneSessions() {
209
+ /**
210
+ * Drop all but the most recently updated session state rows. The row for `keepSessionId` is never
211
+ * deleted: it may be an old row that this tab restored its state from and has not rewritten yet,
212
+ * and deleting it would make a later reload fall back to another tab's session state.
213
+ */
214
+ async pruneSessions({ keepSessionId } = {}) {
204
215
  await this.tx("readwrite", [Table.SessionState], async (tx) => {
205
216
  const sessionStateStore = tx.objectStore(Table.SessionState);
206
- const all = (await sessionStateStore.getAll()).sort((a, b) => a.updatedAt - b.updatedAt);
207
- if (all.length < 10) {
208
- await tx.done;
209
- return;
210
- }
211
- const toDelete = all.slice(0, all.length - 10);
217
+ const all = (await sessionStateStore.getAll()).filter((row) => row.id !== keepSessionId).sort((a, b) => a.updatedAt - b.updatedAt);
218
+ const toDelete = all.slice(0, Math.max(0, all.length - MAX_SESSION_STATE_ROWS));
212
219
  for (const { id } of toDelete) {
213
220
  await sessionStateStore.delete(id);
214
221
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/utils/sync/LocalIndexedDb.ts"],
4
- "sourcesContent": ["import { RecordsDiff, SerializedSchema, SerializedStore } from '@tldraw/store'\nimport { TLRecord, TLStoreSchema } from '@tldraw/tlschema'\nimport { assert, getFromLocalStorage, noop, setInLocalStorage } from '@tldraw/utils'\nimport { IDBPDatabase, IDBPTransaction, deleteDB, openDB } from 'idb'\nimport { TLSessionStateSnapshot } from '../../config/TLSessionStateSnapshot'\n\n// DO NOT CHANGE THESE WITHOUT ADDING MIGRATION LOGIC. DOING SO WOULD WIPE ALL EXISTING DATA.\nconst STORE_PREFIX = 'TLDRAW_DOCUMENT_v2'\nconst LEGACY_ASSET_STORE_PREFIX = 'TLDRAW_ASSET_STORE_v1'\nconst dbNameIndexKey = 'TLDRAW_DB_NAME_INDEX_v2'\n\n/** @internal */\nexport const Table = {\n\tRecords: 'records',\n\tSchema: 'schema',\n\tSessionState: 'session_state',\n\tAssets: 'assets',\n} as const\n\n/** @internal */\nexport type StoreName = (typeof Table)[keyof typeof Table]\n\nasync function openLocalDb(persistenceKey: string) {\n\tconst storeId = STORE_PREFIX + persistenceKey\n\n\taddDbName(storeId)\n\n\treturn await openDB<StoreName>(storeId, 4, {\n\t\tupgrade(database) {\n\t\t\tif (!database.objectStoreNames.contains(Table.Records)) {\n\t\t\t\tdatabase.createObjectStore(Table.Records)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.Schema)) {\n\t\t\t\tdatabase.createObjectStore(Table.Schema)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.SessionState)) {\n\t\t\t\tdatabase.createObjectStore(Table.SessionState)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.Assets)) {\n\t\t\t\tdatabase.createObjectStore(Table.Assets)\n\t\t\t}\n\t\t},\n\t})\n}\n\nasync function migrateLegacyAssetDbIfNeeded(persistenceKey: string) {\n\tconst databases = window.indexedDB.databases\n\t\t? (await window.indexedDB.databases()).map((db) => db.name)\n\t\t: getAllIndexDbNames()\n\tconst oldStoreId = LEGACY_ASSET_STORE_PREFIX + persistenceKey\n\tconst existing = databases.find((dbName) => dbName === oldStoreId)\n\tif (!existing) return\n\n\tconst oldAssetDb = await openDB<StoreName>(oldStoreId, 1, {\n\t\tupgrade(database) {\n\t\t\tif (!database.objectStoreNames.contains('assets')) {\n\t\t\t\tdatabase.createObjectStore('assets')\n\t\t\t}\n\t\t},\n\t})\n\tif (!oldAssetDb.objectStoreNames.contains('assets')) return\n\n\tconst oldTx = oldAssetDb.transaction(['assets'], 'readonly')\n\tconst oldAssetStore = oldTx.objectStore('assets')\n\tconst oldAssetsKeys = await oldAssetStore.getAllKeys()\n\tconst oldAssets = await Promise.all(\n\t\toldAssetsKeys.map(async (key) => [key, await oldAssetStore.get(key)] as const)\n\t)\n\tawait oldTx.done\n\n\tconst newDb = await openLocalDb(persistenceKey)\n\tconst newTx = newDb.transaction([Table.Assets], 'readwrite')\n\tconst newAssetTable = newTx.objectStore(Table.Assets)\n\tfor (const [key, value] of oldAssets) {\n\t\tnewAssetTable.put(value, key)\n\t}\n\tawait newTx.done\n\n\toldAssetDb.close()\n\tnewDb.close()\n\n\tawait deleteDB(oldStoreId)\n}\n\ninterface LoadResult {\n\trecords: TLRecord[]\n\tschema?: SerializedSchema\n\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n}\n\ninterface SessionStateSnapshotRow {\n\tid: string\n\tsnapshot: TLSessionStateSnapshot\n\tupdatedAt: number\n}\n\n/** @internal */\nexport class LocalIndexedDb {\n\tprivate getDbPromise: Promise<IDBPDatabase<StoreName>>\n\tprivate isClosed = false\n\tprivate pendingTransactionSet = new Set<Promise<unknown>>()\n\n\t/** @internal */\n\tstatic connectedInstances = new Set<LocalIndexedDb>()\n\n\tconstructor(persistenceKey: string) {\n\t\tLocalIndexedDb.connectedInstances.add(this)\n\t\tthis.getDbPromise = (async () => {\n\t\t\tawait migrateLegacyAssetDbIfNeeded(persistenceKey)\n\t\t\treturn await openLocalDb(persistenceKey)\n\t\t})()\n\t}\n\n\tprivate getDb() {\n\t\treturn this.getDbPromise\n\t}\n\n\t/**\n\t * Wait for any pending transactions to be completed. Useful for tests.\n\t *\n\t * @internal\n\t */\n\tpending(): Promise<void> {\n\t\treturn Promise.allSettled([this.getDbPromise, ...this.pendingTransactionSet]).then(noop)\n\t}\n\n\tasync close() {\n\t\tif (this.isClosed) return\n\t\tthis.isClosed = true\n\t\tawait this.pending()\n\t\t;(await this.getDb()).close()\n\t\tLocalIndexedDb.connectedInstances.delete(this)\n\t}\n\n\tprivate tx<Names extends StoreName[], Mode extends IDBTransactionMode, T>(\n\t\tmode: Mode,\n\t\tnames: Names,\n\t\tcb: (tx: IDBPTransaction<StoreName, Names, Mode>) => Promise<T>\n\t): Promise<T> {\n\t\tconst txPromise = (async () => {\n\t\t\tassert(!this.isClosed, 'db is closed')\n\t\t\tconst db = await this.getDb()\n\t\t\tconst tx = db.transaction(names, mode)\n\t\t\t// need to add a catch here early to prevent unhandled promise rejection\n\t\t\t// during react-strict-mode where this tx.done promise can be rejected\n\t\t\t// before we have a chance to await on it\n\t\t\tconst done = tx.done.catch((e: unknown) => {\n\t\t\t\tif (!this.isClosed) {\n\t\t\t\t\tthrow e\n\t\t\t\t}\n\t\t\t})\n\t\t\ttry {\n\t\t\t\treturn await cb(tx)\n\t\t\t} finally {\n\t\t\t\tif (!this.isClosed) {\n\t\t\t\t\tawait done\n\t\t\t\t} else {\n\t\t\t\t\ttx.abort()\n\t\t\t\t}\n\t\t\t}\n\t\t})()\n\t\tthis.pendingTransactionSet.add(txPromise)\n\t\ttxPromise.finally(() => this.pendingTransactionSet.delete(txPromise))\n\t\treturn txPromise\n\t}\n\n\tasync load({ sessionId }: { sessionId?: string } = {}) {\n\t\treturn await this.tx(\n\t\t\t'readonly',\n\t\t\t[Table.Records, Table.Schema, Table.SessionState],\n\t\t\tasync (tx) => {\n\t\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\t\t\t\tlet sessionStateSnapshot = sessionId\n\t\t\t\t\t? ((await sessionStateStore.get(sessionId)) as SessionStateSnapshotRow | undefined)\n\t\t\t\t\t\t\t?.snapshot\n\t\t\t\t\t: null\n\t\t\t\tif (!sessionStateSnapshot) {\n\t\t\t\t\t// get the most recent session state\n\t\t\t\t\tconst all = (await sessionStateStore.getAll()) as SessionStateSnapshotRow[]\n\t\t\t\t\tsessionStateSnapshot = all.sort((a, b) => a.updatedAt - b.updatedAt).pop()?.snapshot\n\t\t\t\t}\n\t\t\t\tconst result = {\n\t\t\t\t\trecords: await recordsStore.getAll(),\n\t\t\t\t\tschema: await schemaStore.get(Table.Schema),\n\t\t\t\t\tsessionStateSnapshot,\n\t\t\t\t} satisfies LoadResult\n\n\t\t\t\treturn result\n\t\t\t}\n\t\t)\n\t}\n\n\tasync storeChanges({\n\t\tschema,\n\t\tchanges,\n\t\tsessionId,\n\t\tsessionStateSnapshot,\n\t}: {\n\t\tschema: TLStoreSchema\n\t\tchanges: RecordsDiff<any>\n\t\tsessionId?: string | null\n\t\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n\t}) {\n\t\tawait this.tx('readwrite', [Table.Records, Table.Schema, Table.SessionState], async (tx) => {\n\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\n\t\t\tfor (const [id, record] of Object.entries(changes.added)) {\n\t\t\t\tawait recordsStore.put(record, id)\n\t\t\t}\n\n\t\t\tfor (const [_prev, updated] of Object.values(changes.updated)) {\n\t\t\t\tawait recordsStore.put(updated, updated.id)\n\t\t\t}\n\n\t\t\tfor (const id of Object.keys(changes.removed)) {\n\t\t\t\tawait recordsStore.delete(id)\n\t\t\t}\n\n\t\t\tschemaStore.put(schema.serialize(), Table.Schema)\n\t\t\tif (sessionStateSnapshot && sessionId) {\n\t\t\t\tsessionStateStore.put(\n\t\t\t\t\t{\n\t\t\t\t\t\tsnapshot: sessionStateSnapshot,\n\t\t\t\t\t\tupdatedAt: Date.now(),\n\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t} satisfies SessionStateSnapshotRow,\n\t\t\t\t\tsessionId\n\t\t\t\t)\n\t\t\t} else if (sessionStateSnapshot || sessionId) {\n\t\t\t\tconsole.error('sessionStateSnapshot and instanceId must be provided together')\n\t\t\t}\n\t\t})\n\t}\n\n\tasync storeSnapshot({\n\t\tschema,\n\t\tsnapshot,\n\t\tsessionId,\n\t\tsessionStateSnapshot,\n\t}: {\n\t\tschema: TLStoreSchema\n\t\tsnapshot: SerializedStore<any>\n\t\tsessionId?: string | null\n\t\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n\t}) {\n\t\tawait this.tx('readwrite', [Table.Records, Table.Schema, Table.SessionState], async (tx) => {\n\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\n\t\t\tawait recordsStore.clear()\n\n\t\t\tfor (const [id, record] of Object.entries(snapshot)) {\n\t\t\t\tawait recordsStore.put(record, id)\n\t\t\t}\n\n\t\t\tschemaStore.put(schema.serialize(), Table.Schema)\n\n\t\t\tif (sessionStateSnapshot && sessionId) {\n\t\t\t\tsessionStateStore.put(\n\t\t\t\t\t{\n\t\t\t\t\t\tsnapshot: sessionStateSnapshot,\n\t\t\t\t\t\tupdatedAt: Date.now(),\n\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t} satisfies SessionStateSnapshotRow,\n\t\t\t\t\tsessionId\n\t\t\t\t)\n\t\t\t} else if (sessionStateSnapshot || sessionId) {\n\t\t\t\tconsole.error('sessionStateSnapshot and instanceId must be provided together')\n\t\t\t}\n\t\t})\n\t}\n\n\tasync pruneSessions() {\n\t\tawait this.tx('readwrite', [Table.SessionState], async (tx) => {\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\t\t\tconst all = (await sessionStateStore.getAll()).sort((a, b) => a.updatedAt - b.updatedAt)\n\t\t\tif (all.length < 10) {\n\t\t\t\tawait tx.done\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst toDelete = all.slice(0, all.length - 10)\n\t\t\tfor (const { id } of toDelete) {\n\t\t\t\tawait sessionStateStore.delete(id)\n\t\t\t}\n\t\t})\n\t}\n\n\tasync getAsset(assetId: string): Promise<File | undefined> {\n\t\treturn await this.tx('readonly', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\treturn await assetsStore.get(assetId)\n\t\t})\n\t}\n\n\tasync storeAsset(assetId: string, blob: File) {\n\t\tawait this.tx('readwrite', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\tawait assetsStore.put(blob, assetId)\n\t\t})\n\t}\n\n\tasync removeAssets(assetId: string[]) {\n\t\tawait this.tx('readwrite', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\tfor (const id of assetId) {\n\t\t\t\tawait assetsStore.delete(id)\n\t\t\t}\n\t\t})\n\t}\n}\n\n/** @internal */\nexport function getAllIndexDbNames(): string[] {\n\tconst result = JSON.parse(getFromLocalStorage(dbNameIndexKey) || '[]') ?? []\n\tif (!Array.isArray(result)) {\n\t\treturn []\n\t}\n\treturn result\n}\n\nfunction addDbName(name: string) {\n\tconst all = new Set(getAllIndexDbNames())\n\tall.add(name)\n\tsetInLocalStorage(dbNameIndexKey, JSON.stringify([...all]))\n}\n"],
5
- "mappings": "AAEA,SAAS,QAAQ,qBAAqB,MAAM,yBAAyB;AACrE,SAAwC,UAAU,cAAc;AAIhE,MAAM,eAAe;AACrB,MAAM,4BAA4B;AAClC,MAAM,iBAAiB;AAGhB,MAAM,QAAQ;AAAA,EACpB,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,QAAQ;AACT;AAKA,eAAe,YAAY,gBAAwB;AAClD,QAAM,UAAU,eAAe;AAE/B,YAAU,OAAO;AAEjB,SAAO,MAAM,OAAkB,SAAS,GAAG;AAAA,IAC1C,QAAQ,UAAU;AACjB,UAAI,CAAC,SAAS,iBAAiB,SAAS,MAAM,OAAO,GAAG;AACvD,iBAAS,kBAAkB,MAAM,OAAO;AAAA,MACzC;AACA,UAAI,CAAC,SAAS,iBAAiB,SAAS,MAAM,MAAM,GAAG;AACtD,iBAAS,kBAAkB,MAAM,MAAM;AAAA,MACxC;AACA,UAAI,CAAC,SAAS,iBAAiB,SAAS,MAAM,YAAY,GAAG;AAC5D,iBAAS,kBAAkB,MAAM,YAAY;AAAA,MAC9C;AACA,UAAI,CAAC,SAAS,iBAAiB,SAAS,MAAM,MAAM,GAAG;AACtD,iBAAS,kBAAkB,MAAM,MAAM;AAAA,MACxC;AAAA,IACD;AAAA,EACD,CAAC;AACF;AAEA,eAAe,6BAA6B,gBAAwB;AACnE,QAAM,YAAY,OAAO,UAAU,aAC/B,MAAM,OAAO,UAAU,UAAU,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,IACxD,mBAAmB;AACtB,QAAM,aAAa,4BAA4B;AAC/C,QAAM,WAAW,UAAU,KAAK,CAAC,WAAW,WAAW,UAAU;AACjE,MAAI,CAAC,SAAU;AAEf,QAAM,aAAa,MAAM,OAAkB,YAAY,GAAG;AAAA,IACzD,QAAQ,UAAU;AACjB,UAAI,CAAC,SAAS,iBAAiB,SAAS,QAAQ,GAAG;AAClD,iBAAS,kBAAkB,QAAQ;AAAA,MACpC;AAAA,IACD;AAAA,EACD,CAAC;AACD,MAAI,CAAC,WAAW,iBAAiB,SAAS,QAAQ,EAAG;AAErD,QAAM,QAAQ,WAAW,YAAY,CAAC,QAAQ,GAAG,UAAU;AAC3D,QAAM,gBAAgB,MAAM,YAAY,QAAQ;AAChD,QAAM,gBAAgB,MAAM,cAAc,WAAW;AACrD,QAAM,YAAY,MAAM,QAAQ;AAAA,IAC/B,cAAc,IAAI,OAAO,QAAQ,CAAC,KAAK,MAAM,cAAc,IAAI,GAAG,CAAC,CAAU;AAAA,EAC9E;AACA,QAAM,MAAM;AAEZ,QAAM,QAAQ,MAAM,YAAY,cAAc;AAC9C,QAAM,QAAQ,MAAM,YAAY,CAAC,MAAM,MAAM,GAAG,WAAW;AAC3D,QAAM,gBAAgB,MAAM,YAAY,MAAM,MAAM;AACpD,aAAW,CAAC,KAAK,KAAK,KAAK,WAAW;AACrC,kBAAc,IAAI,OAAO,GAAG;AAAA,EAC7B;AACA,QAAM,MAAM;AAEZ,aAAW,MAAM;AACjB,QAAM,MAAM;AAEZ,QAAM,SAAS,UAAU;AAC1B;AAeO,MAAM,eAAe;AAAA,EACnB;AAAA,EACA,WAAW;AAAA,EACX,wBAAwB,oBAAI,IAAsB;AAAA;AAAA,EAG1D,OAAO,qBAAqB,oBAAI,IAAoB;AAAA,EAEpD,YAAY,gBAAwB;AACnC,mBAAe,mBAAmB,IAAI,IAAI;AAC1C,SAAK,gBAAgB,YAAY;AAChC,YAAM,6BAA6B,cAAc;AACjD,aAAO,MAAM,YAAY,cAAc;AAAA,IACxC,GAAG;AAAA,EACJ;AAAA,EAEQ,QAAQ;AACf,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAyB;AACxB,WAAO,QAAQ,WAAW,CAAC,KAAK,cAAc,GAAG,KAAK,qBAAqB,CAAC,EAAE,KAAK,IAAI;AAAA,EACxF;AAAA,EAEA,MAAM,QAAQ;AACb,QAAI,KAAK,SAAU;AACnB,SAAK,WAAW;AAChB,UAAM,KAAK,QAAQ;AAClB,KAAC,MAAM,KAAK,MAAM,GAAG,MAAM;AAC5B,mBAAe,mBAAmB,OAAO,IAAI;AAAA,EAC9C;AAAA,EAEQ,GACP,MACA,OACA,IACa;AACb,UAAM,aAAa,YAAY;AAC9B,aAAO,CAAC,KAAK,UAAU,cAAc;AACrC,YAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,YAAM,KAAK,GAAG,YAAY,OAAO,IAAI;AAIrC,YAAM,OAAO,GAAG,KAAK,MAAM,CAAC,MAAe;AAC1C,YAAI,CAAC,KAAK,UAAU;AACnB,gBAAM;AAAA,QACP;AAAA,MACD,CAAC;AACD,UAAI;AACH,eAAO,MAAM,GAAG,EAAE;AAAA,MACnB,UAAE;AACD,YAAI,CAAC,KAAK,UAAU;AACnB,gBAAM;AAAA,QACP,OAAO;AACN,aAAG,MAAM;AAAA,QACV;AAAA,MACD;AAAA,IACD,GAAG;AACH,SAAK,sBAAsB,IAAI,SAAS;AACxC,cAAU,QAAQ,MAAM,KAAK,sBAAsB,OAAO,SAAS,CAAC;AACpE,WAAO;AAAA,EACR;AAAA,EAEA,MAAM,KAAK,EAAE,UAAU,IAA4B,CAAC,GAAG;AACtD,WAAO,MAAM,KAAK;AAAA,MACjB;AAAA,MACA,CAAC,MAAM,SAAS,MAAM,QAAQ,MAAM,YAAY;AAAA,MAChD,OAAO,OAAO;AACb,cAAM,eAAe,GAAG,YAAY,MAAM,OAAO;AACjD,cAAM,cAAc,GAAG,YAAY,MAAM,MAAM;AAC/C,cAAM,oBAAoB,GAAG,YAAY,MAAM,YAAY;AAC3D,YAAI,uBAAuB,aACtB,MAAM,kBAAkB,IAAI,SAAS,IACrC,WACF;AACH,YAAI,CAAC,sBAAsB;AAE1B,gBAAM,MAAO,MAAM,kBAAkB,OAAO;AAC5C,iCAAuB,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,GAAG;AAAA,QAC7E;AACA,cAAM,SAAS;AAAA,UACd,SAAS,MAAM,aAAa,OAAO;AAAA,UACnC,QAAQ,MAAM,YAAY,IAAI,MAAM,MAAM;AAAA,UAC1C;AAAA,QACD;AAEA,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,aAAa;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,GAKG;AACF,UAAM,KAAK,GAAG,aAAa,CAAC,MAAM,SAAS,MAAM,QAAQ,MAAM,YAAY,GAAG,OAAO,OAAO;AAC3F,YAAM,eAAe,GAAG,YAAY,MAAM,OAAO;AACjD,YAAM,cAAc,GAAG,YAAY,MAAM,MAAM;AAC/C,YAAM,oBAAoB,GAAG,YAAY,MAAM,YAAY;AAE3D,iBAAW,CAAC,IAAI,MAAM,KAAK,OAAO,QAAQ,QAAQ,KAAK,GAAG;AACzD,cAAM,aAAa,IAAI,QAAQ,EAAE;AAAA,MAClC;AAEA,iBAAW,CAAC,OAAO,OAAO,KAAK,OAAO,OAAO,QAAQ,OAAO,GAAG;AAC9D,cAAM,aAAa,IAAI,SAAS,QAAQ,EAAE;AAAA,MAC3C;AAEA,iBAAW,MAAM,OAAO,KAAK,QAAQ,OAAO,GAAG;AAC9C,cAAM,aAAa,OAAO,EAAE;AAAA,MAC7B;AAEA,kBAAY,IAAI,OAAO,UAAU,GAAG,MAAM,MAAM;AAChD,UAAI,wBAAwB,WAAW;AACtC,0BAAkB;AAAA,UACjB;AAAA,YACC,UAAU;AAAA,YACV,WAAW,KAAK,IAAI;AAAA,YACpB,IAAI;AAAA,UACL;AAAA,UACA;AAAA,QACD;AAAA,MACD,WAAW,wBAAwB,WAAW;AAC7C,gBAAQ,MAAM,+DAA+D;AAAA,MAC9E;AAAA,IACD,CAAC;AAAA,EACF;AAAA,EAEA,MAAM,cAAc;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,GAKG;AACF,UAAM,KAAK,GAAG,aAAa,CAAC,MAAM,SAAS,MAAM,QAAQ,MAAM,YAAY,GAAG,OAAO,OAAO;AAC3F,YAAM,eAAe,GAAG,YAAY,MAAM,OAAO;AACjD,YAAM,cAAc,GAAG,YAAY,MAAM,MAAM;AAC/C,YAAM,oBAAoB,GAAG,YAAY,MAAM,YAAY;AAE3D,YAAM,aAAa,MAAM;AAEzB,iBAAW,CAAC,IAAI,MAAM,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACpD,cAAM,aAAa,IAAI,QAAQ,EAAE;AAAA,MAClC;AAEA,kBAAY,IAAI,OAAO,UAAU,GAAG,MAAM,MAAM;AAEhD,UAAI,wBAAwB,WAAW;AACtC,0BAAkB;AAAA,UACjB;AAAA,YACC,UAAU;AAAA,YACV,WAAW,KAAK,IAAI;AAAA,YACpB,IAAI;AAAA,UACL;AAAA,UACA;AAAA,QACD;AAAA,MACD,WAAW,wBAAwB,WAAW;AAC7C,gBAAQ,MAAM,+DAA+D;AAAA,MAC9E;AAAA,IACD,CAAC;AAAA,EACF;AAAA,EAEA,MAAM,gBAAgB;AACrB,UAAM,KAAK,GAAG,aAAa,CAAC,MAAM,YAAY,GAAG,OAAO,OAAO;AAC9D,YAAM,oBAAoB,GAAG,YAAY,MAAM,YAAY;AAC3D,YAAM,OAAO,MAAM,kBAAkB,OAAO,GAAG,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AACvF,UAAI,IAAI,SAAS,IAAI;AACpB,cAAM,GAAG;AACT;AAAA,MACD;AACA,YAAM,WAAW,IAAI,MAAM,GAAG,IAAI,SAAS,EAAE;AAC7C,iBAAW,EAAE,GAAG,KAAK,UAAU;AAC9B,cAAM,kBAAkB,OAAO,EAAE;AAAA,MAClC;AAAA,IACD,CAAC;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,SAA4C;AAC1D,WAAO,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,MAAM,GAAG,OAAO,OAAO;AAC9D,YAAM,cAAc,GAAG,YAAY,MAAM,MAAM;AAC/C,aAAO,MAAM,YAAY,IAAI,OAAO;AAAA,IACrC,CAAC;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,SAAiB,MAAY;AAC7C,UAAM,KAAK,GAAG,aAAa,CAAC,MAAM,MAAM,GAAG,OAAO,OAAO;AACxD,YAAM,cAAc,GAAG,YAAY,MAAM,MAAM;AAC/C,YAAM,YAAY,IAAI,MAAM,OAAO;AAAA,IACpC,CAAC;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,SAAmB;AACrC,UAAM,KAAK,GAAG,aAAa,CAAC,MAAM,MAAM,GAAG,OAAO,OAAO;AACxD,YAAM,cAAc,GAAG,YAAY,MAAM,MAAM;AAC/C,iBAAW,MAAM,SAAS;AACzB,cAAM,YAAY,OAAO,EAAE;AAAA,MAC5B;AAAA,IACD,CAAC;AAAA,EACF;AACD;AAGO,SAAS,qBAA+B;AAC9C,QAAM,SAAS,KAAK,MAAM,oBAAoB,cAAc,KAAK,IAAI,KAAK,CAAC;AAC3E,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC3B,WAAO,CAAC;AAAA,EACT;AACA,SAAO;AACR;AAEA,SAAS,UAAU,MAAc;AAChC,QAAM,MAAM,IAAI,IAAI,mBAAmB,CAAC;AACxC,MAAI,IAAI,IAAI;AACZ,oBAAkB,gBAAgB,KAAK,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC;AAC3D;",
4
+ "sourcesContent": ["import { RecordsDiff, SerializedSchema, SerializedStore } from '@tldraw/store'\nimport { TLRecord, TLStoreSchema } from '@tldraw/tlschema'\nimport { assert, getFromLocalStorage, noop, setInLocalStorage } from '@tldraw/utils'\nimport { IDBPDatabase, IDBPTransaction, deleteDB, openDB } from 'idb'\nimport { TLSessionStateSnapshot } from '../../config/TLSessionStateSnapshot'\n\n// DO NOT CHANGE THESE WITHOUT ADDING MIGRATION LOGIC. DOING SO WOULD WIPE ALL EXISTING DATA.\nconst STORE_PREFIX = 'TLDRAW_DOCUMENT_v2'\nconst LEGACY_ASSET_STORE_PREFIX = 'TLDRAW_ASSET_STORE_v1'\nconst dbNameIndexKey = 'TLDRAW_DB_NAME_INDEX_v2'\n\n/** How many session state rows (one per tab) to keep per document when pruning. */\nconst MAX_SESSION_STATE_ROWS = 10\n\n/** @internal */\nexport const Table = {\n\tRecords: 'records',\n\tSchema: 'schema',\n\tSessionState: 'session_state',\n\tAssets: 'assets',\n} as const\n\n/** @internal */\nexport type StoreName = (typeof Table)[keyof typeof Table]\n\nasync function openLocalDb(persistenceKey: string) {\n\tconst storeId = STORE_PREFIX + persistenceKey\n\n\taddDbName(storeId)\n\n\tconst db = await openDB<StoreName>(storeId, 4, {\n\t\tupgrade(database) {\n\t\t\tif (!database.objectStoreNames.contains(Table.Records)) {\n\t\t\t\tdatabase.createObjectStore(Table.Records)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.Schema)) {\n\t\t\t\tdatabase.createObjectStore(Table.Schema)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.SessionState)) {\n\t\t\t\tdatabase.createObjectStore(Table.SessionState)\n\t\t\t}\n\t\t\tif (!database.objectStoreNames.contains(Table.Assets)) {\n\t\t\t\tdatabase.createObjectStore(Table.Assets)\n\t\t\t}\n\t\t},\n\t\t// Another tab is deleting (hard reset) or upgrading this database. Its request waits\n\t\t// until every open connection closes, so holding ours would stall it indefinitely.\n\t\t// Closing here lets it proceed; this tab's next write then fails and goes through\n\t\t// the write-failure alert-and-reload path.\n\t\tblocking() {\n\t\t\tconsole.warn(`Closing ${storeId} so another tab can delete or upgrade it`)\n\t\t\tdb.close()\n\t\t},\n\t})\n\treturn db\n}\n\nasync function migrateLegacyAssetDbIfNeeded(persistenceKey: string) {\n\tconst databases = window.indexedDB.databases\n\t\t? (await window.indexedDB.databases()).map((db) => db.name)\n\t\t: getAllIndexDbNames()\n\tconst oldStoreId = LEGACY_ASSET_STORE_PREFIX + persistenceKey\n\tconst existing = databases.find((dbName) => dbName === oldStoreId)\n\tif (!existing) return\n\n\tconst oldAssetDb = await openDB<StoreName>(oldStoreId, 1, {\n\t\tupgrade(database) {\n\t\t\tif (!database.objectStoreNames.contains('assets')) {\n\t\t\t\tdatabase.createObjectStore('assets')\n\t\t\t}\n\t\t},\n\t})\n\tif (!oldAssetDb.objectStoreNames.contains('assets')) return\n\n\tconst oldTx = oldAssetDb.transaction(['assets'], 'readonly')\n\tconst oldAssetStore = oldTx.objectStore('assets')\n\tconst oldAssetsKeys = await oldAssetStore.getAllKeys()\n\tconst oldAssets = await Promise.all(\n\t\toldAssetsKeys.map(async (key) => [key, await oldAssetStore.get(key)] as const)\n\t)\n\tawait oldTx.done\n\n\tconst newDb = await openLocalDb(persistenceKey)\n\tconst newTx = newDb.transaction([Table.Assets], 'readwrite')\n\tconst newAssetTable = newTx.objectStore(Table.Assets)\n\tfor (const [key, value] of oldAssets) {\n\t\tnewAssetTable.put(value, key)\n\t}\n\tawait newTx.done\n\n\toldAssetDb.close()\n\tnewDb.close()\n\n\tawait deleteDB(oldStoreId)\n}\n\ninterface LoadResult {\n\trecords: TLRecord[]\n\tschema?: SerializedSchema\n\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n}\n\ninterface SessionStateSnapshotRow {\n\tid: string\n\tsnapshot: TLSessionStateSnapshot\n\tupdatedAt: number\n}\n\n/** @internal */\nexport class LocalIndexedDb {\n\tprivate getDbPromise: Promise<IDBPDatabase<StoreName>>\n\tprivate isClosed = false\n\tprivate pendingTransactionSet = new Set<Promise<unknown>>()\n\n\t/** @internal */\n\tstatic connectedInstances = new Set<LocalIndexedDb>()\n\n\tconstructor(persistenceKey: string) {\n\t\tLocalIndexedDb.connectedInstances.add(this)\n\t\tthis.getDbPromise = (async () => {\n\t\t\tawait migrateLegacyAssetDbIfNeeded(persistenceKey)\n\t\t\treturn await openLocalDb(persistenceKey)\n\t\t})()\n\t}\n\n\tprivate getDb() {\n\t\treturn this.getDbPromise\n\t}\n\n\t/**\n\t * Wait for any pending transactions to be completed. Useful for tests.\n\t *\n\t * @internal\n\t */\n\tpending(): Promise<void> {\n\t\treturn Promise.allSettled([this.getDbPromise, ...this.pendingTransactionSet]).then(noop)\n\t}\n\n\tasync close() {\n\t\tif (this.isClosed) return\n\t\tthis.isClosed = true\n\t\tawait this.pending()\n\t\t;(await this.getDb()).close()\n\t\tLocalIndexedDb.connectedInstances.delete(this)\n\t}\n\n\tprivate tx<Names extends StoreName[], Mode extends IDBTransactionMode, T>(\n\t\tmode: Mode,\n\t\tnames: Names,\n\t\tcb: (tx: IDBPTransaction<StoreName, Names, Mode>) => Promise<T>\n\t): Promise<T> {\n\t\tconst txPromise = (async () => {\n\t\t\tassert(!this.isClosed, 'db is closed')\n\t\t\tconst db = await this.getDb()\n\t\t\tconst tx = db.transaction(names, mode)\n\t\t\t// Commit failures may arrive before the callback finishes; observe them immediately\n\t\t\t// while preserving the rejection for the await below.\n\t\t\tconst done = tx.done\n\t\t\tdone.catch(noop)\n\t\t\ttry {\n\t\t\t\treturn await cb(tx)\n\t\t\t} finally {\n\t\t\t\t// let the transaction commit even if close() was called meanwhile, otherwise a\n\t\t\t\t// persist racing an unmount is silently rolled back\n\t\t\t\tawait done\n\t\t\t}\n\t\t})()\n\t\tthis.pendingTransactionSet.add(txPromise)\n\t\t// not `.finally()`: the promise it returns would re-reject with nobody observing it\n\t\tconst cleanup = () => this.pendingTransactionSet.delete(txPromise)\n\t\ttxPromise.then(cleanup, cleanup)\n\t\treturn txPromise\n\t}\n\n\tasync load({ sessionId }: { sessionId?: string } = {}) {\n\t\treturn await this.tx(\n\t\t\t'readonly',\n\t\t\t[Table.Records, Table.Schema, Table.SessionState],\n\t\t\tasync (tx) => {\n\t\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\t\t\t\tlet sessionStateSnapshot = sessionId\n\t\t\t\t\t? ((await sessionStateStore.get(sessionId)) as SessionStateSnapshotRow | undefined)\n\t\t\t\t\t\t\t?.snapshot\n\t\t\t\t\t: null\n\t\t\t\tif (!sessionStateSnapshot) {\n\t\t\t\t\t// get the most recent session state\n\t\t\t\t\tconst all = (await sessionStateStore.getAll()) as SessionStateSnapshotRow[]\n\t\t\t\t\tsessionStateSnapshot = all.sort((a, b) => a.updatedAt - b.updatedAt).pop()?.snapshot\n\t\t\t\t}\n\t\t\t\tconst result = {\n\t\t\t\t\trecords: await recordsStore.getAll(),\n\t\t\t\t\tschema: await schemaStore.get(Table.Schema),\n\t\t\t\t\tsessionStateSnapshot,\n\t\t\t\t} satisfies LoadResult\n\n\t\t\t\treturn result\n\t\t\t}\n\t\t)\n\t}\n\n\tasync storeChanges({\n\t\tschema,\n\t\tchanges,\n\t\tsessionId,\n\t\tsessionStateSnapshot,\n\t}: {\n\t\tschema: TLStoreSchema\n\t\tchanges: RecordsDiff<any>\n\t\tsessionId?: string | null\n\t\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n\t}) {\n\t\tawait this.tx('readwrite', [Table.Records, Table.Schema, Table.SessionState], async (tx) => {\n\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\n\t\t\tconst requests: Promise<unknown>[] = []\n\t\t\tfor (const [id, record] of Object.entries(changes.added)) {\n\t\t\t\trequests.push(recordsStore.put(record, id))\n\t\t\t}\n\n\t\t\tfor (const [_prev, updated] of Object.values(changes.updated)) {\n\t\t\t\trequests.push(recordsStore.put(updated, updated.id))\n\t\t\t}\n\n\t\t\tfor (const id of Object.keys(changes.removed)) {\n\t\t\t\trequests.push(recordsStore.delete(id))\n\t\t\t}\n\t\t\tawait Promise.all(requests)\n\n\t\t\tschemaStore.put(schema.serialize(), Table.Schema)\n\t\t\tif (sessionStateSnapshot && sessionId) {\n\t\t\t\tsessionStateStore.put(\n\t\t\t\t\t{\n\t\t\t\t\t\tsnapshot: sessionStateSnapshot,\n\t\t\t\t\t\tupdatedAt: Date.now(),\n\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t} satisfies SessionStateSnapshotRow,\n\t\t\t\t\tsessionId\n\t\t\t\t)\n\t\t\t} else if (sessionStateSnapshot || sessionId) {\n\t\t\t\tconsole.error('sessionStateSnapshot and instanceId must be provided together')\n\t\t\t}\n\t\t})\n\t}\n\n\tasync storeSnapshot({\n\t\tschema,\n\t\tsnapshot,\n\t\tsessionId,\n\t\tsessionStateSnapshot,\n\t}: {\n\t\tschema: TLStoreSchema\n\t\tsnapshot: SerializedStore<any>\n\t\tsessionId?: string | null\n\t\tsessionStateSnapshot?: TLSessionStateSnapshot | null\n\t}) {\n\t\tawait this.tx('readwrite', [Table.Records, Table.Schema, Table.SessionState], async (tx) => {\n\t\t\tconst recordsStore = tx.objectStore(Table.Records)\n\t\t\tconst schemaStore = tx.objectStore(Table.Schema)\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\n\t\t\tawait recordsStore.clear()\n\n\t\t\tawait Promise.all(\n\t\t\t\tObject.entries(snapshot).map(([id, record]) => recordsStore.put(record, id))\n\t\t\t)\n\n\t\t\tschemaStore.put(schema.serialize(), Table.Schema)\n\n\t\t\tif (sessionStateSnapshot && sessionId) {\n\t\t\t\tsessionStateStore.put(\n\t\t\t\t\t{\n\t\t\t\t\t\tsnapshot: sessionStateSnapshot,\n\t\t\t\t\t\tupdatedAt: Date.now(),\n\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t} satisfies SessionStateSnapshotRow,\n\t\t\t\t\tsessionId\n\t\t\t\t)\n\t\t\t} else if (sessionStateSnapshot || sessionId) {\n\t\t\t\tconsole.error('sessionStateSnapshot and instanceId must be provided together')\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Drop all but the most recently updated session state rows. The row for `keepSessionId` is never\n\t * deleted: it may be an old row that this tab restored its state from and has not rewritten yet,\n\t * and deleting it would make a later reload fall back to another tab's session state.\n\t */\n\tasync pruneSessions({ keepSessionId }: { keepSessionId?: string } = {}) {\n\t\tawait this.tx('readwrite', [Table.SessionState], async (tx) => {\n\t\t\tconst sessionStateStore = tx.objectStore(Table.SessionState)\n\t\t\tconst all = ((await sessionStateStore.getAll()) as SessionStateSnapshotRow[])\n\t\t\t\t.filter((row) => row.id !== keepSessionId)\n\t\t\t\t.sort((a, b) => a.updatedAt - b.updatedAt)\n\t\t\tconst toDelete = all.slice(0, Math.max(0, all.length - MAX_SESSION_STATE_ROWS))\n\t\t\tfor (const { id } of toDelete) {\n\t\t\t\tawait sessionStateStore.delete(id)\n\t\t\t}\n\t\t})\n\t}\n\n\tasync getAsset(assetId: string): Promise<File | undefined> {\n\t\treturn await this.tx('readonly', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\treturn await assetsStore.get(assetId)\n\t\t})\n\t}\n\n\tasync storeAsset(assetId: string, blob: File) {\n\t\tawait this.tx('readwrite', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\tawait assetsStore.put(blob, assetId)\n\t\t})\n\t}\n\n\tasync removeAssets(assetId: string[]) {\n\t\tawait this.tx('readwrite', [Table.Assets], async (tx) => {\n\t\t\tconst assetsStore = tx.objectStore(Table.Assets)\n\t\t\tfor (const id of assetId) {\n\t\t\t\tawait assetsStore.delete(id)\n\t\t\t}\n\t\t})\n\t}\n}\n\n/** @internal */\nexport function getAllIndexDbNames(): string[] {\n\tconst result = JSON.parse(getFromLocalStorage(dbNameIndexKey) || '[]') ?? []\n\tif (!Array.isArray(result)) {\n\t\treturn []\n\t}\n\treturn result\n}\n\nfunction addDbName(name: string) {\n\tconst all = new Set(getAllIndexDbNames())\n\tall.add(name)\n\tsetInLocalStorage(dbNameIndexKey, JSON.stringify([...all]))\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,7 @@ import {
7
7
  extractSessionStateFromLegacySnapshot,
8
8
  loadSessionStateSnapshotIntoStore
9
9
  } from "../../config/TLSessionStateSnapshot.mjs";
10
+ import { refreshPage } from "../runtime.mjs";
10
11
  import { showCantReadFromIndexDbAlert, showCantWriteToIndexDbAlert } from "./alerts.mjs";
11
12
  import { LocalIndexedDb } from "./LocalIndexedDb.mjs";
12
13
  const PERSIST_THROTTLE_MS = 350;
@@ -39,7 +40,6 @@ class TLLocalSyncClient {
39
40
  this.persistenceKey = persistenceKey;
40
41
  this.sessionId = sessionId;
41
42
  this.db = new LocalIndexedDb(persistenceKey);
42
- this.disposables.add(() => this.db.close());
43
43
  this.serializedSchema = this.store.schema.serialize();
44
44
  this.$sessionStateSnapshot = createSessionStateSnapshotSignal(this.store);
45
45
  this.disposables.add(
@@ -117,6 +117,47 @@ class TLLocalSyncClient {
117
117
  async connect(onLoad, onLoadError) {
118
118
  this.debug("connecting");
119
119
  let data;
120
+ const handleMessage = (msg2) => {
121
+ const res = this.store.schema.getMigrationsSince(msg2.schema);
122
+ if (!res.ok) {
123
+ this.isReloading = true;
124
+ const timeSinceInit = Date.now() - this.initTime;
125
+ if (timeSinceInit < 5e3) {
126
+ onLoadError(new Error("Schema mismatch, please close other tabs and reload the page"));
127
+ return false;
128
+ }
129
+ this.debug("reloading");
130
+ if (typeof window !== "undefined") refreshPage();
131
+ return false;
132
+ } else if (res.value.length > 0) {
133
+ this.debug("telling them to reload");
134
+ this.channel.postMessage({ type: "announce", schema: this.serializedSchema });
135
+ this.shouldDoFullDBWrite = true;
136
+ this.schedulePersist();
137
+ return true;
138
+ }
139
+ if (msg2.type === "diff") {
140
+ this.debug("applying diff");
141
+ transact(() => {
142
+ this.store.mergeRemoteChanges(() => {
143
+ this.store.applyDiff(msg2.changes);
144
+ });
145
+ });
146
+ }
147
+ return true;
148
+ };
149
+ let bufferedMessages = [];
150
+ this.channel.onmessage = ({ data: data2 }) => {
151
+ this.debug("got message", data2);
152
+ if (bufferedMessages) {
153
+ bufferedMessages.push(data2);
154
+ } else {
155
+ handleMessage(data2);
156
+ }
157
+ };
158
+ this.disposables.add(() => {
159
+ this.channel.close();
160
+ });
120
161
  try {
121
162
  data = await this.db.load({ sessionId: this.sessionId });
122
163
  } catch (error) {
@@ -154,42 +195,18 @@ class TLLocalSyncClient {
154
195
  });
155
196
  }
156
197
  }
157
- this.channel.onmessage = ({ data: data2 }) => {
158
- this.debug("got message", data2);
159
- const msg2 = data2;
160
- const res = this.store.schema.getMigrationsSince(msg2.schema);
161
- if (!res.ok) {
162
- const timeSinceInit = Date.now() - this.initTime;
163
- if (timeSinceInit < 5e3) {
164
- onLoadError(new Error("Schema mismatch, please close other tabs and reload the page"));
165
- return;
166
- }
167
- this.debug("reloading");
168
- this.isReloading = true;
169
- window?.location?.reload?.();
170
- return;
171
- } else if (res.value.length > 0) {
172
- this.debug("telling them to reload");
173
- this.channel.postMessage({ type: "announce", schema: this.serializedSchema });
174
- this.shouldDoFullDBWrite = true;
175
- this.persistIfNeeded();
176
- return;
177
- }
178
- if (msg2.type === "diff") {
179
- this.debug("applying diff");
180
- transact(() => {
181
- this.store.mergeRemoteChanges(() => {
182
- this.store.applyDiff(msg2.changes);
183
- });
184
- });
185
- }
186
- };
187
- this.channel.postMessage({ type: "announce", schema: this.serializedSchema });
188
- this.disposables.add(() => {
189
- this.channel.close();
190
- });
198
+ const messages = bufferedMessages;
199
+ bufferedMessages = null;
200
+ for (const message of messages) {
201
+ if (!handleMessage(message)) return;
202
+ }
191
203
  this.didLoad = true;
204
+ if (this.diffQueue.length > 0) this.schedulePersist();
205
+ this.channel.postMessage({ type: "announce", schema: this.serializedSchema });
192
206
  onLoad(this);
207
+ this.db.pruneSessions({ keepSessionId: this.sessionId }).catch((e) => {
208
+ this.debug("failed to prune session state rows", e);
209
+ });
193
210
  } catch (e) {
194
211
  this.debug("error loading data from store", e);
195
212
  if (this.didDispose) return;
@@ -198,14 +215,36 @@ class TLLocalSyncClient {
198
215
  }
199
216
  }
200
217
  close() {
218
+ if (this.didDispose) return;
201
219
  this.debug("closing");
202
220
  this.didDispose = true;
221
+ this.store._flushHistory();
203
222
  this.disposables.forEach((d) => d());
223
+ if (this.scheduledPersistTimeout) {
224
+ clearTimeout(this.scheduledPersistTimeout);
225
+ this.scheduledPersistTimeout = null;
226
+ }
204
227
  if (typeof window !== "undefined" && window.tlsync === this) {
205
228
  delete window.tlsync;
206
229
  }
230
+ this.flushAndCloseDb();
231
+ }
232
+ /**
233
+ * Flush queued changes so edits made in the last PERSIST_THROTTLE_MS before unmount aren't lost,
234
+ * then close the db. Never flushes before load (a full write then would wipe the db with an empty
235
+ * store), and only when something is queued: shouldDoFullDBWrite stays true until the first
236
+ * persist, so an unconditional flush would rewrite the whole document on every no-op close.
237
+ */
238
+ flushAndCloseDb() {
239
+ if (this.persistPromise) {
240
+ this.persistPromise.then(() => this.flushAndCloseDb());
241
+ return;
242
+ }
243
+ if (this.didLoad && this.diffQueue.length > 0) this.persistIfNeeded();
244
+ this.db.close();
207
245
  }
208
246
  isPersisting = false;
247
+ persistPromise = null;
209
248
  didLastWriteError = false;
210
249
  // eslint-disable-next-line no-restricted-globals
211
250
  scheduledPersistTimeout = null;
@@ -218,6 +257,7 @@ class TLLocalSyncClient {
218
257
  schedulePersist() {
219
258
  this.debug("schedulePersist", this.scheduledPersistTimeout);
220
259
  if (this.scheduledPersistTimeout) return;
260
+ if (this.didDispose) return;
221
261
  this.scheduledPersistTimeout = setTimeout(
222
262
  () => {
223
263
  this.scheduledPersistTimeout = null;
@@ -247,11 +287,12 @@ class TLLocalSyncClient {
247
287
  clearTimeout(this.scheduledPersistTimeout);
248
288
  this.scheduledPersistTimeout = null;
249
289
  }
290
+ if (!this.didLoad) return;
250
291
  if (this.isPersisting) return;
251
292
  if (this.isReloading) return;
252
293
  if (this.store.isPossiblyCorrupted()) return;
253
294
  if (this.shouldDoFullDBWrite || this.diffQueue.length > 0) {
254
- this.doPersist();
295
+ this.persistPromise = this.doPersist();
255
296
  }
256
297
  }
257
298
  /**
@@ -260,7 +301,6 @@ class TLLocalSyncClient {
260
301
  */
261
302
  async doPersist() {
262
303
  assert(!this.isPersisting, "persist already in progress");
263
- if (this.didDispose) return;
264
304
  this.isPersisting = true;
265
305
  this.debug("doPersist start");
266
306
  const diffQueue = this.diffQueue;
@@ -290,12 +330,13 @@ class TLLocalSyncClient {
290
330
  this.shouldDoFullDBWrite = true;
291
331
  this.didLastWriteError = true;
292
332
  console.error("failed to store changes in indexed db", e);
293
- showCantWriteToIndexDbAlert();
294
- if (typeof window !== "undefined") {
295
- window.location.reload();
333
+ if (!this.didDispose && typeof window !== "undefined") {
334
+ showCantWriteToIndexDbAlert();
335
+ refreshPage();
296
336
  }
297
337
  }
298
338
  this.isPersisting = false;
339
+ this.persistPromise = null;
299
340
  this.debug("doPersist end");
300
341
  this.schedulePersist();
301
342
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/utils/sync/TLLocalSyncClient.ts"],
4
- "sourcesContent": ["import { Signal, transact } from '@tldraw/state'\nimport { RecordsDiff, SerializedSchema, UnknownRecord, squashRecordDiffs } from '@tldraw/store'\nimport { TLStore } from '@tldraw/tlschema'\nimport { assert } from '@tldraw/utils'\nimport {\n\tTAB_ID,\n\tTLSessionStateSnapshot,\n\tcreateSessionStateSnapshotSignal,\n\textractSessionStateFromLegacySnapshot,\n\tloadSessionStateSnapshotIntoStore,\n} from '../../config/TLSessionStateSnapshot'\nimport { showCantReadFromIndexDbAlert, showCantWriteToIndexDbAlert } from './alerts'\nimport { LocalIndexedDb } from './LocalIndexedDb'\n\n/** How should we debounce persists? */\nconst PERSIST_THROTTLE_MS = 350\n/** If we're in an error state, how long should we wait before retrying a write? */\nconst PERSIST_RETRY_THROTTLE_MS = 10_000\n\nconst UPDATE_INSTANCE_STATE = Symbol('UPDATE_INSTANCE_STATE')\n\n/**\n * IMPORTANT!!!\n *\n * This is just a quick-n-dirty temporary solution that will be replaced with the remote sync client\n * once it has the db integrated\n */\n\ninterface SyncMessage {\n\ttype: 'diff'\n\tstoreId: string\n\tchanges: RecordsDiff<UnknownRecord>\n\tschema: SerializedSchema\n}\n\n// Sent by new clients when they connect\n// If another client is on the channel with a newer schema version\n// It will\ninterface AnnounceMessage {\n\ttype: 'announce'\n\tschema: SerializedSchema\n}\n\ntype Message = SyncMessage | AnnounceMessage\n\ntype UnpackPromise<T> = T extends Promise<infer U> ? U : T\n\nconst msg = (msg: Message) => msg\n\n/** @internal */\nexport class BroadcastChannelMock {\n\tonmessage?: (e: MessageEvent) => void\n\tconstructor(_name: string) {\n\t\t// noop\n\t}\n\tpostMessage(_msg: Message) {\n\t\t// noop\n\t}\n\tclose() {\n\t\t// noop\n\t}\n}\n\nconst BC = typeof BroadcastChannel === 'undefined' ? BroadcastChannelMock : BroadcastChannel\n\n/** @internal */\nexport class TLLocalSyncClient {\n\tprivate disposables = new Set<() => void>()\n\tprivate diffQueue: Array<RecordsDiff<UnknownRecord> | typeof UPDATE_INSTANCE_STATE> = []\n\tprivate didDispose = false\n\tprivate didLoad = false\n\tprivate shouldDoFullDBWrite = true\n\tprivate isReloading = false\n\treadonly persistenceKey: string\n\treadonly sessionId: string\n\treadonly serializedSchema: SerializedSchema\n\tprivate isDebugging = false\n\tprivate readonly documentTypes: ReadonlySet<string>\n\tprivate readonly $sessionStateSnapshot: Signal<TLSessionStateSnapshot | null>\n\t/** @internal */\n\treadonly db: LocalIndexedDb\n\n\tinitTime = Date.now()\n\tprivate debug(...args: any[]) {\n\t\tif (this.isDebugging) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.debug(...args)\n\t\t}\n\t}\n\tconstructor(\n\t\tpublic readonly store: TLStore,\n\t\t{\n\t\t\tpersistenceKey,\n\t\t\tsessionId = TAB_ID,\n\t\t\tonLoad,\n\t\t\tonLoadError,\n\t\t}: {\n\t\t\tpersistenceKey: string\n\t\t\tsessionId?: string\n\t\t\tonLoad(self: TLLocalSyncClient): void\n\t\t\tonLoadError(error: Error): void\n\t\t},\n\t\tpublic readonly channel = new BC(`tldraw-tab-sync-${persistenceKey}`)\n\t) {\n\t\tif (typeof window !== 'undefined') {\n\t\t\t;(window as any).tlsync = this\n\t\t}\n\t\tthis.persistenceKey = persistenceKey\n\t\tthis.sessionId = sessionId\n\t\tthis.db = new LocalIndexedDb(persistenceKey)\n\t\tthis.disposables.add(() => this.db.close())\n\n\t\tthis.serializedSchema = this.store.schema.serialize()\n\t\tthis.$sessionStateSnapshot = createSessionStateSnapshotSignal(this.store)\n\n\t\tthis.disposables.add(\n\t\t\t// Set up a subscription to changes from the store: When\n\t\t\t// the store changes (and if the change was made by the user)\n\t\t\t// then immediately send the diff to other tabs via postMessage\n\t\t\t// and schedule a persist.\n\t\t\tstore.listen(\n\t\t\t\t({ changes }) => {\n\t\t\t\t\tthis.diffQueue.push(changes)\n\t\t\t\t\tthis.channel.postMessage(\n\t\t\t\t\t\tmsg({\n\t\t\t\t\t\t\ttype: 'diff',\n\t\t\t\t\t\t\tstoreId: this.store.id,\n\t\t\t\t\t\t\tchanges,\n\t\t\t\t\t\t\tschema: this.serializedSchema,\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ source: 'user', scope: 'document' }\n\t\t\t)\n\t\t)\n\t\tthis.disposables.add(\n\t\t\tstore.listen(\n\t\t\t\t() => {\n\t\t\t\t\tthis.diffQueue.push(UPDATE_INSTANCE_STATE)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ scope: 'session' }\n\t\t\t)\n\t\t)\n\n\t\tif (typeof window !== 'undefined') {\n\t\t\t// Persists are throttled, so without this the last PERSIST_THROTTLE_MS of edits are lost\n\t\t\t// whenever the tab is closed or reloaded. `pagehide` is the last reliable event before\n\t\t\t// unload on desktop; on mobile the page can be discarded without it firing, so we also\n\t\t\t// flush when the tab is hidden.\n\t\t\tconst flush = () => {\n\t\t\t\t// Before the initial load completes a full write would overwrite the saved\n\t\t\t\t// document with an empty store.\n\t\t\t\tif (!this.didLoad) return\n\t\t\t\tthis.persistIfNeeded()\n\t\t\t}\n\t\t\tconst onVisibilityChange = () => {\n\t\t\t\tif (document.visibilityState === 'hidden') flush()\n\t\t\t}\n\t\t\twindow.addEventListener('pagehide', flush)\n\t\t\tdocument.addEventListener('visibilitychange', onVisibilityChange)\n\t\t\tthis.disposables.add(() => {\n\t\t\t\twindow.removeEventListener('pagehide', flush)\n\t\t\t\tdocument.removeEventListener('visibilitychange', onVisibilityChange)\n\t\t\t})\n\t\t}\n\n\t\tthis.connect(onLoad, onLoadError)\n\n\t\tthis.documentTypes = new Set(\n\t\t\tObject.values(this.store.schema.types)\n\t\t\t\t.filter((t) => t.scope === 'document')\n\t\t\t\t.map((t) => t.typeName)\n\t\t)\n\t}\n\n\tprivate async connect(onLoad: (client: this) => void, onLoadError: (error: Error) => void) {\n\t\tthis.debug('connecting')\n\t\tlet data: UnpackPromise<ReturnType<LocalIndexedDb['load']>> | undefined\n\n\t\ttry {\n\t\t\tdata = await this.db.load({ sessionId: this.sessionId })\n\t\t} catch (error: any) {\n\t\t\tonLoadError(error)\n\t\t\tshowCantReadFromIndexDbAlert()\n\t\t\treturn\n\t\t}\n\n\t\tthis.debug('loaded data from store', data, 'didDispose', this.didDispose)\n\t\tif (this.didDispose) return\n\n\t\ttry {\n\t\t\tif (data) {\n\t\t\t\tconst documentSnapshot = Object.fromEntries(data.records.map((r) => [r.id, r]))\n\t\t\t\tconst sessionStateSnapshot =\n\t\t\t\t\tdata.sessionStateSnapshot ?? extractSessionStateFromLegacySnapshot(documentSnapshot)\n\t\t\t\tconst migrationResult = this.store.schema.migrateStoreSnapshot({\n\t\t\t\t\tstore: documentSnapshot,\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\tschema: data.schema ?? this.store.schema.serializeEarliestVersion(),\n\t\t\t\t})\n\n\t\t\t\tif (migrationResult.type === 'error') {\n\t\t\t\t\tconsole.error('failed to migrate store', migrationResult)\n\t\t\t\t\tonLoadError(new Error(`Failed to migrate store: ${migrationResult.reason}`))\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst records = Object.values(migrationResult.value).filter((r) =>\n\t\t\t\t\tthis.documentTypes.has(r.typeName)\n\t\t\t\t)\n\t\t\t\tif (records.length > 0) {\n\t\t\t\t\t// 3. Merge the changes into the REAL STORE\n\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t// Calling put will validate the records!\n\t\t\t\t\t\tthis.store.put(records, 'initialize')\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tif (sessionStateSnapshot) {\n\t\t\t\t\tloadSessionStateSnapshotIntoStore(this.store, sessionStateSnapshot, {\n\t\t\t\t\t\tforceOverwrite: true,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.channel.onmessage = ({ data }) => {\n\t\t\t\tthis.debug('got message', data)\n\t\t\t\tconst msg = data as Message\n\t\t\t\t// if their schema is earlier than ours, we need to tell them so they can refresh\n\t\t\t\t// if their schema is later than ours, we need to refresh\n\t\t\t\tconst res = this.store.schema.getMigrationsSince(msg.schema)\n\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\t// we are older, refresh\n\t\t\t\t\t// but add a safety check to make sure we don't get in an infinite loop\n\t\t\t\t\tconst timeSinceInit = Date.now() - this.initTime\n\t\t\t\t\tif (timeSinceInit < 5000) {\n\t\t\t\t\t\t// This tab was just reloaded, but is out of date compared to other tabs.\n\t\t\t\t\t\t// Not expecting this to ever happen. It should only happen if we roll back a release that incremented\n\t\t\t\t\t\t// the schema version (which we should never do)\n\t\t\t\t\t\t// Or maybe during development if you have multiple local tabs open running the app on prod mode and you\n\t\t\t\t\t\t// check out an older commit. Dev server should be fine.\n\t\t\t\t\t\tonLoadError(new Error('Schema mismatch, please close other tabs and reload the page'))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tthis.debug('reloading')\n\t\t\t\t\tthis.isReloading = true\n\t\t\t\t\twindow?.location?.reload?.()\n\t\t\t\t\treturn\n\t\t\t\t} else if (res.value.length > 0) {\n\t\t\t\t\t// they are older, tell them to refresh and not write any more data\n\t\t\t\t\tthis.debug('telling them to reload')\n\t\t\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\t\t\t// schedule a full db write in case they wrote data anyway\n\t\t\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\t\t\tthis.persistIfNeeded()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// otherwise, all good, same version :)\n\t\t\t\tif (msg.type === 'diff') {\n\t\t\t\t\tthis.debug('applying diff')\n\t\t\t\t\ttransact(() => {\n\t\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t\tthis.store.applyDiff(msg.changes as any)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\tthis.disposables.add(() => {\n\t\t\t\tthis.channel.close()\n\t\t\t})\n\t\t\tthis.didLoad = true\n\t\t\tonLoad(this)\n\t\t} catch (e: any) {\n\t\t\tthis.debug('error loading data from store', e)\n\t\t\tif (this.didDispose) return\n\t\t\tonLoadError(e)\n\t\t\treturn\n\t\t}\n\t}\n\n\tclose() {\n\t\tthis.debug('closing')\n\t\tthis.didDispose = true\n\t\tthis.disposables.forEach((d) => d())\n\t\tif (typeof window !== 'undefined' && (window as any).tlsync === this) {\n\t\t\tdelete (window as any).tlsync\n\t\t}\n\t}\n\n\tprivate isPersisting = false\n\tprivate didLastWriteError = false\n\t// eslint-disable-next-line no-restricted-globals\n\tprivate scheduledPersistTimeout: ReturnType<typeof setTimeout> | null = null\n\n\t/**\n\t * Schedule a persist. Persists don't happen immediately: they are throttled to avoid writing too\n\t * often, and will retry if failed.\n\t *\n\t * @internal\n\t */\n\tprivate schedulePersist() {\n\t\tthis.debug('schedulePersist', this.scheduledPersistTimeout)\n\t\tif (this.scheduledPersistTimeout) return\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tthis.scheduledPersistTimeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.scheduledPersistTimeout = null\n\t\t\t\tthis.persistIfNeeded()\n\t\t\t},\n\t\t\tthis.didLastWriteError ? PERSIST_RETRY_THROTTLE_MS : PERSIST_THROTTLE_MS\n\t\t)\n\t}\n\n\t/**\n\t * Persist to IndexedDB only under certain circumstances:\n\t *\n\t * - If we're not already persisting\n\t * - If we're not reloading the page\n\t * - And we have something to persist (a full db write scheduled or changes in the diff queue)\n\t *\n\t * @internal\n\t */\n\tprivate persistIfNeeded() {\n\t\tthis.debug('persistIfNeeded', {\n\t\t\tisPersisting: this.isPersisting,\n\t\t\tisReloading: this.isReloading,\n\t\t\tshouldDoFullDBWrite: this.shouldDoFullDBWrite,\n\t\t\tdiffQueueLength: this.diffQueue.length,\n\t\t\tstoreIsPossiblyCorrupt: this.store.isPossiblyCorrupted(),\n\t\t})\n\n\t\t// if we've scheduled a persist for the future, that's no longer needed\n\t\tif (this.scheduledPersistTimeout) {\n\t\t\tclearTimeout(this.scheduledPersistTimeout)\n\t\t\tthis.scheduledPersistTimeout = null\n\t\t}\n\n\t\t// if a persist is already in progress, we don't need to do anything -\n\t\t// if there are still outstanding changes once it's finished, it'll\n\t\t// schedule another persist\n\t\tif (this.isPersisting) return\n\n\t\t// if we're reloading the page, it's because there's a newer client\n\t\t// present so lets not overwrite their changes\n\t\tif (this.isReloading) return\n\n\t\t// if the store is possibly corrupted, we don't want to persist\n\t\tif (this.store.isPossiblyCorrupted()) return\n\n\t\t// if we're scheduled for a full write or if we have changes outstanding, let's persist them!\n\t\tif (this.shouldDoFullDBWrite || this.diffQueue.length > 0) {\n\t\t\tthis.doPersist()\n\t\t}\n\t}\n\n\t/**\n\t * Actually persist to IndexedDB. If the write fails, then we'll retry with a full db write after\n\t * a short delay.\n\t */\n\tprivate async doPersist() {\n\t\tassert(!this.isPersisting, 'persist already in progress')\n\t\tif (this.didDispose) return\n\t\tthis.isPersisting = true\n\n\t\tthis.debug('doPersist start')\n\n\t\t// instantly empty the diff queue, but keep our own copy of it. this way\n\t\t// diffs that come in during the persist will still get tracked\n\t\tconst diffQueue = this.diffQueue\n\t\tthis.diffQueue = []\n\n\t\ttry {\n\t\t\tif (this.shouldDoFullDBWrite) {\n\t\t\t\tthis.shouldDoFullDBWrite = false\n\t\t\t\tawait this.db.storeSnapshot({\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsnapshot: this.store.serialize(),\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tconst diffs = squashRecordDiffs(\n\t\t\t\t\tdiffQueue.filter((d): d is RecordsDiff<UnknownRecord> => d !== UPDATE_INSTANCE_STATE)\n\t\t\t\t)\n\t\t\t\tawait this.db.storeChanges({\n\t\t\t\t\tchanges: diffs,\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t}\n\t\t\tthis.didLastWriteError = false\n\t\t} catch (e) {\n\t\t\t// set this.shouldDoFullDBWrite because we clear the diffQueue no matter what,\n\t\t\t// so if this is just a temporary error, we will still persist all changes\n\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\tthis.didLastWriteError = true\n\t\t\tconsole.error('failed to store changes in indexed db', e)\n\n\t\t\tshowCantWriteToIndexDbAlert()\n\t\t\tif (typeof window !== 'undefined') {\n\t\t\t\t// adios\n\t\t\t\twindow.location.reload()\n\t\t\t}\n\t\t}\n\n\t\tthis.isPersisting = false\n\t\tthis.debug('doPersist end')\n\n\t\t// changes might have come in between when we started the persist and\n\t\t// now. we request another persist so any new changes can get written\n\t\tthis.schedulePersist()\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Signal, transact } from '@tldraw/state'\nimport { RecordsDiff, SerializedSchema, UnknownRecord, squashRecordDiffs } from '@tldraw/store'\nimport { TLStore } from '@tldraw/tlschema'\nimport { assert } from '@tldraw/utils'\nimport {\n\tTAB_ID,\n\tTLSessionStateSnapshot,\n\tcreateSessionStateSnapshotSignal,\n\textractSessionStateFromLegacySnapshot,\n\tloadSessionStateSnapshotIntoStore,\n} from '../../config/TLSessionStateSnapshot'\nimport { refreshPage } from '../runtime'\nimport { showCantReadFromIndexDbAlert, showCantWriteToIndexDbAlert } from './alerts'\nimport { LocalIndexedDb } from './LocalIndexedDb'\n\n/** How should we debounce persists? */\nconst PERSIST_THROTTLE_MS = 350\n/** If we're in an error state, how long should we wait before retrying a write? */\nconst PERSIST_RETRY_THROTTLE_MS = 10_000\n\nconst UPDATE_INSTANCE_STATE = Symbol('UPDATE_INSTANCE_STATE')\n\n/**\n * IMPORTANT!!!\n *\n * This is just a quick-n-dirty temporary solution that will be replaced with the remote sync client\n * once it has the db integrated\n */\n\ninterface SyncMessage {\n\ttype: 'diff'\n\tstoreId: string\n\tchanges: RecordsDiff<UnknownRecord>\n\tschema: SerializedSchema\n}\n\n// Sent by new clients when they connect\n// If another client is on the channel with a newer schema version\n// It will\ninterface AnnounceMessage {\n\ttype: 'announce'\n\tschema: SerializedSchema\n}\n\ntype Message = SyncMessage | AnnounceMessage\n\ntype UnpackPromise<T> = T extends Promise<infer U> ? U : T\n\nconst msg = (msg: Message) => msg\n\n/** @internal */\nexport class BroadcastChannelMock {\n\tonmessage?: (e: MessageEvent) => void\n\tconstructor(_name: string) {\n\t\t// noop\n\t}\n\tpostMessage(_msg: Message) {\n\t\t// noop\n\t}\n\tclose() {\n\t\t// noop\n\t}\n}\n\nconst BC = typeof BroadcastChannel === 'undefined' ? BroadcastChannelMock : BroadcastChannel\n\n/** @internal */\nexport class TLLocalSyncClient {\n\tprivate disposables = new Set<() => void>()\n\tprivate diffQueue: Array<RecordsDiff<UnknownRecord> | typeof UPDATE_INSTANCE_STATE> = []\n\tprivate didDispose = false\n\tprivate didLoad = false\n\tprivate shouldDoFullDBWrite = true\n\tprivate isReloading = false\n\treadonly persistenceKey: string\n\treadonly sessionId: string\n\treadonly serializedSchema: SerializedSchema\n\tprivate isDebugging = false\n\tprivate readonly documentTypes: ReadonlySet<string>\n\tprivate readonly $sessionStateSnapshot: Signal<TLSessionStateSnapshot | null>\n\t/** @internal */\n\treadonly db: LocalIndexedDb\n\n\tinitTime = Date.now()\n\tprivate debug(...args: any[]) {\n\t\tif (this.isDebugging) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.debug(...args)\n\t\t}\n\t}\n\tconstructor(\n\t\tpublic readonly store: TLStore,\n\t\t{\n\t\t\tpersistenceKey,\n\t\t\tsessionId = TAB_ID,\n\t\t\tonLoad,\n\t\t\tonLoadError,\n\t\t}: {\n\t\t\tpersistenceKey: string\n\t\t\tsessionId?: string\n\t\t\tonLoad(self: TLLocalSyncClient): void\n\t\t\tonLoadError(error: Error): void\n\t\t},\n\t\tpublic readonly channel = new BC(`tldraw-tab-sync-${persistenceKey}`)\n\t) {\n\t\tif (typeof window !== 'undefined') {\n\t\t\t;(window as any).tlsync = this\n\t\t}\n\t\tthis.persistenceKey = persistenceKey\n\t\tthis.sessionId = sessionId\n\t\tthis.db = new LocalIndexedDb(persistenceKey)\n\n\t\tthis.serializedSchema = this.store.schema.serialize()\n\t\tthis.$sessionStateSnapshot = createSessionStateSnapshotSignal(this.store)\n\n\t\tthis.disposables.add(\n\t\t\t// Set up a subscription to changes from the store: When\n\t\t\t// the store changes (and if the change was made by the user)\n\t\t\t// then immediately send the diff to other tabs via postMessage\n\t\t\t// and schedule a persist.\n\t\t\tstore.listen(\n\t\t\t\t({ changes }) => {\n\t\t\t\t\tthis.diffQueue.push(changes)\n\t\t\t\t\tthis.channel.postMessage(\n\t\t\t\t\t\tmsg({\n\t\t\t\t\t\t\ttype: 'diff',\n\t\t\t\t\t\t\tstoreId: this.store.id,\n\t\t\t\t\t\t\tchanges,\n\t\t\t\t\t\t\tschema: this.serializedSchema,\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ source: 'user', scope: 'document' }\n\t\t\t)\n\t\t)\n\t\tthis.disposables.add(\n\t\t\tstore.listen(\n\t\t\t\t() => {\n\t\t\t\t\tthis.diffQueue.push(UPDATE_INSTANCE_STATE)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ scope: 'session' }\n\t\t\t)\n\t\t)\n\n\t\tif (typeof window !== 'undefined') {\n\t\t\t// Persists are throttled, so without this the last PERSIST_THROTTLE_MS of edits are lost\n\t\t\t// whenever the tab is closed or reloaded. `pagehide` is the last reliable event before\n\t\t\t// unload on desktop; on mobile the page can be discarded without it firing, so we also\n\t\t\t// flush when the tab is hidden.\n\t\t\tconst flush = () => {\n\t\t\t\t// Before the initial load completes a full write would overwrite the saved\n\t\t\t\t// document with an empty store.\n\t\t\t\tif (!this.didLoad) return\n\t\t\t\tthis.persistIfNeeded()\n\t\t\t}\n\t\t\tconst onVisibilityChange = () => {\n\t\t\t\tif (document.visibilityState === 'hidden') flush()\n\t\t\t}\n\t\t\twindow.addEventListener('pagehide', flush)\n\t\t\tdocument.addEventListener('visibilitychange', onVisibilityChange)\n\t\t\tthis.disposables.add(() => {\n\t\t\t\twindow.removeEventListener('pagehide', flush)\n\t\t\t\tdocument.removeEventListener('visibilitychange', onVisibilityChange)\n\t\t\t})\n\t\t}\n\n\t\tthis.connect(onLoad, onLoadError)\n\n\t\tthis.documentTypes = new Set(\n\t\t\tObject.values(this.store.schema.types)\n\t\t\t\t.filter((t) => t.scope === 'document')\n\t\t\t\t.map((t) => t.typeName)\n\t\t)\n\t}\n\n\tprivate async connect(onLoad: (client: this) => void, onLoadError: (error: Error) => void) {\n\t\tthis.debug('connecting')\n\t\tlet data: UnpackPromise<ReturnType<LocalIndexedDb['load']>> | undefined\n\n\t\tconst handleMessage = (msg: Message) => {\n\t\t\t// if their schema is earlier than ours, we need to tell them so they can refresh\n\t\t\t// if their schema is later than ours, we need to refresh\n\t\t\tconst res = this.store.schema.getMigrationsSince(msg.schema)\n\n\t\t\tif (!res.ok) {\n\t\t\t\tthis.isReloading = true\n\t\t\t\t// we are older, refresh\n\t\t\t\t// but add a safety check to make sure we don't get in an infinite loop\n\t\t\t\tconst timeSinceInit = Date.now() - this.initTime\n\t\t\t\tif (timeSinceInit < 5000) {\n\t\t\t\t\t// This tab was just reloaded, but is out of date compared to other tabs.\n\t\t\t\t\t// Not expecting this to ever happen. It should only happen if we roll back a release that incremented\n\t\t\t\t\t// the schema version (which we should never do)\n\t\t\t\t\t// Or maybe during development if you have multiple local tabs open running the app on prod mode and you\n\t\t\t\t\t// check out an older commit. Dev server should be fine.\n\t\t\t\t\tonLoadError(new Error('Schema mismatch, please close other tabs and reload the page'))\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tthis.debug('reloading')\n\t\t\t\tif (typeof window !== 'undefined') refreshPage()\n\t\t\t\treturn false\n\t\t\t} else if (res.value.length > 0) {\n\t\t\t\t// they are older, tell them to refresh and not write any more data\n\t\t\t\tthis.debug('telling them to reload')\n\t\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\t\t// schedule a full db write in case they wrote data anyway\n\t\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\t\tthis.schedulePersist()\n\t\t\t\treturn true\n\t\t\t}\n\t\t\t// otherwise, all good, same version :)\n\t\t\tif (msg.type === 'diff') {\n\t\t\t\tthis.debug('applying diff')\n\t\t\t\ttransact(() => {\n\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\tthis.store.applyDiff(msg.changes as any)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\n\t\t// Listen from the start and buffer until we've loaded: diffs other tabs broadcast while\n\t\t// we're still reading from IndexedDB would otherwise be dropped, then erased by our first\n\t\t// (full) db write.\n\t\tlet bufferedMessages: Message[] | null = []\n\t\tthis.channel.onmessage = ({ data }) => {\n\t\t\tthis.debug('got message', data)\n\t\t\tif (bufferedMessages) {\n\t\t\t\tbufferedMessages.push(data as Message)\n\t\t\t} else {\n\t\t\t\thandleMessage(data as Message)\n\t\t\t}\n\t\t}\n\t\tthis.disposables.add(() => {\n\t\t\tthis.channel.close()\n\t\t})\n\n\t\ttry {\n\t\t\tdata = await this.db.load({ sessionId: this.sessionId })\n\t\t} catch (error: any) {\n\t\t\tonLoadError(error)\n\t\t\tshowCantReadFromIndexDbAlert()\n\t\t\treturn\n\t\t}\n\n\t\tthis.debug('loaded data from store', data, 'didDispose', this.didDispose)\n\t\tif (this.didDispose) return\n\n\t\ttry {\n\t\t\tif (data) {\n\t\t\t\tconst documentSnapshot = Object.fromEntries(data.records.map((r) => [r.id, r]))\n\t\t\t\tconst sessionStateSnapshot =\n\t\t\t\t\tdata.sessionStateSnapshot ?? extractSessionStateFromLegacySnapshot(documentSnapshot)\n\t\t\t\tconst migrationResult = this.store.schema.migrateStoreSnapshot({\n\t\t\t\t\tstore: documentSnapshot,\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\tschema: data.schema ?? this.store.schema.serializeEarliestVersion(),\n\t\t\t\t})\n\n\t\t\t\tif (migrationResult.type === 'error') {\n\t\t\t\t\tconsole.error('failed to migrate store', migrationResult)\n\t\t\t\t\tonLoadError(new Error(`Failed to migrate store: ${migrationResult.reason}`))\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst records = Object.values(migrationResult.value).filter((r) =>\n\t\t\t\t\tthis.documentTypes.has(r.typeName)\n\t\t\t\t)\n\t\t\t\tif (records.length > 0) {\n\t\t\t\t\t// 3. Merge the changes into the REAL STORE\n\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t// Calling put will validate the records!\n\t\t\t\t\t\tthis.store.put(records, 'initialize')\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tif (sessionStateSnapshot) {\n\t\t\t\t\tloadSessionStateSnapshotIntoStore(this.store, sessionStateSnapshot, {\n\t\t\t\t\t\tforceOverwrite: true,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Older-schema announcements schedule writes, so drain every diff before those writes\n\t\t\t// or an onLoad callback that immediately closes the client can capture a snapshot.\n\t\t\tconst messages = bufferedMessages\n\t\t\tbufferedMessages = null\n\t\t\tfor (const message of messages) {\n\t\t\t\tif (!handleMessage(message)) return\n\t\t\t}\n\t\t\tthis.didLoad = true\n\t\t\tif (this.diffQueue.length > 0) this.schedulePersist()\n\n\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\tonLoad(this)\n\n\t\t\t// Session state rows accumulate one per tab, so trim them on load. Pruning is best-effort\n\t\t\t// and must never turn a successful load into a failure.\n\t\t\tthis.db.pruneSessions({ keepSessionId: this.sessionId }).catch((e: unknown) => {\n\t\t\t\tthis.debug('failed to prune session state rows', e)\n\t\t\t})\n\t\t} catch (e: any) {\n\t\t\tthis.debug('error loading data from store', e)\n\t\t\tif (this.didDispose) return\n\t\t\tonLoadError(e)\n\t\t\treturn\n\t\t}\n\t}\n\n\tclose() {\n\t\tif (this.didDispose) return\n\t\tthis.debug('closing')\n\t\tthis.didDispose = true\n\t\t// Store listeners normally run on the next frame; collect the last edits before removal.\n\t\tthis.store._flushHistory()\n\t\tthis.disposables.forEach((d) => d())\n\t\tif (this.scheduledPersistTimeout) {\n\t\t\tclearTimeout(this.scheduledPersistTimeout)\n\t\t\tthis.scheduledPersistTimeout = null\n\t\t}\n\t\tif (typeof window !== 'undefined' && (window as any).tlsync === this) {\n\t\t\tdelete (window as any).tlsync\n\t\t}\n\t\tthis.flushAndCloseDb()\n\t}\n\n\t/**\n\t * Flush queued changes so edits made in the last PERSIST_THROTTLE_MS before unmount aren't lost,\n\t * then close the db. Never flushes before load (a full write then would wipe the db with an empty\n\t * store), and only when something is queued: shouldDoFullDBWrite stays true until the first\n\t * persist, so an unconditional flush would rewrite the whole document on every no-op close.\n\t */\n\tprivate flushAndCloseDb() {\n\t\t// A persist in flight can't be joined (persistIfNeeded bails while one is running) and,\n\t\t// now that we're disposed, won't schedule a follow-up. Edits queued during that write\n\t\t// would be lost if the db closed now, so wait for it and flush again.\n\t\tif (this.persistPromise) {\n\t\t\tthis.persistPromise.then(() => this.flushAndCloseDb())\n\t\t\treturn\n\t\t}\n\t\tif (this.didLoad && this.diffQueue.length > 0) this.persistIfNeeded()\n\t\t// the db waits for the transaction the flush just opened before actually closing\n\t\tthis.db.close()\n\t}\n\n\tprivate isPersisting = false\n\tprivate persistPromise: Promise<void> | null = null\n\tprivate didLastWriteError = false\n\t// eslint-disable-next-line no-restricted-globals\n\tprivate scheduledPersistTimeout: ReturnType<typeof setTimeout> | null = null\n\n\t/**\n\t * Schedule a persist. Persists don't happen immediately: they are throttled to avoid writing too\n\t * often, and will retry if failed.\n\t *\n\t * @internal\n\t */\n\tprivate schedulePersist() {\n\t\tthis.debug('schedulePersist', this.scheduledPersistTimeout)\n\t\tif (this.scheduledPersistTimeout) return\n\t\t// A deferred persist would try to write to a database that is closing or already closed.\n\t\tif (this.didDispose) return\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tthis.scheduledPersistTimeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.scheduledPersistTimeout = null\n\t\t\t\tthis.persistIfNeeded()\n\t\t\t},\n\t\t\tthis.didLastWriteError ? PERSIST_RETRY_THROTTLE_MS : PERSIST_THROTTLE_MS\n\t\t)\n\t}\n\n\t/**\n\t * Persist to IndexedDB only under certain circumstances:\n\t *\n\t * - If we're not already persisting\n\t * - If we're not reloading the page\n\t * - And we have something to persist (a full db write scheduled or changes in the diff queue)\n\t *\n\t * @internal\n\t */\n\tprivate persistIfNeeded() {\n\t\tthis.debug('persistIfNeeded', {\n\t\t\tisPersisting: this.isPersisting,\n\t\t\tisReloading: this.isReloading,\n\t\t\tshouldDoFullDBWrite: this.shouldDoFullDBWrite,\n\t\t\tdiffQueueLength: this.diffQueue.length,\n\t\t\tstoreIsPossiblyCorrupt: this.store.isPossiblyCorrupted(),\n\t\t})\n\n\t\t// if we've scheduled a persist for the future, that's no longer needed\n\t\tif (this.scheduledPersistTimeout) {\n\t\t\tclearTimeout(this.scheduledPersistTimeout)\n\t\t\tthis.scheduledPersistTimeout = null\n\t\t}\n\n\t\tif (!this.didLoad) return\n\n\t\t// if a persist is already in progress, we don't need to do anything -\n\t\t// if there are still outstanding changes once it's finished, it'll\n\t\t// schedule another persist\n\t\tif (this.isPersisting) return\n\n\t\t// if we're reloading the page, it's because there's a newer client\n\t\t// present so lets not overwrite their changes\n\t\tif (this.isReloading) return\n\n\t\t// if the store is possibly corrupted, we don't want to persist\n\t\tif (this.store.isPossiblyCorrupted()) return\n\n\t\t// if we're scheduled for a full write or if we have changes outstanding, let's persist them!\n\t\tif (this.shouldDoFullDBWrite || this.diffQueue.length > 0) {\n\t\t\tthis.persistPromise = this.doPersist()\n\t\t}\n\t}\n\n\t/**\n\t * Actually persist to IndexedDB. If the write fails, then we'll retry with a full db write after\n\t * a short delay.\n\t */\n\tprivate async doPersist() {\n\t\tassert(!this.isPersisting, 'persist already in progress')\n\t\tthis.isPersisting = true\n\n\t\tthis.debug('doPersist start')\n\n\t\t// instantly empty the diff queue, but keep our own copy of it. this way\n\t\t// diffs that come in during the persist will still get tracked\n\t\tconst diffQueue = this.diffQueue\n\t\tthis.diffQueue = []\n\n\t\ttry {\n\t\t\tif (this.shouldDoFullDBWrite) {\n\t\t\t\tthis.shouldDoFullDBWrite = false\n\t\t\t\tawait this.db.storeSnapshot({\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsnapshot: this.store.serialize(),\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tconst diffs = squashRecordDiffs(\n\t\t\t\t\tdiffQueue.filter((d): d is RecordsDiff<UnknownRecord> => d !== UPDATE_INSTANCE_STATE)\n\t\t\t\t)\n\t\t\t\tawait this.db.storeChanges({\n\t\t\t\t\tchanges: diffs,\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t}\n\t\t\tthis.didLastWriteError = false\n\t\t} catch (e) {\n\t\t\t// set this.shouldDoFullDBWrite because we clear the diffQueue no matter what,\n\t\t\t// so if this is just a temporary error, we will still persist all changes\n\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\tthis.didLastWriteError = true\n\t\t\tconsole.error('failed to store changes in indexed db', e)\n\n\t\t\tif (!this.didDispose && typeof window !== 'undefined') {\n\t\t\t\tshowCantWriteToIndexDbAlert()\n\t\t\t\trefreshPage()\n\t\t\t}\n\t\t}\n\n\t\tthis.isPersisting = false\n\t\tthis.persistPromise = null\n\t\tthis.debug('doPersist end')\n\n\t\t// changes might have come in between when we started the persist and\n\t\t// now. we request another persist so any new changes can get written\n\t\tthis.schedulePersist()\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["msg", "data"]
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "5.5.0-next.f9308d324e1c";
1
+ const version = "5.5.0";
2
2
  const publishDates = {
3
3
  major: "2026-05-06T16:28:18.473Z",
4
- minor: "2026-09-04T10:04:53.550Z",
5
- patch: "2026-09-04T10:04:53.550Z"
4
+ minor: "2026-09-30T10:03:27.724Z",
5
+ patch: "2026-09-30T10:03:27.724Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.5.0-next.f9308d324e1c'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-09-04T10:04:53.550Z',\n\tpatch: '2026-09-04T10:04:53.550Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.5.0'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-09-30T10:03:27.724Z',\n\tpatch: '2026-09-30T10:03:27.724Z',\n}\n"],
5
5
  "mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
package/editor.css CHANGED
@@ -650,7 +650,15 @@ input,
650
650
  overflow: hidden;
651
651
  }
652
652
 
653
- .tl-text-input::selection {
653
+ /**
654
+ * Only when there's actually a highlight to style. ProseMirror hides the native selection whenever
655
+ * it holds a NodeSelection — triple-clicking a list's bullet selects the whole list, for one — by
656
+ * zeroing the ::selection background and nothing else. Painting the selected-contrast color with
657
+ * no highlight behind it leaves the text invisible against the shape, and `inherit` can't undo it:
658
+ * highlight pseudo-elements inherit from the ancestor's ::selection, so it just picks the same
659
+ * color back up.
660
+ */
661
+ .tl-text-input:not(:has(.ProseMirror-hideselection))::selection {
654
662
  background: var(--tl-color-selected);
655
663
  color: var(--tl-color-selected-contrast);
656
664
  text-shadow: none;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
3
  "description": "tldraw infinite canvas SDK (editor).",
4
- "version": "5.5.0-next.f9308d324e1c",
4
+ "version": "5.5.0",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -42,7 +42,6 @@
42
42
  "scripts": {
43
43
  "test-ci": "yarn run -T vitest run --passWithNoTests",
44
44
  "test": "yarn run -T vitest --passWithNoTests",
45
- "benchmark": "yarn run -T tsx ./internal/scripts/benchmark.ts",
46
45
  "test-coverage": "yarn run -T vitest run --coverage --passWithNoTests",
47
46
  "build": "yarn run -T tsx ../../internal/scripts/build-package.ts",
48
47
  "build-api": "yarn run -T tsx ../../internal/scripts/build-api.ts",
@@ -55,12 +54,12 @@
55
54
  "@tiptap/core": "^3.12.1",
56
55
  "@tiptap/pm": "^3.12.1",
57
56
  "@tiptap/react": "^3.12.1",
58
- "@tldraw/state": "5.5.0-next.f9308d324e1c",
59
- "@tldraw/state-react": "5.5.0-next.f9308d324e1c",
60
- "@tldraw/store": "5.5.0-next.f9308d324e1c",
61
- "@tldraw/tlschema": "5.5.0-next.f9308d324e1c",
62
- "@tldraw/utils": "5.5.0-next.f9308d324e1c",
63
- "@tldraw/validate": "5.5.0-next.f9308d324e1c",
57
+ "@tldraw/state": "5.5.0",
58
+ "@tldraw/state-react": "5.5.0",
59
+ "@tldraw/store": "5.5.0",
60
+ "@tldraw/tlschema": "5.5.0",
61
+ "@tldraw/utils": "5.5.0",
62
+ "@tldraw/validate": "5.5.0",
64
63
  "classnames": "^2.5.1",
65
64
  "eventemitter3": "^4.0.7",
66
65
  "idb": "^7.1.1",
@@ -75,18 +74,16 @@
75
74
  "@peculiar/webcrypto": "^1.5.0",
76
75
  "@testing-library/dom": "^10.0.0",
77
76
  "@testing-library/react": "^16.0.0",
78
- "@types/benchmark": "^2.1.5",
79
77
  "@types/rbush": "^3.0.0",
80
78
  "@types/react": "^19.2.7",
81
79
  "@types/react-dom": "^19.2.3",
82
80
  "@types/wicg-file-system-access": "^2020.9.8",
83
- "benchmark": "^2.1.4",
84
81
  "fake-indexeddb": "^4.0.2",
85
82
  "lazyrepo": "0.0.0-alpha.27",
86
83
  "react": "^19.2.1",
87
84
  "react-dom": "^19.2.1",
88
85
  "resize-observer-polyfill": "^1.5.1",
89
- "vitest": "^4.1.7"
86
+ "vitest": "^5.0.0"
90
87
  },
91
88
  "engines": {
92
89
  "node": ">=22.12.0"