@tldraw/sync-core 5.4.0-next.ef99ab47e5ce → 5.5.0-next.6d9e51e9f3f7

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.
@@ -122,6 +122,43 @@ describe('loadSnapshotIntoStorage', () => {
122
122
  expect(storage.getClock()).toBe(5)
123
123
  })
124
124
 
125
+ it('[SL3] does not delete while a key iterator is open (better-sqlite3 rejects that)', () => {
126
+ const extraPage = makePage('extra', 'Extra')
127
+ const storage = new InMemorySyncStorage<TLRecord>({
128
+ snapshot: makeContractSnapshot([...contractRecords, extraPage]),
129
+ })
130
+
131
+ storage.transaction((txn) => {
132
+ // better-sqlite3 throws on any write while a statement iterator is still open; mimic
133
+ // that so a delete issued from inside `for (const id of txn.keys())` fails loudly
134
+ let openIterators = 0
135
+ const guarded: typeof txn = Object.create(txn, {
136
+ keys: {
137
+ value: function* () {
138
+ openIterators++
139
+ try {
140
+ yield* Array.from(txn.keys())
141
+ } finally {
142
+ openIterators--
143
+ }
144
+ },
145
+ },
146
+ delete: {
147
+ value: (id: string) => {
148
+ if (openIterators > 0) {
149
+ throw new Error('This database connection is busy executing a query')
150
+ }
151
+ return txn.delete(id)
152
+ },
153
+ },
154
+ })
155
+ loadSnapshotIntoStorage(guarded, contractSchema, makeContractSnapshot(contractRecords))
156
+ })
157
+
158
+ expect(storage.documents.has(extraPage.id)).toBe(false)
159
+ expect(storage.tombstones.has(extraPage.id)).toBe(true)
160
+ })
161
+
125
162
  it('[SL4] persists the snapshot schema and migrates loaded records to the current version', () => {
126
163
  interface TestRecord extends BaseRecord<'test', RecordId<TestRecord>> {
127
164
  value: number
@@ -1,6 +1,6 @@
1
1
  import { StoreSchema, SynchronousStorage, UnknownRecord } from '@tldraw/store'
2
+ import { TLStoreSnapshot } from '@tldraw/tlschema'
2
3
  import { assert, isEqual, objectMapEntriesIterable, objectMapValues } from '@tldraw/utils'
3
- import { TLStoreSnapshot } from 'tldraw'
4
4
  import { diffRecord, NetworkDiff, RecordOpType } from './diff'
5
5
  import { RoomSnapshot } from './TLSyncRoom'
6
6
 
@@ -205,7 +205,9 @@ export function loadSnapshotIntoStorage<R extends UnknownRecord>(
205
205
  if (isEqual(existing, doc.state)) continue
206
206
  txn.set(doc.state.id, doc.state as R)
207
207
  }
208
- for (const id of txn.keys()) {
208
+ // materialize the keys first: some SQLite drivers (better-sqlite3) refuse writes while a
209
+ // statement iterator is open, so deleting during `keys()` would throw
210
+ for (const id of Array.from(txn.keys())) {
209
211
  if (!docIds.has(id)) {
210
212
  txn.delete(id)
211
213
  }