@tldraw/sync-core 4.2.0 → 4.2.2

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 (84) hide show
  1. package/dist-cjs/index.d.ts +483 -58
  2. package/dist-cjs/index.js +13 -3
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/DurableObjectSqliteSyncWrapper.js +55 -0
  5. package/dist-cjs/lib/DurableObjectSqliteSyncWrapper.js.map +7 -0
  6. package/dist-cjs/lib/InMemorySyncStorage.js +287 -0
  7. package/dist-cjs/lib/InMemorySyncStorage.js.map +7 -0
  8. package/dist-cjs/lib/MicrotaskNotifier.js +50 -0
  9. package/dist-cjs/lib/MicrotaskNotifier.js.map +7 -0
  10. package/dist-cjs/lib/NodeSqliteWrapper.js +48 -0
  11. package/dist-cjs/lib/NodeSqliteWrapper.js.map +7 -0
  12. package/dist-cjs/lib/RoomSession.js.map +1 -1
  13. package/dist-cjs/lib/SQLiteSyncStorage.js +428 -0
  14. package/dist-cjs/lib/SQLiteSyncStorage.js.map +7 -0
  15. package/dist-cjs/lib/TLSocketRoom.js +117 -69
  16. package/dist-cjs/lib/TLSocketRoom.js.map +2 -2
  17. package/dist-cjs/lib/TLSyncClient.js +7 -0
  18. package/dist-cjs/lib/TLSyncClient.js.map +2 -2
  19. package/dist-cjs/lib/TLSyncRoom.js +357 -688
  20. package/dist-cjs/lib/TLSyncRoom.js.map +3 -3
  21. package/dist-cjs/lib/TLSyncStorage.js +76 -0
  22. package/dist-cjs/lib/TLSyncStorage.js.map +7 -0
  23. package/dist-cjs/lib/chunk.js +2 -2
  24. package/dist-cjs/lib/chunk.js.map +1 -1
  25. package/dist-cjs/lib/recordDiff.js +52 -0
  26. package/dist-cjs/lib/recordDiff.js.map +7 -0
  27. package/dist-esm/index.d.mts +483 -58
  28. package/dist-esm/index.mjs +20 -5
  29. package/dist-esm/index.mjs.map +2 -2
  30. package/dist-esm/lib/DurableObjectSqliteSyncWrapper.mjs +35 -0
  31. package/dist-esm/lib/DurableObjectSqliteSyncWrapper.mjs.map +7 -0
  32. package/dist-esm/lib/InMemorySyncStorage.mjs +272 -0
  33. package/dist-esm/lib/InMemorySyncStorage.mjs.map +7 -0
  34. package/dist-esm/lib/MicrotaskNotifier.mjs +30 -0
  35. package/dist-esm/lib/MicrotaskNotifier.mjs.map +7 -0
  36. package/dist-esm/lib/NodeSqliteWrapper.mjs +28 -0
  37. package/dist-esm/lib/NodeSqliteWrapper.mjs.map +7 -0
  38. package/dist-esm/lib/RoomSession.mjs.map +1 -1
  39. package/dist-esm/lib/SQLiteSyncStorage.mjs +414 -0
  40. package/dist-esm/lib/SQLiteSyncStorage.mjs.map +7 -0
  41. package/dist-esm/lib/TLSocketRoom.mjs +121 -70
  42. package/dist-esm/lib/TLSocketRoom.mjs.map +2 -2
  43. package/dist-esm/lib/TLSyncClient.mjs +7 -0
  44. package/dist-esm/lib/TLSyncClient.mjs.map +2 -2
  45. package/dist-esm/lib/TLSyncRoom.mjs +370 -702
  46. package/dist-esm/lib/TLSyncRoom.mjs.map +3 -3
  47. package/dist-esm/lib/TLSyncStorage.mjs +56 -0
  48. package/dist-esm/lib/TLSyncStorage.mjs.map +7 -0
  49. package/dist-esm/lib/chunk.mjs +2 -2
  50. package/dist-esm/lib/chunk.mjs.map +1 -1
  51. package/dist-esm/lib/recordDiff.mjs +32 -0
  52. package/dist-esm/lib/recordDiff.mjs.map +7 -0
  53. package/package.json +12 -11
  54. package/src/index.ts +32 -3
  55. package/src/lib/ClientWebSocketAdapter.test.ts +3 -0
  56. package/src/lib/DurableObjectSqliteSyncWrapper.ts +95 -0
  57. package/src/lib/InMemorySyncStorage.ts +387 -0
  58. package/src/lib/MicrotaskNotifier.test.ts +429 -0
  59. package/src/lib/MicrotaskNotifier.ts +38 -0
  60. package/src/lib/NodeSqliteSyncWrapper.integration.test.ts +270 -0
  61. package/src/lib/NodeSqliteSyncWrapper.test.ts +272 -0
  62. package/src/lib/NodeSqliteWrapper.ts +99 -0
  63. package/src/lib/RoomSession.test.ts +1 -0
  64. package/src/lib/RoomSession.ts +2 -0
  65. package/src/lib/SQLiteSyncStorage.ts +627 -0
  66. package/src/lib/TLSocketRoom.ts +228 -114
  67. package/src/lib/TLSyncClient.ts +12 -0
  68. package/src/lib/TLSyncRoom.ts +473 -913
  69. package/src/lib/TLSyncStorage.ts +216 -0
  70. package/src/lib/chunk.ts +2 -2
  71. package/src/lib/computeTombstonePruning.test.ts +352 -0
  72. package/src/lib/recordDiff.ts +73 -0
  73. package/src/test/FuzzEditor.ts +4 -5
  74. package/src/test/InMemorySyncStorage.test.ts +1684 -0
  75. package/src/test/SQLiteSyncStorage.test.ts +1378 -0
  76. package/src/test/TLSocketRoom.test.ts +255 -49
  77. package/src/test/TLSyncRoom.test.ts +1024 -534
  78. package/src/test/TestServer.ts +12 -1
  79. package/src/test/customMessages.test.ts +1 -1
  80. package/src/test/presenceMode.test.ts +6 -6
  81. package/src/test/syncFuzz.test.ts +2 -4
  82. package/src/test/upgradeDowngrade.test.ts +290 -8
  83. package/src/test/validation.test.ts +15 -10
  84. package/src/test/pruneTombstones.test.ts +0 -178
@@ -1,178 +0,0 @@
1
- import { BaseRecord, createRecordType, RecordId, StoreSchema } from '@tldraw/store'
2
- import { TLSyncRoom } from '../lib/TLSyncRoom'
3
- import { findMin } from '../lib/findMin'
4
-
5
- interface TestRecord extends BaseRecord<'test', RecordId<TestRecord>> {
6
- name: string
7
- }
8
-
9
- describe('TLSyncRoom pruneTombstones', () => {
10
- let room: TLSyncRoom<TestRecord, any>
11
- let TestRecordType: any
12
- let schema: StoreSchema<TestRecord, any>
13
-
14
- beforeEach(() => {
15
- TestRecordType = createRecordType<TestRecord>('test', {
16
- scope: 'document',
17
- })
18
-
19
- schema = StoreSchema.create({
20
- test: TestRecordType,
21
- })
22
-
23
- // Create room with empty snapshot to avoid default documents being converted to tombstones
24
- room = new TLSyncRoom({
25
- schema,
26
- snapshot: {
27
- clock: 0,
28
- documents: [],
29
- },
30
- })
31
- })
32
-
33
- it('should not prune when tombstone count is below threshold', () => {
34
- // Add some tombstones but below MAX_TOMBSTONES (3000)
35
- for (let i = 0; i < 100; i++) {
36
- room.tombstones.set(`doc${i}`, i + 1)
37
- }
38
-
39
- const initialSize = room.tombstones.size
40
- const initialHistoryClock = room.tombstoneHistoryStartsAtClock
41
-
42
- // Reset needsPrune flag and call pruneTombstones
43
- ;(room as any).needsPrune = true
44
- ;(room as any).pruneTombstones()
45
-
46
- // Should not have pruned anything
47
- expect(room.tombstones.size).toBe(initialSize)
48
- expect(room.tombstoneHistoryStartsAtClock).toBe(initialHistoryClock)
49
-
50
- expect(findMin(room.tombstones.values())).toBeGreaterThanOrEqual(
51
- room.tombstoneHistoryStartsAtClock
52
- )
53
- })
54
-
55
- it('should prune tombstones when count exceeds threshold', () => {
56
- // Add more tombstones than MAX_TOMBSTONES
57
- const totalTombstones = 3200 // Above MAX_TOMBSTONES (3000)
58
- for (let i = 0; i < totalTombstones; i++) {
59
- room.tombstones.set(`doc${i}`, i + 1)
60
- }
61
-
62
- const startCock = room.tombstoneHistoryStartsAtClock
63
-
64
- expect(room.tombstones.size).toBe(totalTombstones)
65
-
66
- // Reset needsPrune flag and call pruneTombstones
67
- ;(room as any).needsPrune = true
68
- ;(room as any).pruneTombstones()
69
-
70
- expect(room.tombstones.size).toBeLessThan(totalTombstones)
71
- expect(room.tombstoneHistoryStartsAtClock).toBeGreaterThan(startCock)
72
-
73
- expect(room.tombstones.size).toMatchInlineSnapshot(`2700`) // should be about 1500
74
- expect(room.tombstoneHistoryStartsAtClock).toMatchInlineSnapshot(`501`) // should be about 1700
75
-
76
- expect(findMin(room.tombstones.values())).toBeGreaterThanOrEqual(
77
- room.tombstoneHistoryStartsAtClock
78
- )
79
- })
80
-
81
- it('should handle tombstones with same clock value correctly', () => {
82
- // Add tombstones with some having the same clock values
83
- const totalTombstones = 3200
84
- for (let i = 0; i < totalTombstones; i++) {
85
- // Use clock values that repeat: 1, 1, 1, ..., 2, 2, 2, ..., 320, 320, 320
86
- const clock = Math.floor(i / 10) + 1
87
- room.tombstones.set(`doc${i}`, clock)
88
- }
89
-
90
- const startCock = room.tombstoneHistoryStartsAtClock
91
-
92
- // Reset needsPrune flag and call pruneTombstones
93
- ;(room as any).needsPrune = true
94
- ;(room as any).pruneTombstones()
95
-
96
- // The algorithm keeps the oldest tombstones (preserving history)
97
- // With repeating clock values, we have 10 tombstones for each clock 1-320
98
- // We keep the oldest 200 tombstones (clocks 1-20)
99
- // We delete the newest 3000 tombstones (clocks 21-320)
100
- expect(room.tombstones.size).toBeLessThan(totalTombstones)
101
- expect(room.tombstoneHistoryStartsAtClock).toBeGreaterThan(startCock)
102
-
103
- expect(room.tombstones.size).toMatchInlineSnapshot(`2700`) // should be about 1500
104
- expect(room.tombstoneHistoryStartsAtClock).toMatchInlineSnapshot(`51`) // should be about 150
105
-
106
- expect(findMin(room.tombstones.values())).toBeGreaterThanOrEqual(
107
- room.tombstoneHistoryStartsAtClock
108
- )
109
- })
110
-
111
- it('should handle edge case where all tombstones have same clock value', () => {
112
- // Add tombstones all with the same clock value
113
- const totalTombstones = 3200
114
- const sameClock = 100
115
- for (let i = 0; i < totalTombstones; i++) {
116
- room.tombstones.set(`doc${i}`, sameClock)
117
- }
118
-
119
- const startClock = room.tombstoneHistoryStartsAtClock
120
-
121
- // Reset needsPrune flag and call pruneTombstones
122
- ;(room as any).needsPrune = true
123
- ;(room as any).pruneTombstones()
124
-
125
- // When all tombstones have the same clock value, the algorithm deletes all of them
126
- // because the while loop advances to the end and all tombstones are marked for deletion
127
- expect(room.tombstones.size).toBeLessThan(totalTombstones)
128
- expect(room.tombstoneHistoryStartsAtClock).toBeGreaterThan(startClock)
129
-
130
- expect(room.tombstones.size).toMatchInlineSnapshot(`0`) // all deleted
131
- expect(room.tombstoneHistoryStartsAtClock).toMatchInlineSnapshot(`101`) // next clock after deletion
132
-
133
- expect(findMin(room.tombstones.values())).toBe(null) // findMin returns null for empty collections
134
- })
135
-
136
- it('should handle exact threshold case', () => {
137
- // Add exactly MAX_TOMBSTONES tombstones
138
- for (let i = 0; i < 3000; i++) {
139
- room.tombstones.set(`doc${i}`, i + 1)
140
- }
141
-
142
- const initialSize = room.tombstones.size
143
- expect(initialSize).toBe(3000)
144
-
145
- // Reset needsPrune flag and call pruneTombstones
146
- ;(room as any).needsPrune = true
147
- ;(room as any).pruneTombstones()
148
-
149
- // Should not prune anything since we're exactly at the threshold
150
- expect(room.tombstones.size).toBe(initialSize)
151
- })
152
-
153
- it('should handle very large tombstone counts', () => {
154
- // Test with a much larger number of tombstones
155
- const totalTombstones = 10000
156
- for (let i = 0; i < totalTombstones; i++) {
157
- room.tombstones.set(`doc${i}`, i + 1)
158
- }
159
-
160
- const startClock = room.tombstoneHistoryStartsAtClock
161
-
162
- // Reset needsPrune flag and call pruneTombstones
163
- ;(room as any).needsPrune = true
164
- ;(room as any).pruneTombstones()
165
-
166
- // The algorithm keeps the oldest tombstones (preserving history)
167
- // With 10000 tombstones, we keep about 7000 and delete about 3000
168
- expect(room.tombstones.size).toBeLessThan(totalTombstones)
169
- expect(room.tombstoneHistoryStartsAtClock).toBeGreaterThan(startClock)
170
-
171
- expect(room.tombstones.size).toMatchInlineSnapshot(`2700`) // should be about 1500
172
- expect(room.tombstoneHistoryStartsAtClock).toMatchInlineSnapshot(`7301`) // should be about 1500
173
-
174
- expect(findMin(room.tombstones.values())).toBeGreaterThanOrEqual(
175
- room.tombstoneHistoryStartsAtClock
176
- )
177
- })
178
- })