@tldraw/sync-core 4.6.0-next.5a871ec02ff3 → 4.6.0-next.6594d48ace27

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 (70) hide show
  1. package/dist-cjs/index.js +1 -1
  2. package/dist-cjs/index.js.map +2 -2
  3. package/dist-cjs/lib/ClientWebSocketAdapter.js +3 -1
  4. package/dist-cjs/lib/ClientWebSocketAdapter.js.map +2 -2
  5. package/dist-cjs/lib/DurableObjectSqliteSyncWrapper.js +4 -0
  6. package/dist-cjs/lib/DurableObjectSqliteSyncWrapper.js.map +2 -2
  7. package/dist-cjs/lib/InMemorySyncStorage.js +1 -0
  8. package/dist-cjs/lib/InMemorySyncStorage.js.map +2 -2
  9. package/dist-cjs/lib/NodeSqliteWrapper.js +2 -0
  10. package/dist-cjs/lib/NodeSqliteWrapper.js.map +1 -1
  11. package/dist-cjs/lib/RoomSession.js.map +1 -1
  12. package/dist-cjs/lib/SQLiteSyncStorage.js +2 -0
  13. package/dist-cjs/lib/SQLiteSyncStorage.js.map +1 -1
  14. package/dist-cjs/lib/ServerSocketAdapter.js +2 -1
  15. package/dist-cjs/lib/ServerSocketAdapter.js.map +2 -2
  16. package/dist-cjs/lib/TLRemoteSyncError.js +1 -0
  17. package/dist-cjs/lib/TLRemoteSyncError.js.map +1 -1
  18. package/dist-cjs/lib/TLSocketRoom.js +4 -1
  19. package/dist-cjs/lib/TLSocketRoom.js.map +2 -2
  20. package/dist-cjs/lib/TLSyncClient.js +5 -1
  21. package/dist-cjs/lib/TLSyncClient.js.map +2 -2
  22. package/dist-cjs/lib/TLSyncRoom.js +7 -1
  23. package/dist-cjs/lib/TLSyncRoom.js.map +2 -2
  24. package/dist-cjs/lib/protocol.js.map +1 -1
  25. package/dist-esm/index.mjs +1 -1
  26. package/dist-esm/index.mjs.map +2 -2
  27. package/dist-esm/lib/ClientWebSocketAdapter.mjs +3 -1
  28. package/dist-esm/lib/ClientWebSocketAdapter.mjs.map +2 -2
  29. package/dist-esm/lib/DurableObjectSqliteSyncWrapper.mjs +4 -0
  30. package/dist-esm/lib/DurableObjectSqliteSyncWrapper.mjs.map +2 -2
  31. package/dist-esm/lib/InMemorySyncStorage.mjs +1 -0
  32. package/dist-esm/lib/InMemorySyncStorage.mjs.map +2 -2
  33. package/dist-esm/lib/NodeSqliteWrapper.mjs +2 -0
  34. package/dist-esm/lib/NodeSqliteWrapper.mjs.map +1 -1
  35. package/dist-esm/lib/RoomSession.mjs.map +1 -1
  36. package/dist-esm/lib/SQLiteSyncStorage.mjs +2 -0
  37. package/dist-esm/lib/SQLiteSyncStorage.mjs.map +1 -1
  38. package/dist-esm/lib/ServerSocketAdapter.mjs +2 -1
  39. package/dist-esm/lib/ServerSocketAdapter.mjs.map +2 -2
  40. package/dist-esm/lib/TLRemoteSyncError.mjs +1 -0
  41. package/dist-esm/lib/TLRemoteSyncError.mjs.map +1 -1
  42. package/dist-esm/lib/TLSocketRoom.mjs +4 -1
  43. package/dist-esm/lib/TLSocketRoom.mjs.map +2 -2
  44. package/dist-esm/lib/TLSyncClient.mjs +5 -1
  45. package/dist-esm/lib/TLSyncClient.mjs.map +2 -2
  46. package/dist-esm/lib/TLSyncRoom.mjs +7 -1
  47. package/dist-esm/lib/TLSyncRoom.mjs.map +2 -2
  48. package/dist-esm/lib/protocol.mjs.map +1 -1
  49. package/package.json +6 -6
  50. package/src/index.ts +2 -0
  51. package/src/lib/ClientWebSocketAdapter.test.ts +2 -2
  52. package/src/lib/ClientWebSocketAdapter.ts +5 -5
  53. package/src/lib/DurableObjectSqliteSyncWrapper.ts +1 -2
  54. package/src/lib/InMemorySyncStorage.ts +3 -3
  55. package/src/lib/RoomSession.ts +1 -1
  56. package/src/lib/ServerSocketAdapter.test.ts +1 -1
  57. package/src/lib/ServerSocketAdapter.ts +7 -7
  58. package/src/lib/TLSocketRoom.ts +7 -7
  59. package/src/lib/TLSyncClient.test.ts +4 -7
  60. package/src/lib/TLSyncClient.ts +4 -1
  61. package/src/lib/TLSyncRoom.ts +9 -1
  62. package/src/lib/protocol.ts +2 -0
  63. package/src/test/RandomSource.ts +1 -1
  64. package/src/test/SQLiteSyncStorage.test.ts +1 -1
  65. package/src/test/TLSocketRoom.test.ts +8 -12
  66. package/src/test/TLSyncRoom.test.ts +1 -1
  67. package/src/test/TestSocketPair.ts +2 -2
  68. package/src/test/syncFuzz.test.ts +13 -6
  69. package/src/test/upgradeDowngrade.test.ts +2 -2
  70. package/src/test/validation.test.ts +1 -1
@@ -30,13 +30,10 @@ const protocolVersion = getTlsyncProtocolVersion()
30
30
  type TestRecord = TLRecord
31
31
 
32
32
  // Mock socket implementation for testing
33
- class MockSocket
34
- implements
35
- TLPersistentClientSocket<
36
- TLSocketClientSentEvent<TestRecord>,
37
- TLSocketServerSentEvent<TestRecord>
38
- >
39
- {
33
+ class MockSocket implements TLPersistentClientSocket<
34
+ TLSocketClientSentEvent<TestRecord>,
35
+ TLSocketServerSentEvent<TestRecord>
36
+ > {
40
37
  connectionStatus: 'online' | 'offline' | 'error' = 'offline'
41
38
  private messageListeners: Array<(msg: TLSocketServerSentEvent<TestRecord>) => void> = []
42
39
  private statusListeners: Array<(event: TLSocketStatusChangeEvent) => void> = []
@@ -826,6 +826,9 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
826
826
  this.disposables.forEach((dispose) => dispose())
827
827
  this.sendUnsentChanges.cancel?.()
828
828
  this.scheduleRebase.cancel?.()
829
+ if (typeof window !== 'undefined' && (window as any).tlsync === this) {
830
+ delete (window as any).tlsync
831
+ }
829
832
  }
830
833
 
831
834
  private lastPushedPresenceState: R | null = null
@@ -903,7 +906,7 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
903
906
  }
904
907
  }
905
908
 
906
- // eslint-disable-next-line local/prefer-class-methods
909
+ // eslint-disable-next-line tldraw/prefer-class-methods
907
910
  private rebase = () => {
908
911
  // need to make sure that our speculative changes are in sync with the actual store instance before
909
912
  // proceeding, to avoid inconsistency bugs.
@@ -910,7 +910,15 @@ export class TLSyncRoom<R extends UnknownRecord, SessionMeta> {
910
910
  sessionSchema,
911
911
  requiresDownMigrations,
912
912
  {
913
- puts: Object.fromEntries([...this.presenceStore.values()].map((p) => [p.id, p])),
913
+ // Exclude the connecting session's own presence — it will push fresh
914
+ // data immediately after connecting. Sending the stale record back
915
+ // would leave an orphaned presence in the client's local store (the
916
+ // server never echoes a session's own updates back to it).
917
+ puts: Object.fromEntries(
918
+ [...this.presenceStore.values()]
919
+ .filter((p) => p.id !== session.presenceId)
920
+ .map((p) => [p.id, p])
921
+ ),
914
922
  deletes: [],
915
923
  }
916
924
  )
@@ -69,8 +69,10 @@ export const TLIncompatibilityReason = {
69
69
  * @internal
70
70
  * @deprecated replaced by websocket .close status/reason
71
71
  */
72
+ /* eslint-disable @typescript-eslint/no-deprecated */
72
73
  export type TLIncompatibilityReason =
73
74
  (typeof TLIncompatibilityReason)[keyof typeof TLIncompatibilityReason]
75
+ /* eslint-enable @typescript-eslint/no-deprecated */
74
76
 
75
77
  /**
76
78
  * Union type representing all possible message types that can be sent from server to client.
@@ -19,7 +19,7 @@ export class RandomSource {
19
19
  }
20
20
 
21
21
  randomAction<Result>(
22
- // eslint-disable-next-line @typescript-eslint/method-signature-style
22
+ // eslint-disable-next-line tldraw/method-signature-style
23
23
  choices: Array<(() => Result) | { weight: number; do: () => any }>,
24
24
  randomWeights?: boolean
25
25
  ): Result {
@@ -1,3 +1,4 @@
1
+ import { DatabaseSync } from 'node:sqlite'
1
2
  import {
2
3
  BaseRecord,
3
4
  createMigrationSequence,
@@ -13,7 +14,6 @@ import {
13
14
  TLRecord,
14
15
  } from '@tldraw/tlschema'
15
16
  import { IndexKey, ZERO_INDEX_KEY } from '@tldraw/utils'
16
- import { DatabaseSync } from 'node:sqlite'
17
17
  import { vi } from 'vitest'
18
18
  import { MAX_TOMBSTONES, TOMBSTONE_PRUNE_BUFFER_SIZE } from '../lib/InMemorySyncStorage'
19
19
  import { NodeSqliteWrapper } from '../lib/NodeSqliteWrapper'
@@ -1344,12 +1344,10 @@ describe('TLSocketRoom.updateStore', () => {
1344
1344
  document.name = 'My lovely document'
1345
1345
  store.put(document)
1346
1346
  })
1347
- expect(
1348
- (
1349
- storage.getSnapshot().documents.find((r) => r.state.id === 'document:document')
1350
- ?.state as any
1351
- ).name
1352
- ).toBe('My lovely document')
1347
+ const docRecord = storage
1348
+ .getSnapshot()
1349
+ .documents.find((r) => r.state.id === 'document:document') as any
1350
+ expect(docRecord.state.name).toBe('My lovely document')
1353
1351
  expect(clock).toBeLessThan(storage.getClock())
1354
1352
  })
1355
1353
 
@@ -1359,12 +1357,10 @@ describe('TLSocketRoom.updateStore', () => {
1359
1357
  const document = store.get('document:document') as TLDocument
1360
1358
  document.name = 'My lovely document'
1361
1359
  })
1362
- expect(
1363
- (
1364
- storage.getSnapshot().documents.find((r) => r.state.id === 'document:document')
1365
- ?.state as any
1366
- ).name
1367
- ).toBe('')
1360
+ const docRecord = storage
1361
+ .getSnapshot()
1362
+ .documents.find((r) => r.state.id === 'document:document') as any
1363
+ expect(docRecord.state.name).toBe('')
1368
1364
  expect(clock).toBe(storage.getClock())
1369
1365
  })
1370
1366
 
@@ -16,13 +16,13 @@ import {
16
16
  import { IndexKey, ZERO_INDEX_KEY, mockUniqueId, sortById } from '@tldraw/utils'
17
17
  import { vi } from 'vitest'
18
18
  import { InMemorySyncStorage } from '../lib/InMemorySyncStorage'
19
- import { RoomSnapshot, TLRoomSocket, TLSyncRoom } from '../lib/TLSyncRoom'
20
19
  import {
21
20
  TLConnectRequest,
22
21
  TLPushRequest,
23
22
  TLSocketServerSentEvent,
24
23
  getTlsyncProtocolVersion,
25
24
  } from '../lib/protocol'
25
+ import { RoomSnapshot, TLRoomSocket, TLSyncRoom } from '../lib/TLSyncRoom'
26
26
 
27
27
  const schema = createTLSchema()
28
28
  const compareById = (a: { id: string }, b: { id: string }) => a.id.localeCompare(b.id)
@@ -1,5 +1,6 @@
1
1
  import { UnknownRecord } from '@tldraw/store'
2
2
  import { structuredClone } from '@tldraw/utils'
3
+ import { TLSocketClientSentEvent, TLSocketServerSentEvent } from '../lib/protocol'
3
4
  import {
4
5
  TLPersistentClientSocket,
5
6
  TLSocketStatusListener,
@@ -7,7 +8,6 @@ import {
7
8
  TLSyncErrorCloseEventReason,
8
9
  } from '../lib/TLSyncClient'
9
10
  import { TLRoomSocket } from '../lib/TLSyncRoom'
10
- import { TLSocketClientSentEvent, TLSocketServerSentEvent } from '../lib/protocol'
11
11
  import { TestServer } from './TestServer'
12
12
 
13
13
  export class TestSocketPair<R extends UnknownRecord> {
@@ -97,7 +97,7 @@ export class TestSocketPair<R extends UnknownRecord> {
97
97
  onStatusChange: null as null | TLSocketStatusListener,
98
98
  }
99
99
 
100
- // eslint-disable-next-line no-restricted-syntax
100
+ // eslint-disable-next-line tldraw/no-setter-getter
101
101
  get isConnected() {
102
102
  return this.clientSocket.connectionStatus === 'online'
103
103
  }
@@ -2,10 +2,12 @@ import {
2
2
  Editor,
3
3
  TLRecord,
4
4
  TLStore,
5
+ UserRecordType,
5
6
  computed,
6
7
  createPresenceStateDerivation,
7
8
  createTLSchema,
8
9
  createTLStore,
10
+ createUserId,
9
11
  isRecordsDiffEmpty,
10
12
  mockUniqueId,
11
13
  uniqueId,
@@ -83,12 +85,13 @@ class FuzzTestInstance extends RandomSource {
83
85
  this.editor = new FuzzEditor(this.id, this.seed, this.store)
84
86
  },
85
87
  presence: createPresenceStateDerivation(
86
- computed('', () => ({
87
- id: this.id,
88
- name: 'test',
89
- color: 'red',
90
- locale: 'en',
91
- }))
88
+ computed('', () =>
89
+ UserRecordType.create({
90
+ id: createUserId(this.id),
91
+ name: 'test',
92
+ color: 'red',
93
+ })
94
+ )
92
95
  )(this.store),
93
96
  })
94
97
 
@@ -287,6 +290,10 @@ test('seed 5279266392988747 - undo/redo page integrity regression', () => {
287
290
  runTest(5279266392988747)
288
291
  })
289
292
 
293
+ test('seed 8090628137862085 - duplicate index key regression', () => {
294
+ runTest(8090628137862085)
295
+ })
296
+
290
297
  for (let i = 0; i < NUM_TESTS; i++) {
291
298
  const seed = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
292
299
  test(`seed ${seed}`, () => {
@@ -11,10 +11,10 @@ import {
11
11
  createRecordType,
12
12
  } from '@tldraw/store'
13
13
  import { vi, type Mock } from 'vitest'
14
- import { TLSyncClient, TLSyncErrorCloseEventReason } from '../lib/TLSyncClient'
15
- import { RoomSnapshot, TLRoomSocket } from '../lib/TLSyncRoom'
16
14
  import { RecordOpType, ValueOpType } from '../lib/diff'
17
15
  import { TLSocketServerSentEvent, getTlsyncProtocolVersion } from '../lib/protocol'
16
+ import { TLSyncClient, TLSyncErrorCloseEventReason } from '../lib/TLSyncClient'
17
+ import { RoomSnapshot, TLRoomSocket } from '../lib/TLSyncRoom'
18
18
  import { TestServer } from './TestServer'
19
19
  import { TestSocketPair } from './TestSocketPair'
20
20
 
@@ -1,8 +1,8 @@
1
1
  import { computed } from '@tldraw/state'
2
2
  import { RecordId, Store, StoreSchema, UnknownRecord, createRecordType } from '@tldraw/store'
3
3
  import { vi } from 'vitest'
4
- import { TLSyncClient, TLSyncErrorCloseEventReason } from '../lib/TLSyncClient'
5
4
  import { RecordOpType } from '../lib/diff'
5
+ import { TLSyncClient, TLSyncErrorCloseEventReason } from '../lib/TLSyncClient'
6
6
  import { TestServer } from './TestServer'
7
7
  import { TestSocketPair } from './TestSocketPair'
8
8