@tldraw/sync-core 4.2.2 → 4.2.3
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 +58 -483
- package/dist-cjs/index.js +3 -13
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/RoomSession.js.map +1 -1
- package/dist-cjs/lib/TLSocketRoom.js +69 -117
- package/dist-cjs/lib/TLSocketRoom.js.map +2 -2
- package/dist-cjs/lib/TLSyncClient.js +0 -7
- package/dist-cjs/lib/TLSyncClient.js.map +2 -2
- package/dist-cjs/lib/TLSyncRoom.js +688 -357
- package/dist-cjs/lib/TLSyncRoom.js.map +3 -3
- package/dist-cjs/lib/chunk.js +2 -2
- package/dist-cjs/lib/chunk.js.map +1 -1
- package/dist-esm/index.d.mts +58 -483
- package/dist-esm/index.mjs +5 -20
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/RoomSession.mjs.map +1 -1
- package/dist-esm/lib/TLSocketRoom.mjs +70 -121
- package/dist-esm/lib/TLSocketRoom.mjs.map +2 -2
- package/dist-esm/lib/TLSyncClient.mjs +0 -7
- package/dist-esm/lib/TLSyncClient.mjs.map +2 -2
- package/dist-esm/lib/TLSyncRoom.mjs +702 -370
- package/dist-esm/lib/TLSyncRoom.mjs.map +3 -3
- package/dist-esm/lib/chunk.mjs +2 -2
- package/dist-esm/lib/chunk.mjs.map +1 -1
- package/package.json +11 -12
- package/src/index.ts +3 -32
- package/src/lib/ClientWebSocketAdapter.test.ts +0 -3
- package/src/lib/RoomSession.test.ts +0 -1
- package/src/lib/RoomSession.ts +0 -2
- package/src/lib/TLSocketRoom.ts +114 -228
- package/src/lib/TLSyncClient.ts +0 -12
- package/src/lib/TLSyncRoom.ts +913 -473
- package/src/lib/chunk.ts +2 -2
- package/src/test/FuzzEditor.ts +5 -4
- package/src/test/TLSocketRoom.test.ts +49 -255
- package/src/test/TLSyncRoom.test.ts +534 -1024
- package/src/test/TestServer.ts +1 -12
- package/src/test/customMessages.test.ts +1 -1
- package/src/test/presenceMode.test.ts +6 -6
- package/src/test/pruneTombstones.test.ts +178 -0
- package/src/test/syncFuzz.test.ts +4 -2
- package/src/test/upgradeDowngrade.test.ts +8 -290
- package/src/test/validation.test.ts +10 -15
- package/dist-cjs/lib/DurableObjectSqliteSyncWrapper.js +0 -55
- package/dist-cjs/lib/DurableObjectSqliteSyncWrapper.js.map +0 -7
- package/dist-cjs/lib/InMemorySyncStorage.js +0 -287
- package/dist-cjs/lib/InMemorySyncStorage.js.map +0 -7
- package/dist-cjs/lib/MicrotaskNotifier.js +0 -50
- package/dist-cjs/lib/MicrotaskNotifier.js.map +0 -7
- package/dist-cjs/lib/NodeSqliteWrapper.js +0 -48
- package/dist-cjs/lib/NodeSqliteWrapper.js.map +0 -7
- package/dist-cjs/lib/SQLiteSyncStorage.js +0 -428
- package/dist-cjs/lib/SQLiteSyncStorage.js.map +0 -7
- package/dist-cjs/lib/TLSyncStorage.js +0 -76
- package/dist-cjs/lib/TLSyncStorage.js.map +0 -7
- package/dist-cjs/lib/recordDiff.js +0 -52
- package/dist-cjs/lib/recordDiff.js.map +0 -7
- package/dist-esm/lib/DurableObjectSqliteSyncWrapper.mjs +0 -35
- package/dist-esm/lib/DurableObjectSqliteSyncWrapper.mjs.map +0 -7
- package/dist-esm/lib/InMemorySyncStorage.mjs +0 -272
- package/dist-esm/lib/InMemorySyncStorage.mjs.map +0 -7
- package/dist-esm/lib/MicrotaskNotifier.mjs +0 -30
- package/dist-esm/lib/MicrotaskNotifier.mjs.map +0 -7
- package/dist-esm/lib/NodeSqliteWrapper.mjs +0 -28
- package/dist-esm/lib/NodeSqliteWrapper.mjs.map +0 -7
- package/dist-esm/lib/SQLiteSyncStorage.mjs +0 -414
- package/dist-esm/lib/SQLiteSyncStorage.mjs.map +0 -7
- package/dist-esm/lib/TLSyncStorage.mjs +0 -56
- package/dist-esm/lib/TLSyncStorage.mjs.map +0 -7
- package/dist-esm/lib/recordDiff.mjs +0 -32
- package/dist-esm/lib/recordDiff.mjs.map +0 -7
- package/src/lib/DurableObjectSqliteSyncWrapper.ts +0 -95
- package/src/lib/InMemorySyncStorage.ts +0 -387
- package/src/lib/MicrotaskNotifier.test.ts +0 -429
- package/src/lib/MicrotaskNotifier.ts +0 -38
- package/src/lib/NodeSqliteSyncWrapper.integration.test.ts +0 -270
- package/src/lib/NodeSqliteSyncWrapper.test.ts +0 -272
- package/src/lib/NodeSqliteWrapper.ts +0 -99
- package/src/lib/SQLiteSyncStorage.ts +0 -627
- package/src/lib/TLSyncStorage.ts +0 -216
- package/src/lib/computeTombstonePruning.test.ts +0 -352
- package/src/lib/recordDiff.ts +0 -73
- package/src/test/InMemorySyncStorage.test.ts +0 -1684
- package/src/test/SQLiteSyncStorage.test.ts +0 -1378
|
@@ -50,12 +50,7 @@ const schemaWithoutValidator = StoreSchema.create<Book | Presence>({
|
|
|
50
50
|
})
|
|
51
51
|
|
|
52
52
|
const disposables: Array<() => void> = []
|
|
53
|
-
let consoleSpy: ReturnType<typeof vi.spyOn>
|
|
54
|
-
beforeEach(() => {
|
|
55
|
-
consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
56
|
-
})
|
|
57
53
|
afterEach(() => {
|
|
58
|
-
consoleSpy.mockRestore()
|
|
59
54
|
for (const dispose of disposables) {
|
|
60
55
|
dispose()
|
|
61
56
|
}
|
|
@@ -100,7 +95,7 @@ async function makeTestInstance() {
|
|
|
100
95
|
it('rejects invalid put operations that create a new document', async () => {
|
|
101
96
|
const { client, flush, onSyncError, server } = await makeTestInstance()
|
|
102
97
|
|
|
103
|
-
const prevServerDocs = server.
|
|
98
|
+
const prevServerDocs = server.room.getSnapshot().documents
|
|
104
99
|
|
|
105
100
|
client.store.put([
|
|
106
101
|
{
|
|
@@ -114,20 +109,20 @@ it('rejects invalid put operations that create a new document', async () => {
|
|
|
114
109
|
|
|
115
110
|
expect(onSyncError).toHaveBeenCalledTimes(1)
|
|
116
111
|
expect(onSyncError).toHaveBeenLastCalledWith(TLSyncErrorCloseEventReason.INVALID_RECORD)
|
|
117
|
-
expect(server.
|
|
112
|
+
expect(server.room.getSnapshot().documents).toStrictEqual(prevServerDocs)
|
|
118
113
|
})
|
|
119
114
|
|
|
120
115
|
it('rejects invalid put operations that replace an existing document', async () => {
|
|
121
116
|
const { client, flush, onSyncError, server } = await makeTestInstance()
|
|
122
117
|
|
|
123
|
-
let prevServerDocs = server.
|
|
118
|
+
let prevServerDocs = server.room.getSnapshot().documents
|
|
124
119
|
const book: Book = { typeName: 'book', id: Book.createId('1'), title: 'Annihilation' }
|
|
125
120
|
client.store.put([book])
|
|
126
121
|
await flush()
|
|
127
122
|
|
|
128
123
|
expect(onSyncError).toHaveBeenCalledTimes(0)
|
|
129
|
-
expect(server.
|
|
130
|
-
prevServerDocs = server.
|
|
124
|
+
expect(server.room.getSnapshot().documents).not.toStrictEqual(prevServerDocs)
|
|
125
|
+
prevServerDocs = server.room.getSnapshot().documents
|
|
131
126
|
|
|
132
127
|
client.socket.sendMessage({
|
|
133
128
|
type: 'push',
|
|
@@ -148,13 +143,13 @@ it('rejects invalid put operations that replace an existing document', async ()
|
|
|
148
143
|
|
|
149
144
|
expect(onSyncError).toHaveBeenCalledTimes(1)
|
|
150
145
|
expect(onSyncError).toHaveBeenLastCalledWith(TLSyncErrorCloseEventReason.INVALID_RECORD)
|
|
151
|
-
expect(server.
|
|
146
|
+
expect(server.room.getSnapshot().documents).toStrictEqual(prevServerDocs)
|
|
152
147
|
})
|
|
153
148
|
|
|
154
149
|
it('rejects invalid update operations', async () => {
|
|
155
150
|
const { client, flush, onSyncError, server } = await makeTestInstance()
|
|
156
151
|
|
|
157
|
-
let prevServerDocs = server.
|
|
152
|
+
let prevServerDocs = server.room.getSnapshot().documents
|
|
158
153
|
|
|
159
154
|
// create the book
|
|
160
155
|
client.store.put([
|
|
@@ -167,8 +162,8 @@ it('rejects invalid update operations', async () => {
|
|
|
167
162
|
await flush()
|
|
168
163
|
|
|
169
164
|
expect(onSyncError).toHaveBeenCalledTimes(0)
|
|
170
|
-
expect(server.
|
|
171
|
-
prevServerDocs = server.
|
|
165
|
+
expect(server.room.getSnapshot().documents).not.toStrictEqual(prevServerDocs)
|
|
166
|
+
prevServerDocs = server.room.getSnapshot().documents
|
|
172
167
|
|
|
173
168
|
// update the title to be wrong
|
|
174
169
|
client.store.put([
|
|
@@ -182,5 +177,5 @@ it('rejects invalid update operations', async () => {
|
|
|
182
177
|
await flush()
|
|
183
178
|
expect(onSyncError).toHaveBeenCalledTimes(1)
|
|
184
179
|
expect(onSyncError).toHaveBeenLastCalledWith(TLSyncErrorCloseEventReason.INVALID_RECORD)
|
|
185
|
-
expect(server.
|
|
180
|
+
expect(server.room.getSnapshot().documents).toStrictEqual(prevServerDocs)
|
|
186
181
|
})
|
|
@@ -1,55 +0,0 @@
|
|
|
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 DurableObjectSqliteSyncWrapper_exports = {};
|
|
20
|
-
__export(DurableObjectSqliteSyncWrapper_exports, {
|
|
21
|
-
DurableObjectSqliteSyncWrapper: () => DurableObjectSqliteSyncWrapper
|
|
22
|
-
});
|
|
23
|
-
module.exports = __toCommonJS(DurableObjectSqliteSyncWrapper_exports);
|
|
24
|
-
class DurableObjectStatement {
|
|
25
|
-
constructor(sql, query) {
|
|
26
|
-
this.sql = sql;
|
|
27
|
-
this.query = query;
|
|
28
|
-
}
|
|
29
|
-
iterate(...bindings) {
|
|
30
|
-
const result = this.sql.exec(this.query, ...bindings);
|
|
31
|
-
return result[Symbol.iterator]();
|
|
32
|
-
}
|
|
33
|
-
all(...bindings) {
|
|
34
|
-
return this.sql.exec(this.query, ...bindings).toArray();
|
|
35
|
-
}
|
|
36
|
-
run(...bindings) {
|
|
37
|
-
this.sql.exec(this.query, ...bindings);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
class DurableObjectSqliteSyncWrapper {
|
|
41
|
-
constructor(storage, config) {
|
|
42
|
-
this.storage = storage;
|
|
43
|
-
this.config = config;
|
|
44
|
-
}
|
|
45
|
-
exec(sql) {
|
|
46
|
-
this.storage.sql.exec(sql);
|
|
47
|
-
}
|
|
48
|
-
prepare(sql) {
|
|
49
|
-
return new DurableObjectStatement(this.storage.sql, sql);
|
|
50
|
-
}
|
|
51
|
-
transaction(callback) {
|
|
52
|
-
return this.storage.transactionSync(callback);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
//# sourceMappingURL=DurableObjectSqliteSyncWrapper.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/lib/DurableObjectSqliteSyncWrapper.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n\ttype TLSqliteInputValue,\n\ttype TLSqliteRow,\n\ttype TLSyncSqliteStatement,\n\ttype TLSyncSqliteWrapper,\n\ttype TLSyncSqliteWrapperConfig,\n} from './SQLiteSyncStorage'\n\n/**\n * Mimics a prepared statement interface for Durable Objects SQLite.\n * Rather than actually preparing the statement, it just stores the SQL and\n * executes it fresh each time. This is still fast because DO SQLite maintains\n * an internal LRU cache of prepared statements.\n */\nclass DurableObjectStatement<\n\tTResult extends TLSqliteRow | void,\n\tTParams extends TLSqliteInputValue[],\n> implements TLSyncSqliteStatement<TResult, TParams>\n{\n\tconstructor(\n\t\tprivate sql: {\n\t\t\texec(sql: string, ...bindings: unknown[]): Iterable<any> & { toArray(): any[] }\n\t\t},\n\t\tprivate query: string\n\t) {}\n\n\titerate(...bindings: TParams): IterableIterator<TResult> {\n\t\tconst result = this.sql.exec(this.query, ...bindings)\n\t\treturn result[Symbol.iterator]() as IterableIterator<TResult>\n\t}\n\n\tall(...bindings: TParams): TResult[] {\n\t\treturn this.sql.exec(this.query, ...bindings).toArray()\n\t}\n\n\trun(...bindings: TParams): void {\n\t\tthis.sql.exec(this.query, ...bindings)\n\t}\n}\n\n/**\n * A wrapper around Cloudflare Durable Object's SqlStorage that implements TLSyncSqliteWrapper.\n *\n * Use this wrapper with SQLiteSyncStorage to persist tldraw sync state using\n * Cloudflare Durable Object's built-in SQLite storage. This provides automatic\n * persistence that survives Durable Object hibernation and restarts.\n *\n * @example\n * ```ts\n * import { SQLiteSyncStorage, DurableObjectSqliteSyncWrapper } from '@tldraw/sync-core'\n *\n * // In your Durable Object class:\n * class MyDurableObject extends DurableObject {\n * private storage: SQLiteSyncStorage\n *\n * constructor(ctx: DurableObjectState, env: Env) {\n * super(ctx, env)\n * const sql = new DurableObjectSqliteSyncWrapper(ctx.storage)\n * this.storage = new SQLiteSyncStorage({ sql })\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * // With table prefix to avoid conflicts with other tables\n * const sql = new DurableObjectSqliteSyncWrapper(this.ctx.storage, { tablePrefix: 'tldraw_' })\n * // Creates tables: tldraw_documents, tldraw_tombstones, tldraw_metadata\n * ```\n *\n * @public\n */\nexport class DurableObjectSqliteSyncWrapper implements TLSyncSqliteWrapper {\n\tconstructor(\n\t\tprivate storage: {\n\t\t\tsql: { exec(sql: string, ...bindings: unknown[]): Iterable<any> & { toArray(): any[] } }\n\t\t\ttransactionSync(callback: () => any): any\n\t\t},\n\t\tpublic config?: TLSyncSqliteWrapperConfig\n\t) {}\n\n\texec(sql: string): void {\n\t\tthis.storage.sql.exec(sql)\n\t}\n\n\tprepare<TResult extends TLSqliteRow | void = void, TParams extends TLSqliteInputValue[] = []>(\n\t\tsql: string\n\t): TLSyncSqliteStatement<TResult, TParams> {\n\t\treturn new DurableObjectStatement<TResult, TParams>(this.storage.sql, sql)\n\t}\n\n\ttransaction<T>(callback: () => T): T {\n\t\treturn this.storage.transactionSync(callback)\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,MAAM,uBAIN;AAAA,EACC,YACS,KAGA,OACP;AAJO;AAGA;AAAA,EACN;AAAA,EAEH,WAAW,UAA8C;AACxD,UAAM,SAAS,KAAK,IAAI,KAAK,KAAK,OAAO,GAAG,QAAQ;AACpD,WAAO,OAAO,OAAO,QAAQ,EAAE;AAAA,EAChC;AAAA,EAEA,OAAO,UAA8B;AACpC,WAAO,KAAK,IAAI,KAAK,KAAK,OAAO,GAAG,QAAQ,EAAE,QAAQ;AAAA,EACvD;AAAA,EAEA,OAAO,UAAyB;AAC/B,SAAK,IAAI,KAAK,KAAK,OAAO,GAAG,QAAQ;AAAA,EACtC;AACD;AAkCO,MAAM,+BAA8D;AAAA,EAC1E,YACS,SAID,QACN;AALO;AAID;AAAA,EACL;AAAA,EAEH,KAAK,KAAmB;AACvB,SAAK,QAAQ,IAAI,KAAK,GAAG;AAAA,EAC1B;AAAA,EAEA,QACC,KAC0C;AAC1C,WAAO,IAAI,uBAAyC,KAAK,QAAQ,KAAK,GAAG;AAAA,EAC1E;AAAA,EAEA,YAAe,UAAsB;AACpC,WAAO,KAAK,QAAQ,gBAAgB,QAAQ;AAAA,EAC7C;AACD;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,287 +0,0 @@
|
|
|
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 InMemorySyncStorage_exports = {};
|
|
20
|
-
__export(InMemorySyncStorage_exports, {
|
|
21
|
-
DEFAULT_INITIAL_SNAPSHOT: () => DEFAULT_INITIAL_SNAPSHOT,
|
|
22
|
-
InMemorySyncStorage: () => InMemorySyncStorage,
|
|
23
|
-
MAX_TOMBSTONES: () => MAX_TOMBSTONES,
|
|
24
|
-
TOMBSTONE_PRUNE_BUFFER_SIZE: () => TOMBSTONE_PRUNE_BUFFER_SIZE,
|
|
25
|
-
computeTombstonePruning: () => computeTombstonePruning
|
|
26
|
-
});
|
|
27
|
-
module.exports = __toCommonJS(InMemorySyncStorage_exports);
|
|
28
|
-
var import_state = require("@tldraw/state");
|
|
29
|
-
var import_store = require("@tldraw/store");
|
|
30
|
-
var import_tlschema = require("@tldraw/tlschema");
|
|
31
|
-
var import_utils = require("@tldraw/utils");
|
|
32
|
-
var import_MicrotaskNotifier = require("./MicrotaskNotifier");
|
|
33
|
-
const TOMBSTONE_PRUNE_BUFFER_SIZE = 1e3;
|
|
34
|
-
const MAX_TOMBSTONES = 5e3;
|
|
35
|
-
function computeTombstonePruning({
|
|
36
|
-
tombstones,
|
|
37
|
-
documentClock,
|
|
38
|
-
maxTombstones = MAX_TOMBSTONES,
|
|
39
|
-
pruneBufferSize = TOMBSTONE_PRUNE_BUFFER_SIZE
|
|
40
|
-
}) {
|
|
41
|
-
if (tombstones.length <= maxTombstones) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
let cutoff = pruneBufferSize + tombstones.length - maxTombstones;
|
|
45
|
-
while (cutoff < tombstones.length && tombstones[cutoff - 1]?.clock === tombstones[cutoff]?.clock) {
|
|
46
|
-
cutoff++;
|
|
47
|
-
}
|
|
48
|
-
const oldestRemaining = tombstones[cutoff];
|
|
49
|
-
const newTombstoneHistoryStartsAtClock = oldestRemaining?.clock ?? documentClock;
|
|
50
|
-
const idsToDelete = tombstones.slice(0, cutoff).map((t) => t.id);
|
|
51
|
-
return { newTombstoneHistoryStartsAtClock, idsToDelete };
|
|
52
|
-
}
|
|
53
|
-
const DEFAULT_INITIAL_SNAPSHOT = {
|
|
54
|
-
documentClock: 0,
|
|
55
|
-
tombstoneHistoryStartsAtClock: 0,
|
|
56
|
-
schema: (0, import_tlschema.createTLSchema)().serialize(),
|
|
57
|
-
documents: [
|
|
58
|
-
{
|
|
59
|
-
state: import_tlschema.DocumentRecordType.create({ id: import_tlschema.TLDOCUMENT_ID }),
|
|
60
|
-
lastChangedClock: 0
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
state: import_tlschema.PageRecordType.create({
|
|
64
|
-
id: "page:page",
|
|
65
|
-
name: "Page 1",
|
|
66
|
-
index: "a1"
|
|
67
|
-
}),
|
|
68
|
-
lastChangedClock: 0
|
|
69
|
-
}
|
|
70
|
-
]
|
|
71
|
-
};
|
|
72
|
-
class InMemorySyncStorage {
|
|
73
|
-
/** @internal */
|
|
74
|
-
documents;
|
|
75
|
-
/** @internal */
|
|
76
|
-
tombstones;
|
|
77
|
-
/** @internal */
|
|
78
|
-
schema;
|
|
79
|
-
/** @internal */
|
|
80
|
-
documentClock;
|
|
81
|
-
/** @internal */
|
|
82
|
-
tombstoneHistoryStartsAtClock;
|
|
83
|
-
notifier = new import_MicrotaskNotifier.MicrotaskNotifier();
|
|
84
|
-
onChange(callback) {
|
|
85
|
-
return this.notifier.register(callback);
|
|
86
|
-
}
|
|
87
|
-
constructor({
|
|
88
|
-
snapshot = DEFAULT_INITIAL_SNAPSHOT,
|
|
89
|
-
onChange
|
|
90
|
-
} = {}) {
|
|
91
|
-
const maxClockValue = Math.max(
|
|
92
|
-
0,
|
|
93
|
-
...Object.values(snapshot.tombstones ?? {}),
|
|
94
|
-
...Object.values(snapshot.documents.map((d) => d.lastChangedClock))
|
|
95
|
-
);
|
|
96
|
-
this.documents = new import_store.AtomMap(
|
|
97
|
-
"room documents",
|
|
98
|
-
snapshot.documents.map((d) => [
|
|
99
|
-
d.state.id,
|
|
100
|
-
{ state: (0, import_store.devFreeze)(d.state), lastChangedClock: d.lastChangedClock }
|
|
101
|
-
])
|
|
102
|
-
);
|
|
103
|
-
const documentClock = Math.max(maxClockValue, snapshot.documentClock ?? snapshot.clock ?? 0);
|
|
104
|
-
this.documentClock = (0, import_state.atom)("document clock", documentClock);
|
|
105
|
-
const tombstoneHistoryStartsAtClock = Math.min(
|
|
106
|
-
snapshot.tombstoneHistoryStartsAtClock ?? documentClock,
|
|
107
|
-
documentClock
|
|
108
|
-
);
|
|
109
|
-
this.tombstoneHistoryStartsAtClock = (0, import_state.atom)(
|
|
110
|
-
"tombstone history starts at clock",
|
|
111
|
-
tombstoneHistoryStartsAtClock
|
|
112
|
-
);
|
|
113
|
-
this.schema = (0, import_state.atom)("schema", snapshot.schema ?? (0, import_tlschema.createTLSchema)().serializeEarliestVersion());
|
|
114
|
-
this.tombstones = new import_store.AtomMap(
|
|
115
|
-
"room tombstones",
|
|
116
|
-
// If the tombstone history starts now (or we didn't have the
|
|
117
|
-
// tombstoneHistoryStartsAtClock) then there are no tombstones
|
|
118
|
-
tombstoneHistoryStartsAtClock === documentClock ? [] : (0, import_utils.objectMapEntries)(snapshot.tombstones ?? {})
|
|
119
|
-
);
|
|
120
|
-
if (onChange) {
|
|
121
|
-
this.onChange(onChange);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
transaction(callback, opts) {
|
|
125
|
-
const clockBefore = this.documentClock.get();
|
|
126
|
-
const trackChanges = opts?.emitChanges === "always";
|
|
127
|
-
const txn = new InMemorySyncStorageTransaction(this);
|
|
128
|
-
let result;
|
|
129
|
-
let changes;
|
|
130
|
-
try {
|
|
131
|
-
result = (0, import_state.transaction)(() => {
|
|
132
|
-
return callback(txn);
|
|
133
|
-
});
|
|
134
|
-
if (trackChanges) {
|
|
135
|
-
changes = txn.getChangesSince(clockBefore)?.diff;
|
|
136
|
-
}
|
|
137
|
-
} catch (error) {
|
|
138
|
-
console.error("Error in transaction", error);
|
|
139
|
-
throw error;
|
|
140
|
-
} finally {
|
|
141
|
-
txn.close();
|
|
142
|
-
}
|
|
143
|
-
if (typeof result === "object" && result && "then" in result && typeof result.then === "function") {
|
|
144
|
-
const err = new Error("Transaction must return a value, not a promise");
|
|
145
|
-
console.error(err);
|
|
146
|
-
throw err;
|
|
147
|
-
}
|
|
148
|
-
const clockAfter = this.documentClock.get();
|
|
149
|
-
const didChange = clockAfter > clockBefore;
|
|
150
|
-
if (didChange) {
|
|
151
|
-
this.notifier.notify({ id: opts?.id, documentClock: clockAfter });
|
|
152
|
-
}
|
|
153
|
-
return { documentClock: clockAfter, didChange: clockAfter > clockBefore, result, changes };
|
|
154
|
-
}
|
|
155
|
-
getClock() {
|
|
156
|
-
return this.documentClock.get();
|
|
157
|
-
}
|
|
158
|
-
/** @internal */
|
|
159
|
-
pruneTombstones = (0, import_utils.throttle)(
|
|
160
|
-
() => {
|
|
161
|
-
if (this.tombstones.size > MAX_TOMBSTONES) {
|
|
162
|
-
const tombstones = Array.from(this.tombstones.entries()).map(([id, clock]) => ({ id, clock })).sort((a, b) => a.clock - b.clock);
|
|
163
|
-
const result = computeTombstonePruning({
|
|
164
|
-
tombstones,
|
|
165
|
-
documentClock: this.documentClock.get()
|
|
166
|
-
});
|
|
167
|
-
if (result) {
|
|
168
|
-
this.tombstoneHistoryStartsAtClock.set(result.newTombstoneHistoryStartsAtClock);
|
|
169
|
-
this.tombstones.deleteMany(result.idsToDelete);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
1e3,
|
|
174
|
-
// prevent this from running synchronously to avoid blocking requests
|
|
175
|
-
{ leading: false }
|
|
176
|
-
);
|
|
177
|
-
getSnapshot() {
|
|
178
|
-
return {
|
|
179
|
-
tombstoneHistoryStartsAtClock: this.tombstoneHistoryStartsAtClock.get(),
|
|
180
|
-
documentClock: this.documentClock.get(),
|
|
181
|
-
documents: Array.from(this.documents.values()),
|
|
182
|
-
tombstones: Object.fromEntries(this.tombstones.entries()),
|
|
183
|
-
schema: this.schema.get()
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
class InMemorySyncStorageTransaction {
|
|
188
|
-
constructor(storage) {
|
|
189
|
-
this.storage = storage;
|
|
190
|
-
this._clock = this.storage.documentClock.get();
|
|
191
|
-
}
|
|
192
|
-
_clock;
|
|
193
|
-
_closed = false;
|
|
194
|
-
/** @internal */
|
|
195
|
-
close() {
|
|
196
|
-
this._closed = true;
|
|
197
|
-
}
|
|
198
|
-
assertNotClosed() {
|
|
199
|
-
(0, import_utils.assert)(!this._closed, "Transaction has ended, iterator cannot be consumed");
|
|
200
|
-
}
|
|
201
|
-
getClock() {
|
|
202
|
-
return this._clock;
|
|
203
|
-
}
|
|
204
|
-
didIncrementClock = false;
|
|
205
|
-
getNextClock() {
|
|
206
|
-
if (!this.didIncrementClock) {
|
|
207
|
-
this.didIncrementClock = true;
|
|
208
|
-
this._clock = this.storage.documentClock.set(this.storage.documentClock.get() + 1);
|
|
209
|
-
}
|
|
210
|
-
return this._clock;
|
|
211
|
-
}
|
|
212
|
-
get(id) {
|
|
213
|
-
this.assertNotClosed();
|
|
214
|
-
return this.storage.documents.get(id)?.state;
|
|
215
|
-
}
|
|
216
|
-
set(id, record) {
|
|
217
|
-
this.assertNotClosed();
|
|
218
|
-
(0, import_utils.assert)(id === record.id, `Record id mismatch: key does not match record.id`);
|
|
219
|
-
const clock = this.getNextClock();
|
|
220
|
-
if (this.storage.tombstones.has(id)) {
|
|
221
|
-
this.storage.tombstones.delete(id);
|
|
222
|
-
}
|
|
223
|
-
this.storage.documents.set(id, {
|
|
224
|
-
state: (0, import_store.devFreeze)(record),
|
|
225
|
-
lastChangedClock: clock
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
delete(id) {
|
|
229
|
-
this.assertNotClosed();
|
|
230
|
-
if (!this.storage.documents.has(id)) return;
|
|
231
|
-
const clock = this.getNextClock();
|
|
232
|
-
this.storage.documents.delete(id);
|
|
233
|
-
this.storage.tombstones.set(id, clock);
|
|
234
|
-
this.storage.pruneTombstones();
|
|
235
|
-
}
|
|
236
|
-
*entries() {
|
|
237
|
-
this.assertNotClosed();
|
|
238
|
-
for (const [id, record] of this.storage.documents.entries()) {
|
|
239
|
-
this.assertNotClosed();
|
|
240
|
-
yield [id, record.state];
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
*keys() {
|
|
244
|
-
this.assertNotClosed();
|
|
245
|
-
for (const key of this.storage.documents.keys()) {
|
|
246
|
-
this.assertNotClosed();
|
|
247
|
-
yield key;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
*values() {
|
|
251
|
-
this.assertNotClosed();
|
|
252
|
-
for (const record of this.storage.documents.values()) {
|
|
253
|
-
this.assertNotClosed();
|
|
254
|
-
yield record.state;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
getSchema() {
|
|
258
|
-
this.assertNotClosed();
|
|
259
|
-
return this.storage.schema.get();
|
|
260
|
-
}
|
|
261
|
-
setSchema(schema) {
|
|
262
|
-
this.assertNotClosed();
|
|
263
|
-
this.storage.schema.set(schema);
|
|
264
|
-
}
|
|
265
|
-
getChangesSince(sinceClock) {
|
|
266
|
-
this.assertNotClosed();
|
|
267
|
-
const clock = this.storage.documentClock.get();
|
|
268
|
-
if (sinceClock === clock) return void 0;
|
|
269
|
-
if (sinceClock > clock) {
|
|
270
|
-
sinceClock = -1;
|
|
271
|
-
}
|
|
272
|
-
const diff = { puts: {}, deletes: [] };
|
|
273
|
-
const wipeAll = sinceClock < this.storage.tombstoneHistoryStartsAtClock.get();
|
|
274
|
-
for (const doc of this.storage.documents.values()) {
|
|
275
|
-
if (wipeAll || doc.lastChangedClock > sinceClock) {
|
|
276
|
-
diff.puts[doc.state.id] = doc.state;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
for (const [id, clock2] of this.storage.tombstones.entries()) {
|
|
280
|
-
if (clock2 > sinceClock) {
|
|
281
|
-
diff.deletes.push(id);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return { diff, wipeAll };
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
//# sourceMappingURL=InMemorySyncStorage.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/lib/InMemorySyncStorage.ts"],
|
|
4
|
-
"sourcesContent": ["import { atom, Atom, transaction } from '@tldraw/state'\nimport { AtomMap, devFreeze, SerializedSchema, UnknownRecord } from '@tldraw/store'\nimport {\n\tcreateTLSchema,\n\tDocumentRecordType,\n\tPageRecordType,\n\tTLDOCUMENT_ID,\n\tTLPageId,\n} from '@tldraw/tlschema'\nimport { assert, IndexKey, objectMapEntries, throttle } from '@tldraw/utils'\nimport { MicrotaskNotifier } from './MicrotaskNotifier'\nimport { RoomSnapshot } from './TLSyncRoom'\nimport {\n\tTLSyncForwardDiff,\n\tTLSyncStorage,\n\tTLSyncStorageGetChangesSinceResult,\n\tTLSyncStorageOnChangeCallbackProps,\n\tTLSyncStorageTransaction,\n\tTLSyncStorageTransactionCallback,\n\tTLSyncStorageTransactionOptions,\n\tTLSyncStorageTransactionResult,\n} from './TLSyncStorage'\n\n/** @internal */\nexport const TOMBSTONE_PRUNE_BUFFER_SIZE = 1000\n/** @internal */\nexport const MAX_TOMBSTONES = 5000\n\n/**\n * Result of computing which tombstones to prune.\n * @internal\n */\nexport interface TombstonePruneResult {\n\t/** The new value for tombstoneHistoryStartsAtClock */\n\tnewTombstoneHistoryStartsAtClock: number\n\t/** IDs of tombstones to delete */\n\tidsToDelete: string[]\n}\n\n/**\n * Computes which tombstones should be pruned, avoiding partial history for any clock value.\n * Returns null if no pruning is needed (tombstone count <= maxTombstones).\n *\n * @param tombstones - Array of tombstones sorted by clock ascending (oldest first)\n * @param documentClock - Current document clock (used as fallback if all tombstones are deleted)\n * @param maxTombstones - Maximum number of tombstones to keep (default: MAX_TOMBSTONES)\n * @param pruneBufferSize - Extra tombstones to prune beyond the threshold (default: TOMBSTONE_PRUNE_BUFFER_SIZE)\n * @returns Pruning result or null if no pruning needed\n *\n * @internal\n */\nexport function computeTombstonePruning({\n\ttombstones,\n\tdocumentClock,\n\tmaxTombstones = MAX_TOMBSTONES,\n\tpruneBufferSize = TOMBSTONE_PRUNE_BUFFER_SIZE,\n}: {\n\ttombstones: Array<{ id: string; clock: number }>\n\tdocumentClock: number\n\tmaxTombstones?: number\n\tpruneBufferSize?: number\n}): TombstonePruneResult | null {\n\tif (tombstones.length <= maxTombstones) {\n\t\treturn null\n\t}\n\n\t// Determine how many to delete, avoiding partial history for a clock value\n\tlet cutoff = pruneBufferSize + tombstones.length - maxTombstones\n\twhile (\n\t\tcutoff < tombstones.length &&\n\t\ttombstones[cutoff - 1]?.clock === tombstones[cutoff]?.clock\n\t) {\n\t\tcutoff++\n\t}\n\n\t// Set history start to the oldest remaining tombstone's clock\n\t// (or documentClock if we're deleting everything)\n\tconst oldestRemaining = tombstones[cutoff]\n\tconst newTombstoneHistoryStartsAtClock = oldestRemaining?.clock ?? documentClock\n\n\t// Collect the oldest tombstones to delete (first cutoff entries)\n\tconst idsToDelete = tombstones.slice(0, cutoff).map((t) => t.id)\n\n\treturn { newTombstoneHistoryStartsAtClock, idsToDelete }\n}\n\n/**\n * Default initial snapshot for a new room.\n * @public\n */\nexport const DEFAULT_INITIAL_SNAPSHOT = {\n\tdocumentClock: 0,\n\ttombstoneHistoryStartsAtClock: 0,\n\tschema: createTLSchema().serialize(),\n\tdocuments: [\n\t\t{\n\t\t\tstate: DocumentRecordType.create({ id: TLDOCUMENT_ID }),\n\t\t\tlastChangedClock: 0,\n\t\t},\n\t\t{\n\t\t\tstate: PageRecordType.create({\n\t\t\t\tid: 'page:page' as TLPageId,\n\t\t\t\tname: 'Page 1',\n\t\t\t\tindex: 'a1' as IndexKey,\n\t\t\t}),\n\t\t\tlastChangedClock: 0,\n\t\t},\n\t],\n}\n\n/**\n * In-memory implementation of TLSyncStorage using AtomMap for documents and tombstones,\n * and atoms for clock values. This is the default storage implementation used by TLSyncRoom.\n *\n * @public\n */\nexport class InMemorySyncStorage<R extends UnknownRecord> implements TLSyncStorage<R> {\n\t/** @internal */\n\tdocuments: AtomMap<string, { state: R; lastChangedClock: number }>\n\t/** @internal */\n\ttombstones: AtomMap<string, number>\n\t/** @internal */\n\tschema: Atom<SerializedSchema>\n\t/** @internal */\n\tdocumentClock: Atom<number>\n\t/** @internal */\n\ttombstoneHistoryStartsAtClock: Atom<number>\n\n\tprivate notifier = new MicrotaskNotifier<[TLSyncStorageOnChangeCallbackProps]>()\n\tonChange(callback: (arg: TLSyncStorageOnChangeCallbackProps) => unknown): () => void {\n\t\treturn this.notifier.register(callback)\n\t}\n\n\tconstructor({\n\t\tsnapshot = DEFAULT_INITIAL_SNAPSHOT,\n\t\tonChange,\n\t}: {\n\t\tsnapshot?: RoomSnapshot\n\t\tonChange?(arg: TLSyncStorageOnChangeCallbackProps): unknown\n\t} = {}) {\n\t\tconst maxClockValue = Math.max(\n\t\t\t0,\n\t\t\t...Object.values(snapshot.tombstones ?? {}),\n\t\t\t...Object.values(snapshot.documents.map((d) => d.lastChangedClock))\n\t\t)\n\t\tthis.documents = new AtomMap(\n\t\t\t'room documents',\n\t\t\tsnapshot.documents.map((d) => [\n\t\t\t\td.state.id,\n\t\t\t\t{ state: devFreeze(d.state) as R, lastChangedClock: d.lastChangedClock },\n\t\t\t])\n\t\t)\n\t\tconst documentClock = Math.max(maxClockValue, snapshot.documentClock ?? snapshot.clock ?? 0)\n\n\t\tthis.documentClock = atom('document clock', documentClock)\n\t\t// math.min to make sure the tombstone history starts at or before the document clock\n\t\tconst tombstoneHistoryStartsAtClock = Math.min(\n\t\t\tsnapshot.tombstoneHistoryStartsAtClock ?? documentClock,\n\t\t\tdocumentClock\n\t\t)\n\t\tthis.tombstoneHistoryStartsAtClock = atom(\n\t\t\t'tombstone history starts at clock',\n\t\t\ttombstoneHistoryStartsAtClock\n\t\t)\n\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\tthis.schema = atom('schema', snapshot.schema ?? createTLSchema().serializeEarliestVersion())\n\t\tthis.tombstones = new AtomMap(\n\t\t\t'room tombstones',\n\t\t\t// If the tombstone history starts now (or we didn't have the\n\t\t\t// tombstoneHistoryStartsAtClock) then there are no tombstones\n\t\t\ttombstoneHistoryStartsAtClock === documentClock\n\t\t\t\t? []\n\t\t\t\t: objectMapEntries(snapshot.tombstones ?? {})\n\t\t)\n\t\tif (onChange) {\n\t\t\tthis.onChange(onChange)\n\t\t}\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.documentClock.get()\n\t\tconst trackChanges = opts?.emitChanges === 'always'\n\t\tconst txn = new InMemorySyncStorageTransaction<R>(this)\n\t\tlet result: T\n\t\tlet changes: TLSyncForwardDiff<R> | undefined\n\t\ttry {\n\t\t\tresult = transaction(() => {\n\t\t\t\treturn callback(txn as any)\n\t\t\t}) as T\n\t\t\tif (trackChanges) {\n\t\t\t\tchanges = txn.getChangesSince(clockBefore)?.diff\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error('Error in transaction', error)\n\t\t\tthrow error\n\t\t} finally {\n\t\t\ttxn.close()\n\t\t}\n\t\tif (\n\t\t\ttypeof result === 'object' &&\n\t\t\tresult &&\n\t\t\t'then' in result &&\n\t\t\ttypeof result.then === 'function'\n\t\t) {\n\t\t\tconst err = new Error('Transaction must return a value, not a promise')\n\t\t\tconsole.error(err)\n\t\t\tthrow err\n\t\t}\n\n\t\tconst clockAfter = this.documentClock.get()\n\t\tconst didChange = clockAfter > clockBefore\n\t\tif (didChange) {\n\t\t\tthis.notifier.notify({ id: opts?.id, documentClock: clockAfter })\n\t\t}\n\t\t// InMemorySyncStorage applies changes verbatim, so we only emit changes\n\t\t// when 'always' is specified (not for 'when-different')\n\t\treturn { documentClock: clockAfter, didChange: clockAfter > clockBefore, result, changes }\n\t}\n\n\tgetClock(): number {\n\t\treturn this.documentClock.get()\n\t}\n\n\t/** @internal */\n\tpruneTombstones = throttle(\n\t\t() => {\n\t\t\tif (this.tombstones.size > MAX_TOMBSTONES) {\n\t\t\t\t// Convert to array and sort by clock ascending (oldest first)\n\t\t\t\tconst tombstones = Array.from(this.tombstones.entries())\n\t\t\t\t\t.map(([id, clock]) => ({ id, clock }))\n\t\t\t\t\t.sort((a, b) => a.clock - b.clock)\n\n\t\t\t\tconst result = computeTombstonePruning({\n\t\t\t\t\ttombstones,\n\t\t\t\t\tdocumentClock: this.documentClock.get(),\n\t\t\t\t})\n\t\t\t\tif (result) {\n\t\t\t\t\tthis.tombstoneHistoryStartsAtClock.set(result.newTombstoneHistoryStartsAtClock)\n\t\t\t\t\tthis.tombstones.deleteMany(result.idsToDelete)\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.tombstoneHistoryStartsAtClock.get(),\n\t\t\tdocumentClock: this.documentClock.get(),\n\t\t\tdocuments: Array.from(this.documents.values()),\n\t\t\ttombstones: Object.fromEntries(this.tombstones.entries()),\n\t\t\tschema: this.schema.get(),\n\t\t}\n\t}\n}\n\n/**\n * Transaction implementation for InMemorySyncStorage.\n * Provides access to documents, tombstones, and metadata within a transaction.\n *\n * @internal\n */\nclass InMemorySyncStorageTransaction<R extends UnknownRecord>\n\timplements TLSyncStorageTransaction<R>\n{\n\tprivate _clock\n\tprivate _closed = false\n\n\tconstructor(private storage: InMemorySyncStorage<R>) {\n\t\tthis._clock = this.storage.documentClock.get()\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 didIncrementClock: boolean = false\n\tprivate getNextClock(): number {\n\t\tif (!this.didIncrementClock) {\n\t\t\tthis.didIncrementClock = true\n\t\t\tthis._clock = this.storage.documentClock.set(this.storage.documentClock.get() + 1)\n\t\t}\n\t\treturn this._clock\n\t}\n\n\tget(id: string): R | undefined {\n\t\tthis.assertNotClosed()\n\t\treturn this.storage.documents.get(id)?.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\tif (this.storage.tombstones.has(id)) {\n\t\t\tthis.storage.tombstones.delete(id)\n\t\t}\n\t\tthis.storage.documents.set(id, {\n\t\t\tstate: devFreeze(record) as R,\n\t\t\tlastChangedClock: clock,\n\t\t})\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\tif (!this.storage.documents.has(id)) return\n\t\tconst clock = this.getNextClock()\n\t\tthis.storage.documents.delete(id)\n\t\tthis.storage.tombstones.set(id, clock)\n\t\tthis.storage.pruneTombstones()\n\t}\n\n\t*entries(): IterableIterator<[string, R]> {\n\t\tthis.assertNotClosed()\n\t\tfor (const [id, record] of this.storage.documents.entries()) {\n\t\t\tthis.assertNotClosed()\n\t\t\tyield [id, record.state]\n\t\t}\n\t}\n\n\t*keys(): IterableIterator<string> {\n\t\tthis.assertNotClosed()\n\t\tfor (const key of this.storage.documents.keys()) {\n\t\t\tthis.assertNotClosed()\n\t\t\tyield key\n\t\t}\n\t}\n\n\t*values(): IterableIterator<R> {\n\t\tthis.assertNotClosed()\n\t\tfor (const record of this.storage.documents.values()) {\n\t\t\tthis.assertNotClosed()\n\t\t\tyield record.state\n\t\t}\n\t}\n\n\tgetSchema(): SerializedSchema {\n\t\tthis.assertNotClosed()\n\t\treturn this.storage.schema.get()\n\t}\n\n\tsetSchema(schema: SerializedSchema): void {\n\t\tthis.assertNotClosed()\n\t\tthis.storage.schema.set(schema)\n\t}\n\n\tgetChangesSince(sinceClock: number): TLSyncStorageGetChangesSinceResult<R> | undefined {\n\t\tthis.assertNotClosed()\n\t\tconst clock = this.storage.documentClock.get()\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.tombstoneHistoryStartsAtClock.get()\n\t\tfor (const doc of this.storage.documents.values()) {\n\t\t\tif (wipeAll || doc.lastChangedClock > sinceClock) {\n\t\t\t\t// For historical changes, we don't have \"from\" state, so use added\n\t\t\t\tdiff.puts[doc.state.id] = doc.state as R\n\t\t\t}\n\t\t}\n\t\tfor (const [id, clock] of this.storage.tombstones.entries()) {\n\t\t\tif (clock > sinceClock) {\n\t\t\t\t// For tombstones, we don't have the removed record, use placeholder\n\t\t\t\tdiff.deletes.push(id)\n\t\t\t}\n\t\t}\n\t\treturn { diff, wipeAll }\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["clock"]
|
|
7
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
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 MicrotaskNotifier_exports = {};
|
|
20
|
-
__export(MicrotaskNotifier_exports, {
|
|
21
|
-
MicrotaskNotifier: () => MicrotaskNotifier
|
|
22
|
-
});
|
|
23
|
-
module.exports = __toCommonJS(MicrotaskNotifier_exports);
|
|
24
|
-
class MicrotaskNotifier {
|
|
25
|
-
listeners = /* @__PURE__ */ new Set();
|
|
26
|
-
notify(...props) {
|
|
27
|
-
queueMicrotask(() => {
|
|
28
|
-
for (const listener of this.listeners) {
|
|
29
|
-
try {
|
|
30
|
-
listener(...props);
|
|
31
|
-
} catch (error) {
|
|
32
|
-
console.error("Error in MicrotaskNotifier listener", error);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
register(_listener) {
|
|
38
|
-
let didDelete = false;
|
|
39
|
-
queueMicrotask(() => {
|
|
40
|
-
if (didDelete) return;
|
|
41
|
-
this.listeners.add(_listener);
|
|
42
|
-
});
|
|
43
|
-
return () => {
|
|
44
|
-
if (didDelete) return;
|
|
45
|
-
didDelete = true;
|
|
46
|
-
this.listeners.delete(_listener);
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
//# sourceMappingURL=MicrotaskNotifier.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/lib/MicrotaskNotifier.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * A notifier that queues its notifications to the microtask queue.\n * This is useful for avoiding race conditions where callbacks are triggered prematurely.\n */\nexport class MicrotaskNotifier<T extends unknown[]> {\n\tprivate listeners = new Set<(...props: T) => void>()\n\n\tnotify(...props: T) {\n\t\tqueueMicrotask(() => {\n\t\t\tfor (const listener of this.listeners) {\n\t\t\t\ttry {\n\t\t\t\t\tlistener(...props)\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error('Error in MicrotaskNotifier listener', error)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tregister(_listener: (...props: T) => void) {\n\t\t// Track if unsubscribe was called before the add microtask ran\n\t\tlet didDelete = false\n\n\t\t// We defer the add to the microtask queue to ensure the callback isn't invoked\n\t\t// for changes that happened before this registration\n\t\tqueueMicrotask(() => {\n\t\t\tif (didDelete) return\n\t\t\tthis.listeners.add(_listener)\n\t\t})\n\n\t\treturn () => {\n\t\t\tif (didDelete) return\n\t\t\tdidDelete = true\n\t\t\t// Synchronous delete ensures immediate unsubscription\n\t\t\tthis.listeners.delete(_listener)\n\t\t}\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,kBAAuC;AAAA,EAC3C,YAAY,oBAAI,IAA2B;AAAA,EAEnD,UAAU,OAAU;AACnB,mBAAe,MAAM;AACpB,iBAAW,YAAY,KAAK,WAAW;AACtC,YAAI;AACH,mBAAS,GAAG,KAAK;AAAA,QAClB,SAAS,OAAO;AACf,kBAAQ,MAAM,uCAAuC,KAAK;AAAA,QAC3D;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAAA,EAEA,SAAS,WAAkC;AAE1C,QAAI,YAAY;AAIhB,mBAAe,MAAM;AACpB,UAAI,UAAW;AACf,WAAK,UAAU,IAAI,SAAS;AAAA,IAC7B,CAAC;AAED,WAAO,MAAM;AACZ,UAAI,UAAW;AACf,kBAAY;AAEZ,WAAK,UAAU,OAAO,SAAS;AAAA,IAChC;AAAA,EACD;AACD;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
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 NodeSqliteWrapper_exports = {};
|
|
20
|
-
__export(NodeSqliteWrapper_exports, {
|
|
21
|
-
NodeSqliteWrapper: () => NodeSqliteWrapper
|
|
22
|
-
});
|
|
23
|
-
module.exports = __toCommonJS(NodeSqliteWrapper_exports);
|
|
24
|
-
class NodeSqliteWrapper {
|
|
25
|
-
constructor(db, config) {
|
|
26
|
-
this.db = db;
|
|
27
|
-
this.config = config;
|
|
28
|
-
}
|
|
29
|
-
exec(sql) {
|
|
30
|
-
this.db.exec(sql);
|
|
31
|
-
}
|
|
32
|
-
prepare(sql) {
|
|
33
|
-
return this.db.prepare(sql);
|
|
34
|
-
}
|
|
35
|
-
transaction(callback) {
|
|
36
|
-
this.db.exec("BEGIN");
|
|
37
|
-
let result;
|
|
38
|
-
try {
|
|
39
|
-
result = callback();
|
|
40
|
-
} catch (e) {
|
|
41
|
-
this.db.exec("ROLLBACK");
|
|
42
|
-
throw e;
|
|
43
|
-
}
|
|
44
|
-
this.db.exec("COMMIT");
|
|
45
|
-
return result;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
//# sourceMappingURL=NodeSqliteWrapper.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/lib/NodeSqliteWrapper.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n\ttype TLSqliteInputValue,\n\ttype TLSqliteRow,\n\ttype TLSyncSqliteStatement,\n\ttype TLSyncSqliteWrapper,\n\ttype TLSyncSqliteWrapperConfig,\n} from './SQLiteSyncStorage'\n\n/**\n * Minimal interface for a synchronous SQLite database.\n *\n * This interface is compatible with:\n * - `node:sqlite` DatabaseSync (Node.js 22.5+)\n * - `better-sqlite3` Database\n *\n * Any SQLite library that provides synchronous `exec` and `prepare` methods\n * with the signatures below can be used with {@link NodeSqliteWrapper}.\n *\n * @public\n */\nexport interface SyncSqliteDatabase {\n\t/** Execute raw SQL without returning results */\n\texec(sql: string): void\n\t/** Prepare a statement for execution */\n\tprepare(sql: string): {\n\t\titerate(...params: unknown[]): IterableIterator<unknown>\n\t\tall(...params: unknown[]): unknown[]\n\t\trun(...params: unknown[]): unknown\n\t}\n}\n\n/**\n * A wrapper around synchronous SQLite databases that implements TLSyncSqliteWrapper.\n * Works with both `node:sqlite` DatabaseSync (Node.js 22.5+) and `better-sqlite3` Database.\n *\n * Use this wrapper with SQLiteSyncStorage to persist tldraw sync state to a SQLite database\n * in Node.js environments.\n *\n * @example\n * ```ts\n * // With node:sqlite (Node.js 22.5+)\n * import { DatabaseSync } from 'node:sqlite'\n * import { SQLiteSyncStorage, NodeSqliteWrapper } from '@tldraw/sync-core'\n *\n * const db = new DatabaseSync(':memory:')\n * const sql = new NodeSqliteWrapper(db)\n * const storage = new SQLiteSyncStorage({ sql })\n * ```\n *\n * @example\n * ```ts\n * // With better-sqlite3\n * import Database from 'better-sqlite3'\n * import { SQLiteSyncStorage, NodeSqliteWrapper } from '@tldraw/sync-core'\n *\n * const db = new Database(':memory:')\n * const sql = new NodeSqliteWrapper(db)\n * const storage = new SQLiteSyncStorage({ sql })\n * ```\n *\n * @example\n * ```ts\n * // With table prefix to avoid conflicts with other tables\n * const sql = new NodeSqliteWrapper(db, { tablePrefix: 'tldraw_' })\n * // Creates tables: tldraw_documents, tldraw_tombstones, tldraw_metadata\n * ```\n *\n * @public\n */\nexport class NodeSqliteWrapper implements TLSyncSqliteWrapper {\n\tconstructor(\n\t\tprivate db: SyncSqliteDatabase,\n\t\tpublic config?: TLSyncSqliteWrapperConfig\n\t) {}\n\n\texec(sql: string): void {\n\t\tthis.db.exec(sql)\n\t}\n\n\tprepare<\n\t\tTResult extends TLSqliteRow | void = void,\n\t\tTParams extends TLSqliteInputValue[] = TLSqliteInputValue[],\n\t>(sql: string): TLSyncSqliteStatement<TResult, TParams> {\n\t\treturn this.db.prepare(sql) as unknown as TLSyncSqliteStatement<TResult, TParams>\n\t}\n\n\ttransaction<T>(callback: () => T): T {\n\t\tthis.db.exec('BEGIN')\n\t\tlet result: T\n\t\ttry {\n\t\t\tresult = callback()\n\t\t} catch (e) {\n\t\t\tthis.db.exec('ROLLBACK')\n\t\t\tthrow e\n\t\t}\n\t\tthis.db.exec('COMMIT')\n\t\treturn result\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAqEO,MAAM,kBAAiD;AAAA,EAC7D,YACS,IACD,QACN;AAFO;AACD;AAAA,EACL;AAAA,EAEH,KAAK,KAAmB;AACvB,SAAK,GAAG,KAAK,GAAG;AAAA,EACjB;AAAA,EAEA,QAGE,KAAsD;AACvD,WAAO,KAAK,GAAG,QAAQ,GAAG;AAAA,EAC3B;AAAA,EAEA,YAAe,UAAsB;AACpC,SAAK,GAAG,KAAK,OAAO;AACpB,QAAI;AACJ,QAAI;AACH,eAAS,SAAS;AAAA,IACnB,SAAS,GAAG;AACX,WAAK,GAAG,KAAK,UAAU;AACvB,YAAM;AAAA,IACP;AACA,SAAK,GAAG,KAAK,QAAQ;AACrB,WAAO;AAAA,EACR;AACD;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|