@tldraw/sync-core 4.2.1 → 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.
- package/dist-cjs/index.d.ts +483 -58
- package/dist-cjs/index.js +13 -3
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/DurableObjectSqliteSyncWrapper.js +55 -0
- package/dist-cjs/lib/DurableObjectSqliteSyncWrapper.js.map +7 -0
- package/dist-cjs/lib/InMemorySyncStorage.js +287 -0
- package/dist-cjs/lib/InMemorySyncStorage.js.map +7 -0
- package/dist-cjs/lib/MicrotaskNotifier.js +50 -0
- package/dist-cjs/lib/MicrotaskNotifier.js.map +7 -0
- package/dist-cjs/lib/NodeSqliteWrapper.js +48 -0
- package/dist-cjs/lib/NodeSqliteWrapper.js.map +7 -0
- package/dist-cjs/lib/RoomSession.js.map +1 -1
- package/dist-cjs/lib/SQLiteSyncStorage.js +428 -0
- package/dist-cjs/lib/SQLiteSyncStorage.js.map +7 -0
- package/dist-cjs/lib/TLSocketRoom.js +117 -69
- package/dist-cjs/lib/TLSocketRoom.js.map +2 -2
- package/dist-cjs/lib/TLSyncClient.js +7 -0
- package/dist-cjs/lib/TLSyncClient.js.map +2 -2
- package/dist-cjs/lib/TLSyncRoom.js +357 -688
- package/dist-cjs/lib/TLSyncRoom.js.map +3 -3
- package/dist-cjs/lib/TLSyncStorage.js +76 -0
- package/dist-cjs/lib/TLSyncStorage.js.map +7 -0
- package/dist-cjs/lib/chunk.js +2 -2
- package/dist-cjs/lib/chunk.js.map +1 -1
- package/dist-cjs/lib/recordDiff.js +52 -0
- package/dist-cjs/lib/recordDiff.js.map +7 -0
- package/dist-esm/index.d.mts +483 -58
- package/dist-esm/index.mjs +20 -5
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/DurableObjectSqliteSyncWrapper.mjs +35 -0
- package/dist-esm/lib/DurableObjectSqliteSyncWrapper.mjs.map +7 -0
- package/dist-esm/lib/InMemorySyncStorage.mjs +272 -0
- package/dist-esm/lib/InMemorySyncStorage.mjs.map +7 -0
- package/dist-esm/lib/MicrotaskNotifier.mjs +30 -0
- package/dist-esm/lib/MicrotaskNotifier.mjs.map +7 -0
- package/dist-esm/lib/NodeSqliteWrapper.mjs +28 -0
- package/dist-esm/lib/NodeSqliteWrapper.mjs.map +7 -0
- package/dist-esm/lib/RoomSession.mjs.map +1 -1
- package/dist-esm/lib/SQLiteSyncStorage.mjs +414 -0
- package/dist-esm/lib/SQLiteSyncStorage.mjs.map +7 -0
- package/dist-esm/lib/TLSocketRoom.mjs +121 -70
- package/dist-esm/lib/TLSocketRoom.mjs.map +2 -2
- package/dist-esm/lib/TLSyncClient.mjs +7 -0
- package/dist-esm/lib/TLSyncClient.mjs.map +2 -2
- package/dist-esm/lib/TLSyncRoom.mjs +370 -702
- package/dist-esm/lib/TLSyncRoom.mjs.map +3 -3
- package/dist-esm/lib/TLSyncStorage.mjs +56 -0
- package/dist-esm/lib/TLSyncStorage.mjs.map +7 -0
- package/dist-esm/lib/chunk.mjs +2 -2
- package/dist-esm/lib/chunk.mjs.map +1 -1
- package/dist-esm/lib/recordDiff.mjs +32 -0
- package/dist-esm/lib/recordDiff.mjs.map +7 -0
- package/package.json +12 -11
- package/src/index.ts +32 -3
- package/src/lib/ClientWebSocketAdapter.test.ts +3 -0
- package/src/lib/DurableObjectSqliteSyncWrapper.ts +95 -0
- package/src/lib/InMemorySyncStorage.ts +387 -0
- package/src/lib/MicrotaskNotifier.test.ts +429 -0
- package/src/lib/MicrotaskNotifier.ts +38 -0
- package/src/lib/NodeSqliteSyncWrapper.integration.test.ts +270 -0
- package/src/lib/NodeSqliteSyncWrapper.test.ts +272 -0
- package/src/lib/NodeSqliteWrapper.ts +99 -0
- package/src/lib/RoomSession.test.ts +1 -0
- package/src/lib/RoomSession.ts +2 -0
- package/src/lib/SQLiteSyncStorage.ts +627 -0
- package/src/lib/TLSocketRoom.ts +228 -114
- package/src/lib/TLSyncClient.ts +12 -0
- package/src/lib/TLSyncRoom.ts +473 -913
- package/src/lib/TLSyncStorage.ts +216 -0
- package/src/lib/chunk.ts +2 -2
- package/src/lib/computeTombstonePruning.test.ts +352 -0
- package/src/lib/recordDiff.ts +73 -0
- package/src/test/FuzzEditor.ts +4 -5
- package/src/test/InMemorySyncStorage.test.ts +1684 -0
- package/src/test/SQLiteSyncStorage.test.ts +1378 -0
- package/src/test/TLSocketRoom.test.ts +255 -49
- package/src/test/TLSyncRoom.test.ts +1024 -534
- package/src/test/TestServer.ts +12 -1
- package/src/test/customMessages.test.ts +1 -1
- package/src/test/presenceMode.test.ts +6 -6
- package/src/test/syncFuzz.test.ts +2 -4
- package/src/test/upgradeDowngrade.test.ts +290 -8
- package/src/test/validation.test.ts +15 -10
- package/src/test/pruneTombstones.test.ts +0 -178
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var SQLiteSyncStorage_exports = {};
|
|
20
|
+
__export(SQLiteSyncStorage_exports, {
|
|
21
|
+
SQLiteSyncStorage: () => SQLiteSyncStorage,
|
|
22
|
+
migrateSqliteSyncStorage: () => migrateSqliteSyncStorage
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(SQLiteSyncStorage_exports);
|
|
25
|
+
var import_state = require("@tldraw/state");
|
|
26
|
+
var import_utils = require("@tldraw/utils");
|
|
27
|
+
var import_InMemorySyncStorage = require("./InMemorySyncStorage");
|
|
28
|
+
var import_MicrotaskNotifier = require("./MicrotaskNotifier");
|
|
29
|
+
var import_TLSyncStorage = require("./TLSyncStorage");
|
|
30
|
+
function migrateSqliteSyncStorage(storage, {
|
|
31
|
+
documentsTable = "documents",
|
|
32
|
+
tombstonesTable = "tombstones",
|
|
33
|
+
metadataTable = "metadata"
|
|
34
|
+
} = {}) {
|
|
35
|
+
let migrationVersion = 0;
|
|
36
|
+
try {
|
|
37
|
+
const row = storage.prepare(`SELECT migrationVersion FROM ${metadataTable} LIMIT 1`).all()[0];
|
|
38
|
+
migrationVersion = row?.migrationVersion ?? 0;
|
|
39
|
+
} catch (_e) {
|
|
40
|
+
}
|
|
41
|
+
if (migrationVersion === 0) {
|
|
42
|
+
migrationVersion++;
|
|
43
|
+
storage.exec(`
|
|
44
|
+
CREATE TABLE ${documentsTable} (
|
|
45
|
+
id TEXT PRIMARY KEY,
|
|
46
|
+
state BLOB NOT NULL,
|
|
47
|
+
lastChangedClock INTEGER NOT NULL
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE INDEX idx_${documentsTable}_lastChangedClock ON ${documentsTable}(lastChangedClock);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE ${tombstonesTable} (
|
|
53
|
+
id TEXT PRIMARY KEY,
|
|
54
|
+
clock INTEGER NOT NULL
|
|
55
|
+
);
|
|
56
|
+
CREATE INDEX idx_${tombstonesTable}_clock ON ${tombstonesTable}(clock);
|
|
57
|
+
|
|
58
|
+
-- This table is used to store the metadata for the sync storage.
|
|
59
|
+
-- There should only be one row in this table.
|
|
60
|
+
CREATE TABLE ${metadataTable} (
|
|
61
|
+
migrationVersion INTEGER NOT NULL,
|
|
62
|
+
documentClock INTEGER NOT NULL,
|
|
63
|
+
tombstoneHistoryStartsAtClock INTEGER NOT NULL,
|
|
64
|
+
schema TEXT NOT NULL
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
INSERT INTO ${metadataTable} (migrationVersion, documentClock, tombstoneHistoryStartsAtClock, schema) VALUES (2, 0, 0, '')
|
|
68
|
+
`);
|
|
69
|
+
migrationVersion++;
|
|
70
|
+
}
|
|
71
|
+
if (migrationVersion === 1) {
|
|
72
|
+
migrationVersion++;
|
|
73
|
+
storage.exec(`
|
|
74
|
+
CREATE TABLE ${documentsTable}_new (
|
|
75
|
+
id TEXT PRIMARY KEY,
|
|
76
|
+
state BLOB NOT NULL,
|
|
77
|
+
lastChangedClock INTEGER NOT NULL
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
INSERT INTO ${documentsTable}_new (id, state, lastChangedClock)
|
|
81
|
+
SELECT id, CAST(state AS BLOB), lastChangedClock FROM ${documentsTable};
|
|
82
|
+
|
|
83
|
+
DROP TABLE ${documentsTable};
|
|
84
|
+
|
|
85
|
+
ALTER TABLE ${documentsTable}_new RENAME TO ${documentsTable};
|
|
86
|
+
|
|
87
|
+
CREATE INDEX idx_${documentsTable}_lastChangedClock ON ${documentsTable}(lastChangedClock);
|
|
88
|
+
`);
|
|
89
|
+
}
|
|
90
|
+
storage.exec(`UPDATE ${metadataTable} SET migrationVersion = ${migrationVersion}`);
|
|
91
|
+
}
|
|
92
|
+
const textEncoder = new TextEncoder();
|
|
93
|
+
const textDecoder = new TextDecoder();
|
|
94
|
+
function encodeState(state) {
|
|
95
|
+
return textEncoder.encode(JSON.stringify(state));
|
|
96
|
+
}
|
|
97
|
+
function decodeState(state) {
|
|
98
|
+
return JSON.parse(textDecoder.decode(state));
|
|
99
|
+
}
|
|
100
|
+
class SQLiteSyncStorage {
|
|
101
|
+
/**
|
|
102
|
+
* Check if the storage has been initialized (has data in the clock table).
|
|
103
|
+
* Useful for determining whether to load from an external source on first access.
|
|
104
|
+
*/
|
|
105
|
+
static hasBeenInitialized(storage) {
|
|
106
|
+
const prefix = storage.config?.tablePrefix ?? "";
|
|
107
|
+
try {
|
|
108
|
+
const schema = storage.prepare(`SELECT schema FROM ${prefix}metadata LIMIT 1`).all()[0]?.schema;
|
|
109
|
+
return !!schema;
|
|
110
|
+
} catch (_e) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get the current document clock value from storage without fully initializing.
|
|
116
|
+
* Returns null if storage has not been initialized.
|
|
117
|
+
* Useful for comparing storage freshness against external sources.
|
|
118
|
+
*/
|
|
119
|
+
static getDocumentClock(storage) {
|
|
120
|
+
const prefix = storage.config?.tablePrefix ?? "";
|
|
121
|
+
try {
|
|
122
|
+
const row = storage.prepare(`SELECT documentClock FROM ${prefix}metadata LIMIT 1`).all()[0];
|
|
123
|
+
if (row && SQLiteSyncStorage.hasBeenInitialized(storage)) {
|
|
124
|
+
return row.documentClock;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
} catch (_e) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Prepared statements - created once, reused many times
|
|
132
|
+
stmts;
|
|
133
|
+
sql;
|
|
134
|
+
constructor({
|
|
135
|
+
sql,
|
|
136
|
+
snapshot,
|
|
137
|
+
onChange
|
|
138
|
+
}) {
|
|
139
|
+
this.sql = sql;
|
|
140
|
+
const prefix = sql.config?.tablePrefix ?? "";
|
|
141
|
+
const documentsTable = `${prefix}documents`;
|
|
142
|
+
const tombstonesTable = `${prefix}tombstones`;
|
|
143
|
+
const metadataTable = `${prefix}metadata`;
|
|
144
|
+
migrateSqliteSyncStorage(this.sql, { documentsTable, tombstonesTable, metadataTable });
|
|
145
|
+
this.stmts = {
|
|
146
|
+
// Metadata
|
|
147
|
+
getDocumentClock: this.sql.prepare(
|
|
148
|
+
`SELECT documentClock FROM ${metadataTable} LIMIT 1`
|
|
149
|
+
),
|
|
150
|
+
getTombstoneHistoryStartsAtClock: this.sql.prepare(
|
|
151
|
+
`SELECT tombstoneHistoryStartsAtClock FROM ${metadataTable}`
|
|
152
|
+
),
|
|
153
|
+
getSchema: this.sql.prepare(`SELECT schema FROM ${metadataTable}`),
|
|
154
|
+
setSchema: this.sql.prepare(`UPDATE ${metadataTable} SET schema = ?`),
|
|
155
|
+
setTombstoneHistoryStartsAtClock: this.sql.prepare(
|
|
156
|
+
`UPDATE ${metadataTable} SET tombstoneHistoryStartsAtClock = ?`
|
|
157
|
+
),
|
|
158
|
+
incrementDocumentClock: this.sql.prepare(
|
|
159
|
+
`UPDATE ${metadataTable} SET documentClock = documentClock + 1`
|
|
160
|
+
),
|
|
161
|
+
// Documents
|
|
162
|
+
getDocument: this.sql.prepare(
|
|
163
|
+
`SELECT state FROM ${documentsTable} WHERE id = ?`
|
|
164
|
+
),
|
|
165
|
+
insertDocument: this.sql.prepare(`INSERT OR REPLACE INTO ${documentsTable} (id, state, lastChangedClock) VALUES (?, ?, ?)`),
|
|
166
|
+
deleteDocument: this.sql.prepare(
|
|
167
|
+
`DELETE FROM ${documentsTable} WHERE id = ?`
|
|
168
|
+
),
|
|
169
|
+
documentExists: this.sql.prepare(
|
|
170
|
+
`SELECT id FROM ${documentsTable} WHERE id = ?`
|
|
171
|
+
),
|
|
172
|
+
iterateDocuments: this.sql.prepare(
|
|
173
|
+
`SELECT state, lastChangedClock FROM ${documentsTable}`
|
|
174
|
+
),
|
|
175
|
+
iterateDocumentEntries: this.sql.prepare(
|
|
176
|
+
`SELECT id, state FROM ${documentsTable}`
|
|
177
|
+
),
|
|
178
|
+
iterateDocumentKeys: this.sql.prepare(`SELECT id FROM ${documentsTable}`),
|
|
179
|
+
iterateDocumentValues: this.sql.prepare(
|
|
180
|
+
`SELECT state FROM ${documentsTable}`
|
|
181
|
+
),
|
|
182
|
+
getDocumentsChangedSince: this.sql.prepare(
|
|
183
|
+
`SELECT state FROM ${documentsTable} WHERE lastChangedClock > ?`
|
|
184
|
+
),
|
|
185
|
+
// Tombstones
|
|
186
|
+
insertTombstone: this.sql.prepare(
|
|
187
|
+
`INSERT OR REPLACE INTO ${tombstonesTable} (id, clock) VALUES (?, ?)`
|
|
188
|
+
),
|
|
189
|
+
deleteTombstone: this.sql.prepare(
|
|
190
|
+
`DELETE FROM ${tombstonesTable} WHERE id = ?`
|
|
191
|
+
),
|
|
192
|
+
deleteTombstonesBefore: this.sql.prepare(
|
|
193
|
+
`DELETE FROM ${tombstonesTable} WHERE clock < ?`
|
|
194
|
+
),
|
|
195
|
+
countTombstones: this.sql.prepare(
|
|
196
|
+
`SELECT count(*) as count FROM ${tombstonesTable}`
|
|
197
|
+
),
|
|
198
|
+
iterateTombstones: this.sql.prepare(
|
|
199
|
+
`SELECT id, clock FROM ${tombstonesTable} ORDER BY clock ASC`
|
|
200
|
+
),
|
|
201
|
+
getTombstonesChangedSince: this.sql.prepare(
|
|
202
|
+
`SELECT id FROM ${tombstonesTable} WHERE clock > ?`
|
|
203
|
+
),
|
|
204
|
+
// Initial setup (only used when loading a snapshot)
|
|
205
|
+
updateMetadata: this.sql.prepare(
|
|
206
|
+
`UPDATE ${metadataTable} SET documentClock = ?, tombstoneHistoryStartsAtClock = ?, schema = ?`
|
|
207
|
+
)
|
|
208
|
+
};
|
|
209
|
+
const hasData = SQLiteSyncStorage.hasBeenInitialized(sql);
|
|
210
|
+
if (snapshot || !hasData) {
|
|
211
|
+
snapshot = (0, import_TLSyncStorage.convertStoreSnapshotToRoomSnapshot)(snapshot ?? import_InMemorySyncStorage.DEFAULT_INITIAL_SNAPSHOT);
|
|
212
|
+
const documentClock = snapshot.documentClock ?? snapshot.clock ?? 0;
|
|
213
|
+
const tombstoneHistoryStartsAtClock = snapshot.tombstoneHistoryStartsAtClock ?? documentClock;
|
|
214
|
+
this.sql.exec(`
|
|
215
|
+
DELETE FROM ${documentsTable};
|
|
216
|
+
DELETE FROM ${tombstonesTable};
|
|
217
|
+
`);
|
|
218
|
+
for (const doc of snapshot.documents) {
|
|
219
|
+
this.stmts.insertDocument.run(doc.state.id, encodeState(doc.state), doc.lastChangedClock);
|
|
220
|
+
}
|
|
221
|
+
if (snapshot.tombstones) {
|
|
222
|
+
for (const [id, clock] of (0, import_utils.objectMapEntries)(snapshot.tombstones)) {
|
|
223
|
+
this.stmts.insertTombstone.run(id, clock);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
this.stmts.updateMetadata.run(
|
|
227
|
+
documentClock,
|
|
228
|
+
tombstoneHistoryStartsAtClock,
|
|
229
|
+
JSON.stringify(snapshot.schema)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
if (onChange) {
|
|
233
|
+
this.onChange(onChange);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
notifier = new import_MicrotaskNotifier.MicrotaskNotifier();
|
|
237
|
+
onChange(callback) {
|
|
238
|
+
return this.notifier.register(callback);
|
|
239
|
+
}
|
|
240
|
+
transaction(callback, opts) {
|
|
241
|
+
const clockBefore = this.getClock();
|
|
242
|
+
const trackChanges = opts?.emitChanges === "always";
|
|
243
|
+
return this.sql.transaction(() => {
|
|
244
|
+
const txn = new SQLiteSyncStorageTransaction(this, this.stmts);
|
|
245
|
+
let result;
|
|
246
|
+
let changes;
|
|
247
|
+
try {
|
|
248
|
+
result = (0, import_state.transaction)(() => {
|
|
249
|
+
return callback(txn);
|
|
250
|
+
});
|
|
251
|
+
if (trackChanges) {
|
|
252
|
+
changes = txn.getChangesSince(clockBefore)?.diff;
|
|
253
|
+
}
|
|
254
|
+
} finally {
|
|
255
|
+
txn.close();
|
|
256
|
+
}
|
|
257
|
+
if (typeof result === "object" && result && "then" in result && typeof result.then === "function") {
|
|
258
|
+
throw new Error("Transaction must return a value, not a promise");
|
|
259
|
+
}
|
|
260
|
+
const clockAfter = this.getClock();
|
|
261
|
+
const didChange = clockAfter > clockBefore;
|
|
262
|
+
if (didChange) {
|
|
263
|
+
this.notifier.notify({ id: opts?.id, documentClock: clockAfter });
|
|
264
|
+
}
|
|
265
|
+
return { documentClock: clockAfter, didChange: clockAfter > clockBefore, result, changes };
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
getClock() {
|
|
269
|
+
const clockRow = this.stmts.getDocumentClock.all()[0];
|
|
270
|
+
return clockRow?.documentClock ?? 0;
|
|
271
|
+
}
|
|
272
|
+
/** @internal */
|
|
273
|
+
_getTombstoneHistoryStartsAtClock() {
|
|
274
|
+
const clockRow = this.stmts.getTombstoneHistoryStartsAtClock.all()[0];
|
|
275
|
+
return clockRow?.tombstoneHistoryStartsAtClock ?? 0;
|
|
276
|
+
}
|
|
277
|
+
/** @internal */
|
|
278
|
+
_getSchema() {
|
|
279
|
+
const clockRow = this.stmts.getSchema.all()[0];
|
|
280
|
+
(0, import_utils.assert)(clockRow, "Storage not initialized - clock row missing");
|
|
281
|
+
return JSON.parse(clockRow.schema);
|
|
282
|
+
}
|
|
283
|
+
/** @internal */
|
|
284
|
+
_setSchema(schema) {
|
|
285
|
+
this.stmts.setSchema.run(JSON.stringify(schema));
|
|
286
|
+
}
|
|
287
|
+
/** @internal */
|
|
288
|
+
pruneTombstones = (0, import_utils.throttle)(
|
|
289
|
+
() => {
|
|
290
|
+
const tombstoneCount = this.stmts.countTombstones.all()[0].count;
|
|
291
|
+
if (tombstoneCount > import_InMemorySyncStorage.MAX_TOMBSTONES) {
|
|
292
|
+
const tombstones = this.stmts.iterateTombstones.all();
|
|
293
|
+
const result = (0, import_InMemorySyncStorage.computeTombstonePruning)({ tombstones, documentClock: this.getClock() });
|
|
294
|
+
if (result) {
|
|
295
|
+
this.stmts.setTombstoneHistoryStartsAtClock.run(result.newTombstoneHistoryStartsAtClock);
|
|
296
|
+
this.stmts.deleteTombstonesBefore.run(result.newTombstoneHistoryStartsAtClock);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
1e3,
|
|
301
|
+
// prevent this from running synchronously to avoid blocking requests
|
|
302
|
+
{ leading: false }
|
|
303
|
+
);
|
|
304
|
+
getSnapshot() {
|
|
305
|
+
return {
|
|
306
|
+
tombstoneHistoryStartsAtClock: this._getTombstoneHistoryStartsAtClock(),
|
|
307
|
+
documentClock: this.getClock(),
|
|
308
|
+
documents: Array.from(this._iterateDocuments()),
|
|
309
|
+
tombstones: Object.fromEntries(this._iterateTombstones()),
|
|
310
|
+
schema: this._getSchema()
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
*_iterateDocuments() {
|
|
314
|
+
for (const row of this.stmts.iterateDocuments.iterate()) {
|
|
315
|
+
yield { state: decodeState(row.state), lastChangedClock: row.lastChangedClock };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
*_iterateTombstones() {
|
|
319
|
+
for (const row of this.stmts.iterateTombstones.iterate()) {
|
|
320
|
+
yield [row.id, row.clock];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
class SQLiteSyncStorageTransaction {
|
|
325
|
+
constructor(storage, stmts) {
|
|
326
|
+
this.storage = storage;
|
|
327
|
+
this.stmts = stmts;
|
|
328
|
+
this._clock = this.storage.getClock();
|
|
329
|
+
}
|
|
330
|
+
_clock;
|
|
331
|
+
_closed = false;
|
|
332
|
+
_didIncrementClock = false;
|
|
333
|
+
/** @internal */
|
|
334
|
+
close() {
|
|
335
|
+
this._closed = true;
|
|
336
|
+
}
|
|
337
|
+
assertNotClosed() {
|
|
338
|
+
(0, import_utils.assert)(!this._closed, "Transaction has ended, iterator cannot be consumed");
|
|
339
|
+
}
|
|
340
|
+
getClock() {
|
|
341
|
+
return this._clock;
|
|
342
|
+
}
|
|
343
|
+
getNextClock() {
|
|
344
|
+
if (!this._didIncrementClock) {
|
|
345
|
+
this._didIncrementClock = true;
|
|
346
|
+
this.stmts.incrementDocumentClock.run();
|
|
347
|
+
this._clock = this.storage.getClock();
|
|
348
|
+
}
|
|
349
|
+
return this._clock;
|
|
350
|
+
}
|
|
351
|
+
get(id) {
|
|
352
|
+
this.assertNotClosed();
|
|
353
|
+
const row = this.stmts.getDocument.all(id)[0];
|
|
354
|
+
if (!row) return void 0;
|
|
355
|
+
return decodeState(row.state);
|
|
356
|
+
}
|
|
357
|
+
set(id, record) {
|
|
358
|
+
this.assertNotClosed();
|
|
359
|
+
(0, import_utils.assert)(id === record.id, `Record id mismatch: key does not match record.id`);
|
|
360
|
+
const clock = this.getNextClock();
|
|
361
|
+
this.stmts.deleteTombstone.run(id);
|
|
362
|
+
this.stmts.insertDocument.run(id, encodeState(record), clock);
|
|
363
|
+
}
|
|
364
|
+
delete(id) {
|
|
365
|
+
this.assertNotClosed();
|
|
366
|
+
const exists = this.stmts.documentExists.all(id)[0];
|
|
367
|
+
if (!exists) return;
|
|
368
|
+
const clock = this.getNextClock();
|
|
369
|
+
this.stmts.deleteDocument.run(id);
|
|
370
|
+
this.stmts.insertTombstone.run(id, clock);
|
|
371
|
+
this.storage.pruneTombstones();
|
|
372
|
+
}
|
|
373
|
+
*entries() {
|
|
374
|
+
this.assertNotClosed();
|
|
375
|
+
for (const row of this.stmts.iterateDocumentEntries.iterate()) {
|
|
376
|
+
this.assertNotClosed();
|
|
377
|
+
yield [row.id, decodeState(row.state)];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
*keys() {
|
|
381
|
+
this.assertNotClosed();
|
|
382
|
+
for (const row of this.stmts.iterateDocumentKeys.iterate()) {
|
|
383
|
+
this.assertNotClosed();
|
|
384
|
+
yield row.id;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
*values() {
|
|
388
|
+
this.assertNotClosed();
|
|
389
|
+
for (const row of this.stmts.iterateDocumentValues.iterate()) {
|
|
390
|
+
this.assertNotClosed();
|
|
391
|
+
yield decodeState(row.state);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
getSchema() {
|
|
395
|
+
this.assertNotClosed();
|
|
396
|
+
return this.storage._getSchema();
|
|
397
|
+
}
|
|
398
|
+
setSchema(schema) {
|
|
399
|
+
this.assertNotClosed();
|
|
400
|
+
this.storage._setSchema(schema);
|
|
401
|
+
}
|
|
402
|
+
getChangesSince(sinceClock) {
|
|
403
|
+
this.assertNotClosed();
|
|
404
|
+
const clock = this.storage.getClock();
|
|
405
|
+
if (sinceClock === clock) return void 0;
|
|
406
|
+
if (sinceClock > clock) {
|
|
407
|
+
sinceClock = -1;
|
|
408
|
+
}
|
|
409
|
+
const diff = { puts: {}, deletes: [] };
|
|
410
|
+
const wipeAll = sinceClock < this.storage._getTombstoneHistoryStartsAtClock();
|
|
411
|
+
if (wipeAll) {
|
|
412
|
+
for (const row of this.stmts.iterateDocumentValues.iterate()) {
|
|
413
|
+
const state = decodeState(row.state);
|
|
414
|
+
diff.puts[state.id] = state;
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
for (const row of this.stmts.getDocumentsChangedSince.iterate(sinceClock)) {
|
|
418
|
+
const state = decodeState(row.state);
|
|
419
|
+
diff.puts[state.id] = state;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
for (const row of this.stmts.getTombstonesChangedSince.iterate(sinceClock)) {
|
|
423
|
+
diff.deletes.push(row.id);
|
|
424
|
+
}
|
|
425
|
+
return { diff, wipeAll };
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=SQLiteSyncStorage.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/SQLiteSyncStorage.ts"],
|
|
4
|
+
"sourcesContent": ["import { transaction } from '@tldraw/state'\nimport { SerializedSchema, StoreSnapshot, UnknownRecord } from '@tldraw/store'\nimport { assert, objectMapEntries, throttle } from '@tldraw/utils'\nimport {\n\tcomputeTombstonePruning,\n\tDEFAULT_INITIAL_SNAPSHOT,\n\tMAX_TOMBSTONES,\n} from './InMemorySyncStorage'\nimport { MicrotaskNotifier } from './MicrotaskNotifier'\nimport { RoomSnapshot } from './TLSyncRoom'\nimport {\n\tconvertStoreSnapshotToRoomSnapshot,\n\tTLSyncForwardDiff,\n\tTLSyncStorage,\n\tTLSyncStorageGetChangesSinceResult,\n\tTLSyncStorageOnChangeCallbackProps,\n\tTLSyncStorageTransaction,\n\tTLSyncStorageTransactionCallback,\n\tTLSyncStorageTransactionOptions,\n\tTLSyncStorageTransactionResult,\n} from './TLSyncStorage'\n\n/**\n * Valid input value types for SQLite query parameters.\n * These are the types that can be passed as bindings to prepared statements.\n * @public\n */\nexport type TLSqliteInputValue = null | number | bigint | string | Uint8Array\n\n/**\n * Possible output value types returned from SQLite queries.\n * Includes all input types plus Uint8Array for BLOB columns.\n * @public\n */\nexport type TLSqliteOutputValue = null | number | bigint | string | Uint8Array\n\n/**\n * A row returned from a SQLite query, mapping column names to their values.\n * @public\n */\nexport type TLSqliteRow = Record<string, TLSqliteOutputValue>\n\n/**\n * A prepared statement that can be executed multiple times with different bindings.\n * @public\n */\nexport interface TLSyncSqliteStatement<\n\tTResult extends TLSqliteRow | void,\n\tTParams extends TLSqliteInputValue[] = [],\n> {\n\t/** Execute the statement and iterate over results one at a time */\n\titerate(...bindings: TParams): IterableIterator<TResult>\n\t/** Execute the statement and return all results as an array */\n\tall(...bindings: TParams): TResult[]\n\t/** Execute the statement without returning results (for DML) */\n\trun(...bindings: TParams): void\n}\n\n/**\n * Configuration for SQLiteSyncStorage.\n * @public\n */\nexport interface TLSyncSqliteWrapperConfig {\n\t/** Prefix for all table names (default: ''). E.g. 'sync_' creates tables 'sync_documents', 'sync_tombstones', 'sync_metadata' */\n\ttablePrefix?: string\n}\n\n/**\n * Interface for SQLite storage with prepare, exec and transaction capabilities.\n * @public\n */\nexport interface TLSyncSqliteWrapper {\n\t/** Optional configuration for table names. If not provided, defaults are used. */\n\treadonly config?: TLSyncSqliteWrapperConfig\n\t/** Prepare a SQL statement for execution */\n\tprepare<TResult extends TLSqliteRow | void, TParams extends TLSqliteInputValue[] = []>(\n\t\tsql: string\n\t): TLSyncSqliteStatement<TResult, TParams>\n\t/** Execute raw SQL (for DDL, multi-statement scripts) */\n\texec(sql: string): void\n\t/** Execute a callback within a transaction */\n\ttransaction<T>(callback: () => T): T\n}\n\nexport function migrateSqliteSyncStorage(\n\tstorage: TLSyncSqliteWrapper,\n\t{\n\t\tdocumentsTable = 'documents',\n\t\ttombstonesTable = 'tombstones',\n\t\tmetadataTable = 'metadata',\n\t}: { documentsTable?: string; tombstonesTable?: string; metadataTable?: string } = {}\n): void {\n\tlet migrationVersion = 0\n\ttry {\n\t\tconst row = storage\n\t\t\t.prepare<{\n\t\t\t\tmigrationVersion: number\n\t\t\t}>(`SELECT migrationVersion FROM ${metadataTable} LIMIT 1`)\n\t\t\t.all()[0]\n\t\tmigrationVersion = row?.migrationVersion ?? 0\n\t} catch (_e) {\n\t\t// noop\n\t}\n\n\tif (migrationVersion === 0) {\n\t\tmigrationVersion++\n\t\tstorage.exec(`\n\t\t\tCREATE TABLE ${documentsTable} (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\tstate BLOB NOT NULL,\n\t\t\t\tlastChangedClock INTEGER NOT NULL\n\t\t\t);\n\n\t\t\tCREATE INDEX idx_${documentsTable}_lastChangedClock ON ${documentsTable}(lastChangedClock);\n\n\t\t\tCREATE TABLE ${tombstonesTable} (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\tclock INTEGER NOT NULL\n\t\t\t);\n\t\t\tCREATE INDEX idx_${tombstonesTable}_clock ON ${tombstonesTable}(clock);\n\n\t\t\t-- This table is used to store the metadata for the sync storage.\n\t\t\t-- There should only be one row in this table.\n\t\t\tCREATE TABLE ${metadataTable} (\n\t\t\t migrationVersion INTEGER NOT NULL,\n\t\t\t\tdocumentClock INTEGER NOT NULL,\n\t\t\t\ttombstoneHistoryStartsAtClock INTEGER NOT NULL,\n\t\t\t\tschema TEXT NOT NULL\n\t\t\t);\n\t\t\t\n\t\t\tINSERT INTO ${metadataTable} (migrationVersion, documentClock, tombstoneHistoryStartsAtClock, schema) VALUES (2, 0, 0, '')\n\t\t`)\n\t\t// Skip migration 2 since we created the table with BLOB already\n\t\tmigrationVersion++\n\t}\n\n\tif (migrationVersion === 1) {\n\t\t// Migration 2: Convert state column from TEXT to BLOB\n\t\t// SQLite doesn't support ALTER COLUMN, so we need to recreate the table\n\t\tmigrationVersion++\n\t\tstorage.exec(`\n\t\t\tCREATE TABLE ${documentsTable}_new (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\tstate BLOB NOT NULL,\n\t\t\t\tlastChangedClock INTEGER NOT NULL\n\t\t\t);\n\t\t\t\n\t\t\tINSERT INTO ${documentsTable}_new (id, state, lastChangedClock)\n\t\t\tSELECT id, CAST(state AS BLOB), lastChangedClock FROM ${documentsTable};\n\t\t\t\n\t\t\tDROP TABLE ${documentsTable};\n\t\t\t\n\t\t\tALTER TABLE ${documentsTable}_new RENAME TO ${documentsTable};\n\t\t\t\n\t\t\tCREATE INDEX idx_${documentsTable}_lastChangedClock ON ${documentsTable}(lastChangedClock);\n\t\t`)\n\t}\n\n\t// add more migrations here if and when needed\n\n\tstorage.exec(`UPDATE ${metadataTable} SET migrationVersion = ${migrationVersion}`)\n}\n\nconst textEncoder = new TextEncoder()\nconst textDecoder = new TextDecoder()\n\nfunction encodeState(state: unknown): Uint8Array {\n\treturn textEncoder.encode(JSON.stringify(state))\n}\n\nfunction decodeState<T>(state: Uint8Array): T {\n\treturn JSON.parse(textDecoder.decode(state))\n}\n\n/**\n * SQLite-based implementation of TLSyncStorage.\n * Stores documents, tombstones, metadata, and clock values in SQLite tables.\n *\n * This storage backend provides persistent synchronization state that survives\n * process restarts, unlike InMemorySyncStorage which loses data when the process ends.\n *\n * @example\n * ```ts\n * // With Cloudflare Durable Objects\n * import { SQLiteSyncStorage, DurableObjectSqliteSyncWrapper } from '@tldraw/sync-core'\n *\n * const sql = new DurableObjectSqliteSyncWrapper(this.ctx.storage)\n * const storage = new SQLiteSyncStorage({ sql })\n * ```\n *\n * @example\n * ```ts\n * // With Node.js sqlite (Node 22.5+)\n * import { DatabaseSync } from 'node:sqlite'\n * import { SQLiteSyncStorage, NodeSqliteWrapper } from '@tldraw/sync-core'\n *\n * const db = new DatabaseSync('sync-state.db')\n * const sql = new NodeSqliteWrapper(db)\n * const storage = new SQLiteSyncStorage({ sql })\n * ```\n *\n * @example\n * ```ts\n * // Initialize with an existing snapshot\n * const storage = new SQLiteSyncStorage({ sql, snapshot: existingSnapshot })\n * ```\n *\n * @public\n */\nexport class SQLiteSyncStorage<R extends UnknownRecord> implements TLSyncStorage<R> {\n\t/**\n\t * Check if the storage has been initialized (has data in the clock table).\n\t * Useful for determining whether to load from an external source on first access.\n\t */\n\tstatic hasBeenInitialized(storage: TLSyncSqliteWrapper): boolean {\n\t\tconst prefix = storage.config?.tablePrefix ?? ''\n\t\ttry {\n\t\t\tconst schema = storage\n\t\t\t\t.prepare<{ schema: string }>(`SELECT schema FROM ${prefix}metadata LIMIT 1`)\n\t\t\t\t.all()[0]?.schema\n\t\t\treturn !!schema\n\t\t} catch (_e) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Get the current document clock value from storage without fully initializing.\n\t * Returns null if storage has not been initialized.\n\t * Useful for comparing storage freshness against external sources.\n\t */\n\tstatic getDocumentClock(storage: TLSyncSqliteWrapper): number | null {\n\t\tconst prefix = storage.config?.tablePrefix ?? ''\n\t\ttry {\n\t\t\tconst row = storage\n\t\t\t\t.prepare<{ documentClock: number }>(`SELECT documentClock FROM ${prefix}metadata LIMIT 1`)\n\t\t\t\t.all()[0]\n\t\t\t// documentClock exists but could be 0, so we check if the storage is initialized\n\t\t\tif (row && SQLiteSyncStorage.hasBeenInitialized(storage)) {\n\t\t\t\treturn row.documentClock\n\t\t\t}\n\t\t\treturn null\n\t\t} catch (_e) {\n\t\t\treturn null\n\t\t}\n\t}\n\n\t// Prepared statements - created once, reused many times\n\tprivate readonly stmts\n\n\tprivate readonly sql: TLSyncSqliteWrapper\n\n\tconstructor({\n\t\tsql,\n\t\tsnapshot,\n\t\tonChange,\n\t}: {\n\t\tsql: TLSyncSqliteWrapper\n\t\tsnapshot?: RoomSnapshot | StoreSnapshot<R>\n\t\tonChange?(arg: TLSyncStorageOnChangeCallbackProps): unknown\n\t}) {\n\t\tthis.sql = sql\n\t\tconst prefix = sql.config?.tablePrefix ?? ''\n\t\tconst documentsTable = `${prefix}documents`\n\t\tconst tombstonesTable = `${prefix}tombstones`\n\t\tconst metadataTable = `${prefix}metadata`\n\n\t\tmigrateSqliteSyncStorage(this.sql, { documentsTable, tombstonesTable, metadataTable })\n\n\t\t// Prepare all statements once\n\t\tthis.stmts = {\n\t\t\t// Metadata\n\t\t\tgetDocumentClock: this.sql.prepare<{ documentClock: number }>(\n\t\t\t\t`SELECT documentClock FROM ${metadataTable} LIMIT 1`\n\t\t\t),\n\t\t\tgetTombstoneHistoryStartsAtClock: this.sql.prepare<{ tombstoneHistoryStartsAtClock: number }>(\n\t\t\t\t`SELECT tombstoneHistoryStartsAtClock FROM ${metadataTable}`\n\t\t\t),\n\t\t\tgetSchema: this.sql.prepare<{ schema: string }>(`SELECT schema FROM ${metadataTable}`),\n\t\t\tsetSchema: this.sql.prepare<void, [schema: string]>(`UPDATE ${metadataTable} SET schema = ?`),\n\t\t\tsetTombstoneHistoryStartsAtClock: this.sql.prepare<void, [clock: number]>(\n\t\t\t\t`UPDATE ${metadataTable} SET tombstoneHistoryStartsAtClock = ?`\n\t\t\t),\n\t\t\tincrementDocumentClock: this.sql.prepare<void>(\n\t\t\t\t`UPDATE ${metadataTable} SET documentClock = documentClock + 1`\n\t\t\t),\n\n\t\t\t// Documents\n\t\t\tgetDocument: this.sql.prepare<{ state: Uint8Array }, [id: string]>(\n\t\t\t\t`SELECT state FROM ${documentsTable} WHERE id = ?`\n\t\t\t),\n\t\t\tinsertDocument: this.sql.prepare<\n\t\t\t\tvoid,\n\t\t\t\t[id: string, state: Uint8Array, lastChangedClock: number]\n\t\t\t>(`INSERT OR REPLACE INTO ${documentsTable} (id, state, lastChangedClock) VALUES (?, ?, ?)`),\n\t\t\tdeleteDocument: this.sql.prepare<void, [id: string]>(\n\t\t\t\t`DELETE FROM ${documentsTable} WHERE id = ?`\n\t\t\t),\n\t\t\tdocumentExists: this.sql.prepare<{ id: string }, [id: string]>(\n\t\t\t\t`SELECT id FROM ${documentsTable} WHERE id = ?`\n\t\t\t),\n\t\t\titerateDocuments: this.sql.prepare<{ state: Uint8Array; lastChangedClock: number }>(\n\t\t\t\t`SELECT state, lastChangedClock FROM ${documentsTable}`\n\t\t\t),\n\t\t\titerateDocumentEntries: this.sql.prepare<{ id: string; state: Uint8Array }>(\n\t\t\t\t`SELECT id, state FROM ${documentsTable}`\n\t\t\t),\n\t\t\titerateDocumentKeys: this.sql.prepare<{ id: string }>(`SELECT id FROM ${documentsTable}`),\n\t\t\titerateDocumentValues: this.sql.prepare<{ state: Uint8Array }>(\n\t\t\t\t`SELECT state FROM ${documentsTable}`\n\t\t\t),\n\t\t\tgetDocumentsChangedSince: this.sql.prepare<{ state: Uint8Array }, [sinceClock: number]>(\n\t\t\t\t`SELECT state FROM ${documentsTable} WHERE lastChangedClock > ?`\n\t\t\t),\n\n\t\t\t// Tombstones\n\t\t\tinsertTombstone: this.sql.prepare<void, [id: string, clock: number]>(\n\t\t\t\t`INSERT OR REPLACE INTO ${tombstonesTable} (id, clock) VALUES (?, ?)`\n\t\t\t),\n\t\t\tdeleteTombstone: this.sql.prepare<void, [id: string]>(\n\t\t\t\t`DELETE FROM ${tombstonesTable} WHERE id = ?`\n\t\t\t),\n\t\t\tdeleteTombstonesBefore: this.sql.prepare<void, [clock: number]>(\n\t\t\t\t`DELETE FROM ${tombstonesTable} WHERE clock < ?`\n\t\t\t),\n\t\t\tcountTombstones: this.sql.prepare<{ count: number }>(\n\t\t\t\t`SELECT count(*) as count FROM ${tombstonesTable}`\n\t\t\t),\n\t\t\titerateTombstones: this.sql.prepare<{ id: string; clock: number }>(\n\t\t\t\t`SELECT id, clock FROM ${tombstonesTable} ORDER BY clock ASC`\n\t\t\t),\n\t\t\tgetTombstonesChangedSince: this.sql.prepare<{ id: string }, [sinceClock: number]>(\n\t\t\t\t`SELECT id FROM ${tombstonesTable} WHERE clock > ?`\n\t\t\t),\n\n\t\t\t// Initial setup (only used when loading a snapshot)\n\t\t\tupdateMetadata: this.sql.prepare<\n\t\t\t\tvoid,\n\t\t\t\t[documentClock: number, tombstoneHistoryStartsAtClock: number, schema: string]\n\t\t\t>(\n\t\t\t\t`UPDATE ${metadataTable} SET documentClock = ?, tombstoneHistoryStartsAtClock = ?, schema = ?`\n\t\t\t),\n\t\t}\n\n\t\t// Check if we already have data\n\t\tconst hasData = SQLiteSyncStorage.hasBeenInitialized(sql)\n\n\t\tif (snapshot || !hasData) {\n\t\t\tsnapshot = convertStoreSnapshotToRoomSnapshot(snapshot ?? DEFAULT_INITIAL_SNAPSHOT)\n\n\t\t\tconst documentClock = snapshot.documentClock ?? snapshot.clock ?? 0\n\t\t\tconst tombstoneHistoryStartsAtClock = snapshot.tombstoneHistoryStartsAtClock ?? documentClock\n\n\t\t\t// Clear existing data\n\t\t\tthis.sql.exec(`\n\t\t\t\tDELETE FROM ${documentsTable};\n\t\t\t\tDELETE FROM ${tombstonesTable};\n\t\t\t`)\n\n\t\t\t// Insert documents\n\t\t\tfor (const doc of snapshot.documents) {\n\t\t\t\tthis.stmts.insertDocument.run(doc.state.id, encodeState(doc.state), doc.lastChangedClock)\n\t\t\t}\n\n\t\t\t// Insert tombstones\n\t\t\tif (snapshot.tombstones) {\n\t\t\t\tfor (const [id, clock] of objectMapEntries(snapshot.tombstones)) {\n\t\t\t\t\tthis.stmts.insertTombstone.run(id, clock)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Insert metadata row\n\t\t\tthis.stmts.updateMetadata.run(\n\t\t\t\tdocumentClock,\n\t\t\t\ttombstoneHistoryStartsAtClock,\n\t\t\t\tJSON.stringify(snapshot.schema)\n\t\t\t)\n\t\t}\n\t\tif (onChange) {\n\t\t\tthis.onChange(onChange)\n\t\t}\n\t}\n\n\tprivate notifier = new MicrotaskNotifier<[TLSyncStorageOnChangeCallbackProps]>()\n\tonChange(callback: (arg: TLSyncStorageOnChangeCallbackProps) => void): () => void {\n\t\treturn this.notifier.register(callback)\n\t}\n\n\ttransaction<T>(\n\t\tcallback: TLSyncStorageTransactionCallback<R, T>,\n\t\topts?: TLSyncStorageTransactionOptions\n\t): TLSyncStorageTransactionResult<T, R> {\n\t\tconst clockBefore = this.getClock()\n\t\tconst trackChanges = opts?.emitChanges === 'always'\n\t\treturn this.sql.transaction(() => {\n\t\t\tconst txn = new SQLiteSyncStorageTransaction<R>(this, this.stmts)\n\t\t\tlet result: T\n\t\t\tlet changes: TLSyncForwardDiff<R> | undefined\n\t\t\ttry {\n\t\t\t\tresult = transaction(() => {\n\t\t\t\t\treturn callback(txn)\n\t\t\t\t}) as T\n\t\t\t\tif (trackChanges) {\n\t\t\t\t\tchanges = txn.getChangesSince(clockBefore)?.diff\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\ttxn.close()\n\t\t\t}\n\t\t\tif (\n\t\t\t\ttypeof result === 'object' &&\n\t\t\t\tresult &&\n\t\t\t\t'then' in result &&\n\t\t\t\ttypeof result.then === 'function'\n\t\t\t) {\n\t\t\t\tthrow new Error('Transaction must return a value, not a promise')\n\t\t\t}\n\n\t\t\tconst clockAfter = this.getClock()\n\t\t\tconst didChange = clockAfter > clockBefore\n\t\t\tif (didChange) {\n\t\t\t\tthis.notifier.notify({ id: opts?.id, documentClock: clockAfter })\n\t\t\t}\n\t\t\treturn { documentClock: clockAfter, didChange: clockAfter > clockBefore, result, changes }\n\t\t})\n\t}\n\n\tgetClock(): number {\n\t\tconst clockRow = this.stmts.getDocumentClock.all()[0]\n\t\treturn clockRow?.documentClock ?? 0\n\t}\n\n\t/** @internal */\n\t_getTombstoneHistoryStartsAtClock(): number {\n\t\tconst clockRow = this.stmts.getTombstoneHistoryStartsAtClock.all()[0]\n\t\treturn clockRow?.tombstoneHistoryStartsAtClock ?? 0\n\t}\n\n\t/** @internal */\n\t_getSchema(): SerializedSchema {\n\t\tconst clockRow = this.stmts.getSchema.all()[0]\n\t\tassert(clockRow, 'Storage not initialized - clock row missing')\n\t\treturn JSON.parse(clockRow.schema)\n\t}\n\n\t/** @internal */\n\t_setSchema(schema: SerializedSchema): void {\n\t\tthis.stmts.setSchema.run(JSON.stringify(schema))\n\t}\n\n\t/** @internal */\n\tpruneTombstones = throttle(\n\t\t() => {\n\t\t\tconst tombstoneCount = this.stmts.countTombstones.all()[0].count as number\n\t\t\tif (tombstoneCount > MAX_TOMBSTONES) {\n\t\t\t\t// Get all tombstones sorted by clock ascending (oldest first)\n\t\t\t\tconst tombstones = this.stmts.iterateTombstones.all()\n\n\t\t\t\tconst result = computeTombstonePruning({ tombstones, documentClock: this.getClock() })\n\t\t\t\tif (result) {\n\t\t\t\t\tthis.stmts.setTombstoneHistoryStartsAtClock.run(result.newTombstoneHistoryStartsAtClock)\n\t\t\t\t\t// Delete all tombstones with clock < newTombstoneHistoryStartsAtClock in one operation.\n\t\t\t\t\t// This works because computeTombstonePruning ensures we never split a clock value.\n\t\t\t\t\tthis.stmts.deleteTombstonesBefore.run(result.newTombstoneHistoryStartsAtClock)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t1000,\n\t\t// prevent this from running synchronously to avoid blocking requests\n\t\t{ leading: false }\n\t)\n\n\tgetSnapshot(): RoomSnapshot {\n\t\treturn {\n\t\t\ttombstoneHistoryStartsAtClock: this._getTombstoneHistoryStartsAtClock(),\n\t\t\tdocumentClock: this.getClock(),\n\t\t\tdocuments: Array.from(this._iterateDocuments()),\n\t\t\ttombstones: Object.fromEntries(this._iterateTombstones()),\n\t\t\tschema: this._getSchema(),\n\t\t}\n\t}\n\tprivate *_iterateDocuments(): IterableIterator<{ state: R; lastChangedClock: number }> {\n\t\tfor (const row of this.stmts.iterateDocuments.iterate()) {\n\t\t\tyield { state: decodeState<R>(row.state), lastChangedClock: row.lastChangedClock }\n\t\t}\n\t}\n\n\tprivate *_iterateTombstones(): IterableIterator<[string, number]> {\n\t\tfor (const row of this.stmts.iterateTombstones.iterate()) {\n\t\t\tyield [row.id, row.clock]\n\t\t}\n\t}\n}\n\n/**\n * Transaction implementation for SQLiteSyncStorage.\n * Provides access to documents, tombstones, and metadata within a transaction.\n *\n * @internal\n */\nclass SQLiteSyncStorageTransaction<R extends UnknownRecord> implements TLSyncStorageTransaction<R> {\n\tprivate _clock: number\n\tprivate _closed = false\n\tprivate _didIncrementClock: boolean = false\n\n\tconstructor(\n\t\tprivate storage: SQLiteSyncStorage<R>,\n\t\tprivate stmts: SQLiteSyncStorage<R>['stmts']\n\t) {\n\t\tthis._clock = this.storage.getClock()\n\t}\n\n\t/** @internal */\n\tclose() {\n\t\tthis._closed = true\n\t}\n\n\tprivate assertNotClosed() {\n\t\tassert(!this._closed, 'Transaction has ended, iterator cannot be consumed')\n\t}\n\n\tgetClock(): number {\n\t\treturn this._clock\n\t}\n\n\tprivate getNextClock(): number {\n\t\tif (!this._didIncrementClock) {\n\t\t\tthis._didIncrementClock = true\n\t\t\tthis.stmts.incrementDocumentClock.run()\n\t\t\tthis._clock = this.storage.getClock()\n\t\t}\n\t\treturn this._clock\n\t}\n\n\tget(id: string): R | undefined {\n\t\tthis.assertNotClosed()\n\t\tconst row = this.stmts.getDocument.all(id)[0]\n\t\tif (!row) return undefined\n\t\treturn decodeState<R>(row.state)\n\t}\n\n\tset(id: string, record: R): void {\n\t\tthis.assertNotClosed()\n\t\tassert(id === record.id, `Record id mismatch: key does not match record.id`)\n\t\tconst clock = this.getNextClock()\n\t\t// Automatically clear tombstone if it exists\n\t\tthis.stmts.deleteTombstone.run(id)\n\t\tthis.stmts.insertDocument.run(id, encodeState(record), clock)\n\t}\n\n\tdelete(id: string): void {\n\t\tthis.assertNotClosed()\n\t\t// Only create a tombstone if the record actually exists\n\t\tconst exists = this.stmts.documentExists.all(id)[0]\n\t\tif (!exists) return\n\t\tconst clock = this.getNextClock()\n\t\tthis.stmts.deleteDocument.run(id)\n\t\tthis.stmts.insertTombstone.run(id, clock)\n\t\tthis.storage.pruneTombstones()\n\t}\n\n\t*entries(): IterableIterator<[string, R]> {\n\t\tthis.assertNotClosed()\n\t\tfor (const row of this.stmts.iterateDocumentEntries.iterate()) {\n\t\t\tthis.assertNotClosed()\n\t\t\tyield [row.id, decodeState<R>(row.state)]\n\t\t}\n\t}\n\n\t*keys(): IterableIterator<string> {\n\t\tthis.assertNotClosed()\n\t\tfor (const row of this.stmts.iterateDocumentKeys.iterate()) {\n\t\t\tthis.assertNotClosed()\n\t\t\tyield row.id\n\t\t}\n\t}\n\n\t*values(): IterableIterator<R> {\n\t\tthis.assertNotClosed()\n\t\tfor (const row of this.stmts.iterateDocumentValues.iterate()) {\n\t\t\tthis.assertNotClosed()\n\t\t\tyield decodeState<R>(row.state)\n\t\t}\n\t}\n\n\tgetSchema(): SerializedSchema {\n\t\tthis.assertNotClosed()\n\t\treturn this.storage._getSchema()\n\t}\n\n\tsetSchema(schema: SerializedSchema): void {\n\t\tthis.assertNotClosed()\n\t\tthis.storage._setSchema(schema)\n\t}\n\n\tgetChangesSince(sinceClock: number): TLSyncStorageGetChangesSinceResult<R> | undefined {\n\t\tthis.assertNotClosed()\n\t\tconst clock = this.storage.getClock()\n\t\tif (sinceClock === clock) return undefined\n\t\tif (sinceClock > clock) {\n\t\t\t// something went wrong, wipe the slate clean\n\t\t\tsinceClock = -1\n\t\t}\n\t\tconst diff: TLSyncForwardDiff<R> = { puts: {}, deletes: [] }\n\t\tconst wipeAll = sinceClock < this.storage._getTombstoneHistoryStartsAtClock()\n\n\t\tif (wipeAll) {\n\t\t\t// If wipeAll, include all documents\n\t\t\tfor (const row of this.stmts.iterateDocumentValues.iterate()) {\n\t\t\t\tconst state = decodeState<R>(row.state)\n\t\t\t\tdiff.puts[state.id] = state\n\t\t\t}\n\t\t} else {\n\t\t\t// Get documents changed since clock\n\t\t\tfor (const row of this.stmts.getDocumentsChangedSince.iterate(sinceClock)) {\n\t\t\t\tconst state = decodeState<R>(row.state)\n\t\t\t\tdiff.puts[state.id] = state\n\t\t\t}\n\t\t}\n\n\t\t// Get tombstones changed since clock\n\t\tfor (const row of this.stmts.getTombstonesChangedSince.iterate(sinceClock)) {\n\t\t\tdiff.deletes.push(row.id)\n\t\t}\n\n\t\treturn { diff, wipeAll }\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|