@tldraw/editor 5.5.0-next.a2698a88ecfb → 5.5.0-next.b6a9f7cd7be2

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 (127) hide show
  1. package/dist-cjs/index.d.ts +2 -0
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/lib/TldrawEditor.js +16 -12
  4. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  5. package/dist-cjs/lib/components/MenuClickCapture.js +42 -3
  6. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  7. package/dist-cjs/lib/editor/Editor.js +144 -155
  8. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  9. package/dist-cjs/lib/editor/derivations/bindingsIndex.js +2 -0
  10. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  11. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +4 -4
  12. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  13. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +14 -6
  14. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +3 -3
  15. package/dist-cjs/lib/exports/ExportDelay.js +10 -2
  16. package/dist-cjs/lib/exports/ExportDelay.js.map +2 -2
  17. package/dist-cjs/lib/exports/FontEmbedder.js +25 -11
  18. package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
  19. package/dist-cjs/lib/exports/StyleEmbedder.js +9 -4
  20. package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
  21. package/dist-cjs/lib/exports/fetchCache.js +3 -0
  22. package/dist-cjs/lib/exports/fetchCache.js.map +2 -2
  23. package/dist-cjs/lib/exports/getSvgAsImage.js +10 -4
  24. package/dist-cjs/lib/exports/getSvgAsImage.js.map +2 -2
  25. package/dist-cjs/lib/exports/getSvgJsx.js +18 -9
  26. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  27. package/dist-cjs/lib/exports/parseCss.js +1 -1
  28. package/dist-cjs/lib/exports/parseCss.js.map +1 -1
  29. package/dist-cjs/lib/hooks/useCanvasEvents.js +24 -0
  30. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  31. package/dist-cjs/lib/hooks/useIdentity.js +6 -2
  32. package/dist-cjs/lib/hooks/useIdentity.js.map +3 -3
  33. package/dist-cjs/lib/license/LicenseManager.js +9 -1
  34. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  35. package/dist-cjs/lib/license/LicenseProvider.js +12 -4
  36. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  37. package/dist-cjs/lib/utils/deepLinks.js +2 -1
  38. package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
  39. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js +13 -17
  40. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js.map +2 -2
  41. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +78 -40
  42. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  43. package/dist-cjs/version.js +3 -3
  44. package/dist-cjs/version.js.map +1 -1
  45. package/dist-esm/index.d.mts +2 -0
  46. package/dist-esm/index.mjs +1 -1
  47. package/dist-esm/lib/TldrawEditor.mjs +17 -13
  48. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  49. package/dist-esm/lib/components/MenuClickCapture.mjs +42 -3
  50. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  51. package/dist-esm/lib/editor/Editor.mjs +144 -155
  52. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  53. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +2 -0
  54. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  55. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +4 -4
  56. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  57. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +14 -6
  58. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +3 -3
  59. package/dist-esm/lib/exports/ExportDelay.mjs +11 -3
  60. package/dist-esm/lib/exports/ExportDelay.mjs.map +2 -2
  61. package/dist-esm/lib/exports/FontEmbedder.mjs +25 -11
  62. package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
  63. package/dist-esm/lib/exports/StyleEmbedder.mjs +9 -4
  64. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
  65. package/dist-esm/lib/exports/fetchCache.mjs +3 -0
  66. package/dist-esm/lib/exports/fetchCache.mjs.map +2 -2
  67. package/dist-esm/lib/exports/getSvgAsImage.mjs +10 -4
  68. package/dist-esm/lib/exports/getSvgAsImage.mjs.map +2 -2
  69. package/dist-esm/lib/exports/getSvgJsx.mjs +18 -9
  70. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  71. package/dist-esm/lib/exports/parseCss.mjs +1 -1
  72. package/dist-esm/lib/exports/parseCss.mjs.map +1 -1
  73. package/dist-esm/lib/hooks/useCanvasEvents.mjs +24 -0
  74. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  75. package/dist-esm/lib/hooks/useIdentity.mjs +7 -3
  76. package/dist-esm/lib/hooks/useIdentity.mjs.map +3 -3
  77. package/dist-esm/lib/license/LicenseManager.mjs +9 -1
  78. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  79. package/dist-esm/lib/license/LicenseProvider.mjs +13 -5
  80. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  81. package/dist-esm/lib/utils/deepLinks.mjs +2 -1
  82. package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
  83. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs +13 -17
  84. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs.map +2 -2
  85. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +78 -40
  86. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  87. package/dist-esm/version.mjs +3 -3
  88. package/dist-esm/version.mjs.map +1 -1
  89. package/editor.css +9 -1
  90. package/package.json +8 -11
  91. package/src/lib/TldrawEditor.tsx +27 -15
  92. package/src/lib/components/MenuClickCapture.tsx +55 -4
  93. package/src/lib/editor/Editor.test.ts +20 -0
  94. package/src/lib/editor/Editor.ts +221 -259
  95. package/src/lib/editor/derivations/bindingsIndex.ts +3 -0
  96. package/src/lib/editor/managers/TextManager/TextManager.test.ts +74 -0
  97. package/src/lib/editor/managers/TextManager/TextManager.ts +6 -5
  98. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +23 -8
  99. package/src/lib/exports/ExportDelay.tsx +16 -4
  100. package/src/lib/exports/FontEmbedder.test.ts +99 -0
  101. package/src/lib/exports/FontEmbedder.ts +32 -11
  102. package/src/lib/exports/StyleEmbedder.ts +13 -4
  103. package/src/lib/exports/fetchCache.test.ts +22 -0
  104. package/src/lib/exports/fetchCache.ts +4 -0
  105. package/src/lib/exports/getSvgAsImage.ts +13 -5
  106. package/src/lib/exports/getSvgJsx.test.ts +43 -1
  107. package/src/lib/exports/getSvgJsx.tsx +28 -16
  108. package/src/lib/exports/parseCss.test.ts +4 -0
  109. package/src/lib/exports/parseCss.ts +2 -2
  110. package/src/lib/hooks/useCanvasEvents.ts +42 -0
  111. package/src/lib/hooks/useIdentity.tsx +12 -1
  112. package/src/lib/license/LicenseManager.ts +13 -1
  113. package/src/lib/license/LicenseProvider.test.tsx +76 -0
  114. package/src/lib/license/LicenseProvider.tsx +19 -5
  115. package/src/lib/test/preserveFocus.test.tsx +81 -0
  116. package/src/lib/utils/deepLinks.test.ts +15 -0
  117. package/src/lib/utils/deepLinks.ts +8 -3
  118. package/src/lib/utils/sync/LocalIndexedDb.test.ts +28 -0
  119. package/src/lib/utils/sync/LocalIndexedDb.ts +18 -20
  120. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +236 -0
  121. package/src/lib/utils/sync/TLLocalSyncClient.ts +106 -54
  122. package/src/version.ts +3 -3
  123. package/dist-cjs/lib/primitives/geometry/geometry.bench.js +0 -133
  124. package/dist-cjs/lib/primitives/geometry/geometry.bench.js.map +0 -7
  125. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs +0 -132
  126. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs.map +0 -7
  127. package/src/lib/primitives/geometry/geometry.bench.ts +0 -179
@@ -3,6 +3,7 @@ import { IndexKey, promiseWithResolve } from '@tldraw/utils'
3
3
  import { Mock, vi } from 'vitest'
4
4
  import { createTLStore } from '../../config/createTLStore'
5
5
  import { hardReset } from './hardReset'
6
+ import { LocalIndexedDb } from './LocalIndexedDb'
6
7
  import { TLLocalSyncClient } from './TLLocalSyncClient'
7
8
 
8
9
  class BroadcastChannelMock {
@@ -214,6 +215,73 @@ test('pagehide does not write before the initial load has completed', async () =
214
215
  expect(client.db.storeChanges).not.toHaveBeenCalled()
215
216
  })
216
217
 
218
+ test('diffs received while the client is still loading are applied once it has loaded', async () => {
219
+ const { client, channel, tick } = testClient()
220
+ const remotePage = PageRecordType.create({ name: 'remote', index: 'a0' as IndexKey })
221
+ channel.onmessage?.({
222
+ data: {
223
+ type: 'diff',
224
+ storeId: 'other-tab',
225
+ schema: client.serializedSchema,
226
+ changes: { added: { [remotePage.id]: remotePage }, updated: {}, removed: {} },
227
+ },
228
+ } as any)
229
+ expect(client.store.get(remotePage.id)).toBeUndefined()
230
+
231
+ await tick()
232
+ expect(client.store.get(remotePage.id)).toEqual(remotePage)
233
+
234
+ // and the first (full) db write includes them rather than overwriting them
235
+ client.store.put([PageRecordType.create({ name: 'local', index: 'a1' as IndexKey })])
236
+ await tick()
237
+ expect(client.db.storeSnapshot).toHaveBeenCalledTimes(1)
238
+ expect(client.db.storeSnapshot.mock.calls[0][0].snapshot[remotePage.id]).toEqual(remotePage)
239
+ })
240
+
241
+ test('closing the client persists changes that are still queued', async () => {
242
+ const { client, tick } = testClient()
243
+ await tick()
244
+ client.store.put([PageRecordType.create({ name: 'test', index: 'a0' as IndexKey })])
245
+ expect(client.db.storeSnapshot).not.toHaveBeenCalled()
246
+
247
+ // close before the throttled persist fires
248
+ client.close()
249
+ expect(client.db.storeSnapshot).toHaveBeenCalledTimes(1)
250
+ })
251
+
252
+ test('closing the client while a persist is in flight still persists edits queued during the write', async () => {
253
+ const idbOperationResult = promiseWithResolve<void>()
254
+ const { client, tick } = testClient()
255
+ client.db.storeSnapshot.mockImplementationOnce(() => idbOperationResult)
256
+ const closeDb = vi.spyOn(client.db, 'close')
257
+
258
+ await tick()
259
+ client.store.put([PageRecordType.create({ name: 'test', index: 'a0' as IndexKey })])
260
+ await tick()
261
+ expect(client.db.storeSnapshot).toHaveBeenCalledTimes(1)
262
+
263
+ // an edit made while the first write is still in flight, then unmount
264
+ const page = PageRecordType.create({ name: 'test2', index: 'a1' as IndexKey })
265
+ client.store.put([page])
266
+ client.close()
267
+ expect(client.db.storeChanges).not.toHaveBeenCalled()
268
+ expect(closeDb).not.toHaveBeenCalled()
269
+
270
+ idbOperationResult.resolve()
271
+ await tick()
272
+ expect(client.db.storeChanges).toHaveBeenCalledTimes(1)
273
+ expect(client.db.storeChanges.mock.calls[0][0].changes.added[page.id]).toEqual(page)
274
+ expect(closeDb).toHaveBeenCalledTimes(1)
275
+ })
276
+
277
+ test('closing the client with nothing queued does not write to the db', async () => {
278
+ const { client, tick } = testClient()
279
+ await tick()
280
+ client.close()
281
+ expect(client.db.storeSnapshot).not.toHaveBeenCalled()
282
+ expect(client.db.storeChanges).not.toHaveBeenCalled()
283
+ })
284
+
217
285
  test('pagehide and visibilitychange listeners are removed when the client closes', async () => {
218
286
  const removeWindowListener = vi.spyOn(window, 'removeEventListener')
219
287
  const removeDocumentListener = vi.spyOn(document, 'removeEventListener')
@@ -228,3 +296,171 @@ test('pagehide and visibilitychange listeners are removed when the client closes
228
296
  removeDocumentListener.mockRestore()
229
297
  }
230
298
  })
299
+
300
+ test('closing the client before it has loaded does not write to the db', async () => {
301
+ const { client, tick } = testClient()
302
+ client.close()
303
+ await tick()
304
+ expect(client.db.storeSnapshot).not.toHaveBeenCalled()
305
+ expect(client.db.storeChanges).not.toHaveBeenCalled()
306
+ })
307
+
308
+ test('closing flushes changes that have not reached the next animation frame', async () => {
309
+ const { client, tick } = testClient()
310
+ await tick()
311
+ const page = PageRecordType.create({ name: 'last frame', index: 'a0' as IndexKey })
312
+ vi.stubGlobal('__FORCE_RAF_IN_TESTS__', true)
313
+ try {
314
+ client.store.put([page])
315
+ client.close()
316
+ expect(client.db.storeSnapshot).toHaveBeenCalledTimes(1)
317
+ expect(client.db.storeSnapshot.mock.calls[0][0].snapshot[page.id]).toEqual(page)
318
+ } finally {
319
+ vi.unstubAllGlobals()
320
+ }
321
+ })
322
+
323
+ test('buffered schema announcements cannot persist before the remaining buffered diffs', async () => {
324
+ const { client, channel, tick } = testClient()
325
+ const page = PageRecordType.create({ name: 'remote', index: 'a0' as IndexKey })
326
+ const schema = client.store.schema.serialize()
327
+ channel.onmessage?.({
328
+ data: {
329
+ type: 'announce',
330
+ schema: { ...schema, sequences: { ...schema.sequences, 'com.tldraw.page': 0 } },
331
+ },
332
+ } as MessageEvent)
333
+ channel.onmessage?.({
334
+ data: {
335
+ type: 'diff',
336
+ storeId: 'other-tab',
337
+ schema: client.serializedSchema,
338
+ changes: { added: { [page.id]: page }, updated: {}, removed: {} },
339
+ },
340
+ } as MessageEvent)
341
+ await tick()
342
+ expect(client.db.storeSnapshot).toHaveBeenCalledTimes(1)
343
+ expect(client.db.storeSnapshot.mock.calls[0][0].snapshot[page.id]).toEqual(page)
344
+ client.close()
345
+ })
346
+
347
+ test('closing before load cancels a persist scheduled by an early edit', async () => {
348
+ const load = promiseWithResolve<Awaited<ReturnType<LocalIndexedDb['load']>>>()
349
+ const loadMock = vi.spyOn(LocalIndexedDb.prototype, 'load').mockImplementationOnce(() => load)
350
+ try {
351
+ const { client, tick } = testClient()
352
+ client.store.put([PageRecordType.create({ name: 'early', index: 'a0' as IndexKey })])
353
+ client.close()
354
+ load.resolve({ records: [], schema: undefined, sessionStateSnapshot: undefined })
355
+ await tick()
356
+ expect(client.db.storeSnapshot).not.toHaveBeenCalled()
357
+ expect(client.db.storeChanges).not.toHaveBeenCalled()
358
+ } finally {
359
+ loadMock.mockRestore()
360
+ }
361
+ })
362
+
363
+ test.each(['close flush', 'in-flight write'] as const)(
364
+ 'a failed %s after close logs the error without disrupting the current page',
365
+ async (when) => {
366
+ const { client, tick } = testClient()
367
+ await tick()
368
+ const write = promiseWithResolve<void>()
369
+ client.db.storeSnapshot.mockImplementationOnce(() => write)
370
+ const error = new Error('write failed')
371
+ const log = vi.spyOn(console, 'error').mockImplementation(() => {})
372
+ const alert = vi.spyOn(window, 'alert').mockImplementation(() => {})
373
+ try {
374
+ client.store.put([PageRecordType.create({ name: 'last edit', index: 'a0' as IndexKey })])
375
+ if (when === 'in-flight write') await tick()
376
+ client.close()
377
+ write.reject(error)
378
+ await tick()
379
+ expect(log).toHaveBeenCalledWith('failed to store changes in indexed db', error)
380
+ expect(alert).not.toHaveBeenCalled()
381
+ expect(reloadMock).not.toHaveBeenCalled()
382
+ } finally {
383
+ log.mockRestore()
384
+ alert.mockRestore()
385
+ }
386
+ }
387
+ )
388
+
389
+ test('closing with no queued changes cancels a failed-write retry', async () => {
390
+ const { client, tick } = testClient()
391
+ await tick()
392
+ const log = vi.spyOn(console, 'error').mockImplementation(() => {})
393
+ const alert = vi.spyOn(window, 'alert').mockImplementation(() => {})
394
+ try {
395
+ client.db.storeSnapshot.mockRejectedValueOnce(new Error('first write failed'))
396
+ client.store.put([PageRecordType.create({ name: 'retry', index: 'a0' as IndexKey })])
397
+ await tick()
398
+ expect(reloadMock).toHaveBeenCalledTimes(1)
399
+ client.close()
400
+ await vi.advanceTimersByTimeAsync(10_000)
401
+ expect(client.db.storeSnapshot).toHaveBeenCalledTimes(1)
402
+ } finally {
403
+ log.mockRestore()
404
+ alert.mockRestore()
405
+ }
406
+ })
407
+
408
+ test('a schema mismatch does not try to refresh without a window', async () => {
409
+ const { client, channel, tick } = testClient()
410
+ await tick()
411
+ vi.advanceTimersByTime(10_000)
412
+ vi.stubGlobal('window', undefined)
413
+ try {
414
+ expect(() =>
415
+ channel.onmessage?.({
416
+ data: {
417
+ type: 'announce',
418
+ schema: {
419
+ ...client.serializedSchema,
420
+ schemaVersion: client.serializedSchema.schemaVersion + 1,
421
+ },
422
+ },
423
+ } as MessageEvent)
424
+ ).not.toThrow()
425
+ } finally {
426
+ vi.unstubAllGlobals()
427
+ client.close()
428
+ }
429
+ })
430
+
431
+ test('onLoad can close the client without losing buffered remote changes', async () => {
432
+ const { client, channel, onLoad, tick } = testClient()
433
+ const page = PageRecordType.create({ name: 'remote', index: 'a0' as IndexKey })
434
+ channel.onmessage?.({
435
+ data: {
436
+ type: 'diff',
437
+ storeId: 'other-tab',
438
+ schema: client.serializedSchema,
439
+ changes: { added: { [page.id]: page }, updated: {}, removed: {} },
440
+ },
441
+ } as MessageEvent)
442
+ onLoad.mockImplementation(() => {
443
+ client.store.put([PageRecordType.create({ name: 'local', index: 'a1' as IndexKey })])
444
+ client.close()
445
+ })
446
+ await tick()
447
+ expect(client.db.storeSnapshot.mock.calls[0][0].snapshot[page.id]).toEqual(page)
448
+ })
449
+
450
+ test('a buffered newer schema reports a load error without announcing a successful load', async () => {
451
+ const { client, channel, onLoad, onLoadError, tick } = testClient()
452
+ channel.onmessage?.({
453
+ data: {
454
+ type: 'announce',
455
+ schema: {
456
+ ...client.serializedSchema,
457
+ schemaVersion: client.serializedSchema.schemaVersion + 1,
458
+ },
459
+ },
460
+ } as MessageEvent)
461
+ await tick()
462
+ expect(onLoadError).toHaveBeenCalledTimes(1)
463
+ expect(onLoad).not.toHaveBeenCalled()
464
+ client.close()
465
+ expect(client.db.storeSnapshot).not.toHaveBeenCalled()
466
+ })
@@ -9,6 +9,7 @@ import {
9
9
  extractSessionStateFromLegacySnapshot,
10
10
  loadSessionStateSnapshotIntoStore,
11
11
  } from '../../config/TLSessionStateSnapshot'
12
+ import { refreshPage } from '../runtime'
12
13
  import { showCantReadFromIndexDbAlert, showCantWriteToIndexDbAlert } from './alerts'
13
14
  import { LocalIndexedDb } from './LocalIndexedDb'
14
15
 
@@ -108,7 +109,6 @@ export class TLLocalSyncClient {
108
109
  this.persistenceKey = persistenceKey
109
110
  this.sessionId = sessionId
110
111
  this.db = new LocalIndexedDb(persistenceKey)
111
- this.disposables.add(() => this.db.close())
112
112
 
113
113
  this.serializedSchema = this.store.schema.serialize()
114
114
  this.$sessionStateSnapshot = createSessionStateSnapshotSignal(this.store)
@@ -179,6 +179,65 @@ export class TLLocalSyncClient {
179
179
  this.debug('connecting')
180
180
  let data: UnpackPromise<ReturnType<LocalIndexedDb['load']>> | undefined
181
181
 
182
+ const handleMessage = (msg: Message) => {
183
+ // if their schema is earlier than ours, we need to tell them so they can refresh
184
+ // if their schema is later than ours, we need to refresh
185
+ const res = this.store.schema.getMigrationsSince(msg.schema)
186
+
187
+ if (!res.ok) {
188
+ this.isReloading = true
189
+ // we are older, refresh
190
+ // but add a safety check to make sure we don't get in an infinite loop
191
+ const timeSinceInit = Date.now() - this.initTime
192
+ if (timeSinceInit < 5000) {
193
+ // This tab was just reloaded, but is out of date compared to other tabs.
194
+ // Not expecting this to ever happen. It should only happen if we roll back a release that incremented
195
+ // the schema version (which we should never do)
196
+ // Or maybe during development if you have multiple local tabs open running the app on prod mode and you
197
+ // check out an older commit. Dev server should be fine.
198
+ onLoadError(new Error('Schema mismatch, please close other tabs and reload the page'))
199
+ return false
200
+ }
201
+ this.debug('reloading')
202
+ if (typeof window !== 'undefined') refreshPage()
203
+ return false
204
+ } else if (res.value.length > 0) {
205
+ // they are older, tell them to refresh and not write any more data
206
+ this.debug('telling them to reload')
207
+ this.channel.postMessage({ type: 'announce', schema: this.serializedSchema })
208
+ // schedule a full db write in case they wrote data anyway
209
+ this.shouldDoFullDBWrite = true
210
+ this.schedulePersist()
211
+ return true
212
+ }
213
+ // otherwise, all good, same version :)
214
+ if (msg.type === 'diff') {
215
+ this.debug('applying diff')
216
+ transact(() => {
217
+ this.store.mergeRemoteChanges(() => {
218
+ this.store.applyDiff(msg.changes as any)
219
+ })
220
+ })
221
+ }
222
+ return true
223
+ }
224
+
225
+ // Listen from the start and buffer until we've loaded: diffs other tabs broadcast while
226
+ // we're still reading from IndexedDB would otherwise be dropped, then erased by our first
227
+ // (full) db write.
228
+ let bufferedMessages: Message[] | null = []
229
+ this.channel.onmessage = ({ data }) => {
230
+ this.debug('got message', data)
231
+ if (bufferedMessages) {
232
+ bufferedMessages.push(data as Message)
233
+ } else {
234
+ handleMessage(data as Message)
235
+ }
236
+ }
237
+ this.disposables.add(() => {
238
+ this.channel.close()
239
+ })
240
+
182
241
  try {
183
242
  data = await this.db.load({ sessionId: this.sessionId })
184
243
  } catch (error: any) {
@@ -224,55 +283,17 @@ export class TLLocalSyncClient {
224
283
  })
225
284
  }
226
285
  }
227
-
228
- this.channel.onmessage = ({ data }) => {
229
- this.debug('got message', data)
230
- const msg = data as Message
231
- // if their schema is earlier than ours, we need to tell them so they can refresh
232
- // if their schema is later than ours, we need to refresh
233
- const res = this.store.schema.getMigrationsSince(msg.schema)
234
-
235
- if (!res.ok) {
236
- // we are older, refresh
237
- // but add a safety check to make sure we don't get in an infinite loop
238
- const timeSinceInit = Date.now() - this.initTime
239
- if (timeSinceInit < 5000) {
240
- // This tab was just reloaded, but is out of date compared to other tabs.
241
- // Not expecting this to ever happen. It should only happen if we roll back a release that incremented
242
- // the schema version (which we should never do)
243
- // Or maybe during development if you have multiple local tabs open running the app on prod mode and you
244
- // check out an older commit. Dev server should be fine.
245
- onLoadError(new Error('Schema mismatch, please close other tabs and reload the page'))
246
- return
247
- }
248
- this.debug('reloading')
249
- this.isReloading = true
250
- window?.location?.reload?.()
251
- return
252
- } else if (res.value.length > 0) {
253
- // they are older, tell them to refresh and not write any more data
254
- this.debug('telling them to reload')
255
- this.channel.postMessage({ type: 'announce', schema: this.serializedSchema })
256
- // schedule a full db write in case they wrote data anyway
257
- this.shouldDoFullDBWrite = true
258
- this.persistIfNeeded()
259
- return
260
- }
261
- // otherwise, all good, same version :)
262
- if (msg.type === 'diff') {
263
- this.debug('applying diff')
264
- transact(() => {
265
- this.store.mergeRemoteChanges(() => {
266
- this.store.applyDiff(msg.changes as any)
267
- })
268
- })
269
- }
286
+ // Older-schema announcements schedule writes, so drain every diff before those writes
287
+ // or an onLoad callback that immediately closes the client can capture a snapshot.
288
+ const messages = bufferedMessages
289
+ bufferedMessages = null
290
+ for (const message of messages) {
291
+ if (!handleMessage(message)) return
270
292
  }
271
- this.channel.postMessage({ type: 'announce', schema: this.serializedSchema })
272
- this.disposables.add(() => {
273
- this.channel.close()
274
- })
275
293
  this.didLoad = true
294
+ if (this.diffQueue.length > 0) this.schedulePersist()
295
+
296
+ this.channel.postMessage({ type: 'announce', schema: this.serializedSchema })
276
297
  onLoad(this)
277
298
  } catch (e: any) {
278
299
  this.debug('error loading data from store', e)
@@ -283,15 +304,43 @@ export class TLLocalSyncClient {
283
304
  }
284
305
 
285
306
  close() {
307
+ if (this.didDispose) return
286
308
  this.debug('closing')
287
309
  this.didDispose = true
310
+ // Store listeners normally run on the next frame; collect the last edits before removal.
311
+ this.store._flushHistory()
288
312
  this.disposables.forEach((d) => d())
313
+ if (this.scheduledPersistTimeout) {
314
+ clearTimeout(this.scheduledPersistTimeout)
315
+ this.scheduledPersistTimeout = null
316
+ }
289
317
  if (typeof window !== 'undefined' && (window as any).tlsync === this) {
290
318
  delete (window as any).tlsync
291
319
  }
320
+ this.flushAndCloseDb()
321
+ }
322
+
323
+ /**
324
+ * Flush queued changes so edits made in the last PERSIST_THROTTLE_MS before unmount aren't lost,
325
+ * then close the db. Never flushes before load (a full write then would wipe the db with an empty
326
+ * store), and only when something is queued: shouldDoFullDBWrite stays true until the first
327
+ * persist, so an unconditional flush would rewrite the whole document on every no-op close.
328
+ */
329
+ private flushAndCloseDb() {
330
+ // A persist in flight can't be joined (persistIfNeeded bails while one is running) and,
331
+ // now that we're disposed, won't schedule a follow-up. Edits queued during that write
332
+ // would be lost if the db closed now, so wait for it and flush again.
333
+ if (this.persistPromise) {
334
+ this.persistPromise.then(() => this.flushAndCloseDb())
335
+ return
336
+ }
337
+ if (this.didLoad && this.diffQueue.length > 0) this.persistIfNeeded()
338
+ // the db waits for the transaction the flush just opened before actually closing
339
+ this.db.close()
292
340
  }
293
341
 
294
342
  private isPersisting = false
343
+ private persistPromise: Promise<void> | null = null
295
344
  private didLastWriteError = false
296
345
  // eslint-disable-next-line no-restricted-globals
297
346
  private scheduledPersistTimeout: ReturnType<typeof setTimeout> | null = null
@@ -305,6 +354,8 @@ export class TLLocalSyncClient {
305
354
  private schedulePersist() {
306
355
  this.debug('schedulePersist', this.scheduledPersistTimeout)
307
356
  if (this.scheduledPersistTimeout) return
357
+ // A deferred persist would try to write to a database that is closing or already closed.
358
+ if (this.didDispose) return
308
359
  // eslint-disable-next-line no-restricted-globals
309
360
  this.scheduledPersistTimeout = setTimeout(
310
361
  () => {
@@ -339,6 +390,8 @@ export class TLLocalSyncClient {
339
390
  this.scheduledPersistTimeout = null
340
391
  }
341
392
 
393
+ if (!this.didLoad) return
394
+
342
395
  // if a persist is already in progress, we don't need to do anything -
343
396
  // if there are still outstanding changes once it's finished, it'll
344
397
  // schedule another persist
@@ -353,7 +406,7 @@ export class TLLocalSyncClient {
353
406
 
354
407
  // if we're scheduled for a full write or if we have changes outstanding, let's persist them!
355
408
  if (this.shouldDoFullDBWrite || this.diffQueue.length > 0) {
356
- this.doPersist()
409
+ this.persistPromise = this.doPersist()
357
410
  }
358
411
  }
359
412
 
@@ -363,7 +416,6 @@ export class TLLocalSyncClient {
363
416
  */
364
417
  private async doPersist() {
365
418
  assert(!this.isPersisting, 'persist already in progress')
366
- if (this.didDispose) return
367
419
  this.isPersisting = true
368
420
 
369
421
  this.debug('doPersist start')
@@ -401,14 +453,14 @@ export class TLLocalSyncClient {
401
453
  this.didLastWriteError = true
402
454
  console.error('failed to store changes in indexed db', e)
403
455
 
404
- showCantWriteToIndexDbAlert()
405
- if (typeof window !== 'undefined') {
406
- // adios
407
- window.location.reload()
456
+ if (!this.didDispose && typeof window !== 'undefined') {
457
+ showCantWriteToIndexDbAlert()
458
+ refreshPage()
408
459
  }
409
460
  }
410
461
 
411
462
  this.isPersisting = false
463
+ this.persistPromise = null
412
464
  this.debug('doPersist end')
413
465
 
414
466
  // changes might have come in between when we started the persist and
package/src/version.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '5.5.0-next.a2698a88ecfb'
4
+ export const version = '5.5.0-next.b6a9f7cd7be2'
5
5
  export const publishDates = {
6
6
  major: '2026-05-06T16:28:18.473Z',
7
- minor: '2026-09-02T13:32:15.626Z',
8
- patch: '2026-09-02T13:32:15.626Z',
7
+ minor: '2026-09-16T11:13:34.604Z',
8
+ patch: '2026-09-16T11:13:34.604Z',
9
9
  }
@@ -1,133 +0,0 @@
1
- "use strict";
2
- var import_vitest = require("vitest");
3
- var import_Vec = require("../Vec");
4
- var import_Arc2d = require("./Arc2d");
5
- var import_Circle2d = require("./Circle2d");
6
- var import_Edge2d = require("./Edge2d");
7
- var import_Polyline2d = require("./Polyline2d");
8
- var import_Rectangle2d = require("./Rectangle2d");
9
- var import_intersect = require("../intersect");
10
- const lsA = new import_Vec.Vec(0, 0);
11
- const lsB = new import_Vec.Vec(100, 50);
12
- const lsP = new import_Vec.Vec(30, 60);
13
- (0, import_vitest.describe)("Vec line segment", () => {
14
- (0, import_vitest.bench)("NearestPointOnLineSegment", () => {
15
- import_Vec.Vec.NearestPointOnLineSegment(lsA, lsB, lsP, true);
16
- });
17
- (0, import_vitest.bench)("DistanceToLineSegment", () => {
18
- import_Vec.Vec.DistanceToLineSegment(lsA, lsB, lsP, true);
19
- });
20
- });
21
- const vecA = new import_Vec.Vec(3, 4);
22
- const vecB = new import_Vec.Vec(-1, 2);
23
- const lineOrigin = new import_Vec.Vec(10, 10);
24
- const lineDir = new import_Vec.Vec(0.7071, 0.7071);
25
- const lineTestP = new import_Vec.Vec(20, 15);
26
- (0, import_vitest.describe)("Vec utilities", () => {
27
- (0, import_vitest.bench)("Lrp", () => {
28
- import_Vec.Vec.Lrp(lsA, lsB, 0.35);
29
- });
30
- (0, import_vitest.bench)("AngleBetween", () => {
31
- import_Vec.Vec.AngleBetween(vecA, vecB);
32
- });
33
- (0, import_vitest.bench)("NearestPointOnLineThroughPoint", () => {
34
- import_Vec.Vec.NearestPointOnLineThroughPoint(lineOrigin, lineDir, lineTestP);
35
- });
36
- (0, import_vitest.bench)("DistanceToLineThroughPoint", () => {
37
- import_Vec.Vec.DistanceToLineThroughPoint(lineOrigin, lineDir, lineTestP);
38
- });
39
- });
40
- const intA1 = new import_Vec.Vec(0, 0);
41
- const intA2 = new import_Vec.Vec(100, 100);
42
- const intB1 = new import_Vec.Vec(100, 0);
43
- const intB2 = new import_Vec.Vec(0, 100);
44
- const intB1Miss = new import_Vec.Vec(200, 0);
45
- const intB2Miss = new import_Vec.Vec(200, 100);
46
- const intCenter = new import_Vec.Vec(50, 50);
47
- (0, import_vitest.describe)("Intersections", () => {
48
- (0, import_vitest.bench)("lineSegment-lineSegment (hit)", () => {
49
- (0, import_intersect.intersectLineSegmentLineSegment)(intA1, intA2, intB1, intB2);
50
- });
51
- (0, import_vitest.bench)("lineSegment-lineSegment (miss)", () => {
52
- (0, import_intersect.intersectLineSegmentLineSegment)(intA1, intA2, intB1Miss, intB2Miss);
53
- });
54
- (0, import_vitest.bench)("lineSegment-circle (hit)", () => {
55
- (0, import_intersect.intersectLineSegmentCircle)(intA1, intA2, intCenter, 30);
56
- });
57
- (0, import_vitest.bench)("lineSegment-circle (miss)", () => {
58
- (0, import_intersect.intersectLineSegmentCircle)(intA1, intA2, intCenter, 5);
59
- });
60
- });
61
- const edge = new import_Edge2d.Edge2d({ start: new import_Vec.Vec(0, 0), end: new import_Vec.Vec(100, 50) });
62
- const edgePoint = new import_Vec.Vec(30, 60);
63
- (0, import_vitest.describe)("Edge2d", () => {
64
- (0, import_vitest.bench)("nearestPoint", () => {
65
- edge.nearestPoint(edgePoint);
66
- });
67
- (0, import_vitest.bench)("distanceToPoint", () => {
68
- edge.distanceToPoint(edgePoint);
69
- });
70
- });
71
- const circle = new import_Circle2d.Circle2d({ radius: 50, isFilled: true });
72
- const circlePoint = new import_Vec.Vec(80, 60);
73
- const circlePointInside = new import_Vec.Vec(50, 50);
74
- (0, import_vitest.describe)("Circle2d", () => {
75
- (0, import_vitest.bench)("nearestPoint", () => {
76
- circle.nearestPoint(circlePoint);
77
- });
78
- (0, import_vitest.bench)("distanceToPoint", () => {
79
- circle.distanceToPoint(circlePoint);
80
- });
81
- (0, import_vitest.bench)("hitTestPoint (outside)", () => {
82
- circle.hitTestPoint(circlePoint, 5);
83
- });
84
- (0, import_vitest.bench)("hitTestPoint (inside)", () => {
85
- circle.hitTestPoint(circlePointInside, 5, true);
86
- });
87
- });
88
- const arc = new import_Arc2d.Arc2d({
89
- center: new import_Vec.Vec(50, 50),
90
- start: new import_Vec.Vec(100, 50),
91
- end: new import_Vec.Vec(50, 100),
92
- sweepFlag: 1,
93
- largeArcFlag: 0
94
- });
95
- const arcPoint = new import_Vec.Vec(90, 90);
96
- (0, import_vitest.describe)("Arc2d", () => {
97
- (0, import_vitest.bench)("nearestPoint", () => {
98
- arc.nearestPoint(arcPoint);
99
- });
100
- });
101
- const polyPoints = [];
102
- for (let i = 0; i <= 20; i++) {
103
- const t = i / 20;
104
- polyPoints.push(new import_Vec.Vec(t * 200, Math.sin(t * Math.PI * 2) * 50 + 50));
105
- }
106
- const polyline = new import_Polyline2d.Polyline2d({ points: polyPoints });
107
- const polyPoint = new import_Vec.Vec(100, 80);
108
- (0, import_vitest.describe)("Polyline2d", () => {
109
- (0, import_vitest.bench)("nearestPoint", () => {
110
- polyline.nearestPoint(polyPoint);
111
- });
112
- (0, import_vitest.bench)("distanceToPoint", () => {
113
- polyline.distanceToPoint(polyPoint);
114
- });
115
- });
116
- const rect = new import_Rectangle2d.Rectangle2d({ width: 100, height: 80, isFilled: true });
117
- const rectPointOutside = new import_Vec.Vec(120, 40);
118
- const rectPointInside = new import_Vec.Vec(50, 40);
119
- (0, import_vitest.describe)("Rectangle2d (Geometry2d)", () => {
120
- (0, import_vitest.bench)("hitTestPoint (outside)", () => {
121
- rect.hitTestPoint(rectPointOutside, 5);
122
- });
123
- (0, import_vitest.bench)("hitTestPoint (inside)", () => {
124
- rect.hitTestPoint(rectPointInside, 5, true);
125
- });
126
- (0, import_vitest.bench)("distanceToPoint (outside)", () => {
127
- rect.distanceToPoint(rectPointOutside);
128
- });
129
- (0, import_vitest.bench)("distanceToPoint (inside)", () => {
130
- rect.distanceToPoint(rectPointInside, true);
131
- });
132
- });
133
- //# sourceMappingURL=geometry.bench.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/lib/primitives/geometry/geometry.bench.ts"],
4
- "sourcesContent": ["import { bench, describe } from 'vitest'\nimport { Vec } from '../Vec'\nimport { Arc2d } from './Arc2d'\nimport { Circle2d } from './Circle2d'\nimport { Edge2d } from './Edge2d'\nimport { Polyline2d } from './Polyline2d'\nimport { Rectangle2d } from './Rectangle2d'\n\n// --- Vec line segment utilities ---\n\nconst lsA = new Vec(0, 0)\nconst lsB = new Vec(100, 50)\nconst lsP = new Vec(30, 60)\n\ndescribe('Vec line segment', () => {\n\tbench('NearestPointOnLineSegment', () => {\n\t\tVec.NearestPointOnLineSegment(lsA, lsB, lsP, true)\n\t})\n\n\tbench('DistanceToLineSegment', () => {\n\t\tVec.DistanceToLineSegment(lsA, lsB, lsP, true)\n\t})\n})\n\n// --- Vec utilities ---\n\nconst vecA = new Vec(3, 4)\nconst vecB = new Vec(-1, 2)\nconst lineOrigin = new Vec(10, 10)\nconst lineDir = new Vec(0.7071, 0.7071) // ~unit vector at 45\u00B0\nconst lineTestP = new Vec(20, 15)\n\ndescribe('Vec utilities', () => {\n\tbench('Lrp', () => {\n\t\tVec.Lrp(lsA, lsB, 0.35)\n\t})\n\n\tbench('AngleBetween', () => {\n\t\tVec.AngleBetween(vecA, vecB)\n\t})\n\n\tbench('NearestPointOnLineThroughPoint', () => {\n\t\tVec.NearestPointOnLineThroughPoint(lineOrigin, lineDir, lineTestP)\n\t})\n\n\tbench('DistanceToLineThroughPoint', () => {\n\t\tVec.DistanceToLineThroughPoint(lineOrigin, lineDir, lineTestP)\n\t})\n})\n\n// --- Intersections ---\n\nimport { intersectLineSegmentCircle, intersectLineSegmentLineSegment } from '../intersect'\n\nconst intA1 = new Vec(0, 0)\nconst intA2 = new Vec(100, 100)\nconst intB1 = new Vec(100, 0)\nconst intB2 = new Vec(0, 100)\nconst intB1Miss = new Vec(200, 0)\nconst intB2Miss = new Vec(200, 100)\nconst intCenter = new Vec(50, 50)\n\ndescribe('Intersections', () => {\n\tbench('lineSegment-lineSegment (hit)', () => {\n\t\tintersectLineSegmentLineSegment(intA1, intA2, intB1, intB2)\n\t})\n\n\tbench('lineSegment-lineSegment (miss)', () => {\n\t\tintersectLineSegmentLineSegment(intA1, intA2, intB1Miss, intB2Miss)\n\t})\n\n\tbench('lineSegment-circle (hit)', () => {\n\t\tintersectLineSegmentCircle(intA1, intA2, intCenter, 30)\n\t})\n\n\tbench('lineSegment-circle (miss)', () => {\n\t\tintersectLineSegmentCircle(intA1, intA2, intCenter, 5)\n\t})\n})\n\n// --- Edge2d ---\n\nconst edge = new Edge2d({ start: new Vec(0, 0), end: new Vec(100, 50) })\nconst edgePoint = new Vec(30, 60)\n\ndescribe('Edge2d', () => {\n\tbench('nearestPoint', () => {\n\t\tedge.nearestPoint(edgePoint)\n\t})\n\n\tbench('distanceToPoint', () => {\n\t\tedge.distanceToPoint(edgePoint)\n\t})\n})\n\n// --- Circle2d ---\n\nconst circle = new Circle2d({ radius: 50, isFilled: true })\nconst circlePoint = new Vec(80, 60)\nconst circlePointInside = new Vec(50, 50)\n\ndescribe('Circle2d', () => {\n\tbench('nearestPoint', () => {\n\t\tcircle.nearestPoint(circlePoint)\n\t})\n\n\tbench('distanceToPoint', () => {\n\t\tcircle.distanceToPoint(circlePoint)\n\t})\n\n\tbench('hitTestPoint (outside)', () => {\n\t\tcircle.hitTestPoint(circlePoint, 5)\n\t})\n\n\tbench('hitTestPoint (inside)', () => {\n\t\tcircle.hitTestPoint(circlePointInside, 5, true)\n\t})\n})\n\n// --- Arc2d ---\n\nconst arc = new Arc2d({\n\tcenter: new Vec(50, 50),\n\tstart: new Vec(100, 50),\n\tend: new Vec(50, 100),\n\tsweepFlag: 1,\n\tlargeArcFlag: 0,\n})\nconst arcPoint = new Vec(90, 90)\n\ndescribe('Arc2d', () => {\n\tbench('nearestPoint', () => {\n\t\tarc.nearestPoint(arcPoint)\n\t})\n})\n\n// --- Polyline2d (20 segments) ---\n\nconst polyPoints: Vec[] = []\nfor (let i = 0; i <= 20; i++) {\n\tconst t = i / 20\n\tpolyPoints.push(new Vec(t * 200, Math.sin(t * Math.PI * 2) * 50 + 50))\n}\nconst polyline = new Polyline2d({ points: polyPoints })\nconst polyPoint = new Vec(100, 80)\n\ndescribe('Polyline2d', () => {\n\tbench('nearestPoint', () => {\n\t\tpolyline.nearestPoint(polyPoint)\n\t})\n\n\tbench('distanceToPoint', () => {\n\t\tpolyline.distanceToPoint(polyPoint)\n\t})\n})\n\n// --- Rectangle2d (Geometry2d base) ---\n\nconst rect = new Rectangle2d({ width: 100, height: 80, isFilled: true })\nconst rectPointOutside = new Vec(120, 40)\nconst rectPointInside = new Vec(50, 40)\n\ndescribe('Rectangle2d (Geometry2d)', () => {\n\tbench('hitTestPoint (outside)', () => {\n\t\trect.hitTestPoint(rectPointOutside, 5)\n\t})\n\n\tbench('hitTestPoint (inside)', () => {\n\t\trect.hitTestPoint(rectPointInside, 5, true)\n\t})\n\n\tbench('distanceToPoint (outside)', () => {\n\t\trect.distanceToPoint(rectPointOutside)\n\t})\n\n\tbench('distanceToPoint (inside)', () => {\n\t\trect.distanceToPoint(rectPointInside, true)\n\t})\n})\n"],
5
- "mappings": "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",
6
- "names": []
7
- }