@tldraw/sync-core 4.3.0-canary.bf87ebaf143a → 4.3.0-canary.c08047039e53
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/README.md +1 -1
- package/dist-cjs/index.d.ts +252 -4
- package/dist-cjs/index.js +7 -1
- 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 +32 -34
- package/dist-cjs/lib/InMemorySyncStorage.js.map +2 -2
- 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/SQLiteSyncStorage.js +428 -0
- package/dist-cjs/lib/SQLiteSyncStorage.js.map +7 -0
- package/dist-cjs/lib/TLSyncClient.js +34 -20
- package/dist-cjs/lib/TLSyncClient.js.map +2 -2
- package/dist-esm/index.d.mts +252 -4
- package/dist-esm/index.mjs +9 -1
- 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 +32 -34
- package/dist-esm/lib/InMemorySyncStorage.mjs.map +2 -2
- 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/SQLiteSyncStorage.mjs +414 -0
- package/dist-esm/lib/SQLiteSyncStorage.mjs.map +7 -0
- package/dist-esm/lib/TLSyncClient.mjs +35 -21
- package/dist-esm/lib/TLSyncClient.mjs.map +2 -2
- package/package.json +12 -11
- package/src/index.ts +11 -0
- package/src/lib/ClientWebSocketAdapter.test.ts +3 -0
- package/src/lib/DurableObjectSqliteSyncWrapper.ts +95 -0
- package/src/lib/InMemorySyncStorage.ts +74 -44
- 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/SQLiteSyncStorage.ts +627 -0
- package/src/lib/TLSyncClient.ts +55 -23
- package/src/lib/computeTombstonePruning.test.ts +352 -0
- package/src/test/InMemorySyncStorage.test.ts +105 -0
- package/src/test/SQLiteSyncStorage.test.ts +1484 -0
- package/src/test/TLSyncRoom.test.ts +2 -0
- package/src/test/upgradeDowngrade.test.ts +8 -0
- package/src/test/validation.test.ts +5 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ This project contains core functionality for [tldraw sync](https://tldraw.dev/do
|
|
|
6
6
|
|
|
7
7
|
This project is part of the tldraw SDK. It is provided under the [tldraw SDK license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md).
|
|
8
8
|
|
|
9
|
-
You can use the tldraw SDK in commercial or non-commercial projects so long as you preserve the "Made with tldraw" watermark on the canvas. To remove the watermark, you can purchase a [business license](https://tldraw.dev
|
|
9
|
+
You can use the tldraw SDK in commercial or non-commercial projects so long as you preserve the "Made with tldraw" watermark on the canvas. To remove the watermark, you can purchase a [business license](https://tldraw.dev/pricing). Visit [tldraw.dev](https://tldraw.dev) to learn more.
|
|
10
10
|
|
|
11
11
|
## Trademarks
|
|
12
12
|
|
package/dist-cjs/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { SerializedSchemaV2 } from '@tldraw/store';
|
|
|
9
9
|
import { Signal } from '@tldraw/state';
|
|
10
10
|
import { Store } from '@tldraw/store';
|
|
11
11
|
import { StoreSchema } from '@tldraw/store';
|
|
12
|
+
import { StoreSnapshot } from '@tldraw/store';
|
|
12
13
|
import { SynchronousStorage } from '@tldraw/store';
|
|
13
14
|
import { TLDocument } from '@tldraw/tlschema';
|
|
14
15
|
import { TLPage } from '@tldraw/tlschema';
|
|
@@ -46,6 +47,54 @@ export declare const DEFAULT_INITIAL_SNAPSHOT: {
|
|
|
46
47
|
|
|
47
48
|
/* Excluded from this release type: diffRecord */
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* A wrapper around Cloudflare Durable Object's SqlStorage that implements TLSyncSqliteWrapper.
|
|
52
|
+
*
|
|
53
|
+
* Use this wrapper with SQLiteSyncStorage to persist tldraw sync state using
|
|
54
|
+
* Cloudflare Durable Object's built-in SQLite storage. This provides automatic
|
|
55
|
+
* persistence that survives Durable Object hibernation and restarts.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { SQLiteSyncStorage, DurableObjectSqliteSyncWrapper } from '@tldraw/sync-core'
|
|
60
|
+
*
|
|
61
|
+
* // In your Durable Object class:
|
|
62
|
+
* class MyDurableObject extends DurableObject {
|
|
63
|
+
* private storage: SQLiteSyncStorage
|
|
64
|
+
*
|
|
65
|
+
* constructor(ctx: DurableObjectState, env: Env) {
|
|
66
|
+
* super(ctx, env)
|
|
67
|
+
* const sql = new DurableObjectSqliteSyncWrapper(ctx.storage)
|
|
68
|
+
* this.storage = new SQLiteSyncStorage({ sql })
|
|
69
|
+
* }
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* // With table prefix to avoid conflicts with other tables
|
|
76
|
+
* const sql = new DurableObjectSqliteSyncWrapper(this.ctx.storage, { tablePrefix: 'tldraw_' })
|
|
77
|
+
* // Creates tables: tldraw_documents, tldraw_tombstones, tldraw_metadata
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @public
|
|
81
|
+
*/
|
|
82
|
+
export declare class DurableObjectSqliteSyncWrapper implements TLSyncSqliteWrapper {
|
|
83
|
+
private storage;
|
|
84
|
+
config?: TLSyncSqliteWrapperConfig | undefined;
|
|
85
|
+
constructor(storage: {
|
|
86
|
+
sql: {
|
|
87
|
+
exec(sql: string, ...bindings: unknown[]): Iterable<any> & {
|
|
88
|
+
toArray(): any[];
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
transactionSync(callback: () => any): any;
|
|
92
|
+
}, config?: TLSyncSqliteWrapperConfig | undefined);
|
|
93
|
+
exec(sql: string): void;
|
|
94
|
+
prepare<TResult extends TLSqliteRow | void = void, TParams extends TLSqliteInputValue[] = []>(sql: string): TLSyncSqliteStatement<TResult, TParams>;
|
|
95
|
+
transaction<T>(callback: () => T): T;
|
|
96
|
+
}
|
|
97
|
+
|
|
49
98
|
/* Excluded from this release type: getNetworkDiff */
|
|
50
99
|
|
|
51
100
|
/* Excluded from this release type: getTlsyncProtocolVersion */
|
|
@@ -62,7 +111,7 @@ export declare class InMemorySyncStorage<R extends UnknownRecord> implements TLS
|
|
|
62
111
|
/* Excluded from this release type: schema */
|
|
63
112
|
/* Excluded from this release type: documentClock */
|
|
64
113
|
/* Excluded from this release type: tombstoneHistoryStartsAtClock */
|
|
65
|
-
private
|
|
114
|
+
private notifier;
|
|
66
115
|
onChange(callback: (arg: TLSyncStorageOnChangeCallbackProps) => unknown): () => void;
|
|
67
116
|
constructor({ snapshot, onChange, }?: {
|
|
68
117
|
onChange?(arg: TLSyncStorageOnChangeCallbackProps): unknown;
|
|
@@ -153,6 +202,53 @@ export declare function loadSnapshotIntoStorage<R extends UnknownRecord>(txn: TL
|
|
|
153
202
|
|
|
154
203
|
/* Excluded from this release type: NetworkDiff */
|
|
155
204
|
|
|
205
|
+
/**
|
|
206
|
+
* A wrapper around synchronous SQLite databases that implements TLSyncSqliteWrapper.
|
|
207
|
+
* Works with both `node:sqlite` DatabaseSync (Node.js 22.5+) and `better-sqlite3` Database.
|
|
208
|
+
*
|
|
209
|
+
* Use this wrapper with SQLiteSyncStorage to persist tldraw sync state to a SQLite database
|
|
210
|
+
* in Node.js environments.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* // With node:sqlite (Node.js 22.5+)
|
|
215
|
+
* import { DatabaseSync } from 'node:sqlite'
|
|
216
|
+
* import { SQLiteSyncStorage, NodeSqliteWrapper } from '@tldraw/sync-core'
|
|
217
|
+
*
|
|
218
|
+
* const db = new DatabaseSync(':memory:')
|
|
219
|
+
* const sql = new NodeSqliteWrapper(db)
|
|
220
|
+
* const storage = new SQLiteSyncStorage({ sql })
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```ts
|
|
225
|
+
* // With better-sqlite3
|
|
226
|
+
* import Database from 'better-sqlite3'
|
|
227
|
+
* import { SQLiteSyncStorage, NodeSqliteWrapper } from '@tldraw/sync-core'
|
|
228
|
+
*
|
|
229
|
+
* const db = new Database(':memory:')
|
|
230
|
+
* const sql = new NodeSqliteWrapper(db)
|
|
231
|
+
* const storage = new SQLiteSyncStorage({ sql })
|
|
232
|
+
* ```
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* // With table prefix to avoid conflicts with other tables
|
|
237
|
+
* const sql = new NodeSqliteWrapper(db, { tablePrefix: 'tldraw_' })
|
|
238
|
+
* // Creates tables: tldraw_documents, tldraw_tombstones, tldraw_metadata
|
|
239
|
+
* ```
|
|
240
|
+
*
|
|
241
|
+
* @public
|
|
242
|
+
*/
|
|
243
|
+
export declare class NodeSqliteWrapper implements TLSyncSqliteWrapper {
|
|
244
|
+
private db;
|
|
245
|
+
config?: TLSyncSqliteWrapperConfig | undefined;
|
|
246
|
+
constructor(db: SyncSqliteDatabase, config?: TLSyncSqliteWrapperConfig | undefined);
|
|
247
|
+
exec(sql: string): void;
|
|
248
|
+
prepare<TResult extends TLSqliteRow | void = void, TParams extends TLSqliteInputValue[] = TLSqliteInputValue[]>(sql: string): TLSyncSqliteStatement<TResult, TParams>;
|
|
249
|
+
transaction<T>(callback: () => T): T;
|
|
250
|
+
}
|
|
251
|
+
|
|
156
252
|
/* Excluded from this release type: ObjectDiff */
|
|
157
253
|
|
|
158
254
|
/**
|
|
@@ -274,6 +370,73 @@ export declare interface RoomStoreMethods<R extends UnknownRecord = UnknownRecor
|
|
|
274
370
|
getAll(): R[];
|
|
275
371
|
}
|
|
276
372
|
|
|
373
|
+
/**
|
|
374
|
+
* SQLite-based implementation of TLSyncStorage.
|
|
375
|
+
* Stores documents, tombstones, metadata, and clock values in SQLite tables.
|
|
376
|
+
*
|
|
377
|
+
* This storage backend provides persistent synchronization state that survives
|
|
378
|
+
* process restarts, unlike InMemorySyncStorage which loses data when the process ends.
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```ts
|
|
382
|
+
* // With Cloudflare Durable Objects
|
|
383
|
+
* import { SQLiteSyncStorage, DurableObjectSqliteSyncWrapper } from '@tldraw/sync-core'
|
|
384
|
+
*
|
|
385
|
+
* const sql = new DurableObjectSqliteSyncWrapper(this.ctx.storage)
|
|
386
|
+
* const storage = new SQLiteSyncStorage({ sql })
|
|
387
|
+
* ```
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* ```ts
|
|
391
|
+
* // With Node.js sqlite (Node 22.5+)
|
|
392
|
+
* import { DatabaseSync } from 'node:sqlite'
|
|
393
|
+
* import { SQLiteSyncStorage, NodeSqliteWrapper } from '@tldraw/sync-core'
|
|
394
|
+
*
|
|
395
|
+
* const db = new DatabaseSync('sync-state.db')
|
|
396
|
+
* const sql = new NodeSqliteWrapper(db)
|
|
397
|
+
* const storage = new SQLiteSyncStorage({ sql })
|
|
398
|
+
* ```
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```ts
|
|
402
|
+
* // Initialize with an existing snapshot
|
|
403
|
+
* const storage = new SQLiteSyncStorage({ sql, snapshot: existingSnapshot })
|
|
404
|
+
* ```
|
|
405
|
+
*
|
|
406
|
+
* @public
|
|
407
|
+
*/
|
|
408
|
+
export declare class SQLiteSyncStorage<R extends UnknownRecord> implements TLSyncStorage<R> {
|
|
409
|
+
/**
|
|
410
|
+
* Check if the storage has been initialized (has data in the clock table).
|
|
411
|
+
* Useful for determining whether to load from an external source on first access.
|
|
412
|
+
*/
|
|
413
|
+
static hasBeenInitialized(storage: TLSyncSqliteWrapper): boolean;
|
|
414
|
+
/**
|
|
415
|
+
* Get the current document clock value from storage without fully initializing.
|
|
416
|
+
* Returns null if storage has not been initialized.
|
|
417
|
+
* Useful for comparing storage freshness against external sources.
|
|
418
|
+
*/
|
|
419
|
+
static getDocumentClock(storage: TLSyncSqliteWrapper): null | number;
|
|
420
|
+
private readonly stmts;
|
|
421
|
+
private readonly sql;
|
|
422
|
+
constructor({ sql, snapshot, onChange, }: {
|
|
423
|
+
onChange?(arg: TLSyncStorageOnChangeCallbackProps): unknown;
|
|
424
|
+
snapshot?: RoomSnapshot | StoreSnapshot<R>;
|
|
425
|
+
sql: TLSyncSqliteWrapper;
|
|
426
|
+
});
|
|
427
|
+
private notifier;
|
|
428
|
+
onChange(callback: (arg: TLSyncStorageOnChangeCallbackProps) => void): () => void;
|
|
429
|
+
transaction<T>(callback: TLSyncStorageTransactionCallback<R, T>, opts?: TLSyncStorageTransactionOptions): TLSyncStorageTransactionResult<T, R>;
|
|
430
|
+
getClock(): number;
|
|
431
|
+
/* Excluded from this release type: _getTombstoneHistoryStartsAtClock */
|
|
432
|
+
/* Excluded from this release type: _getSchema */
|
|
433
|
+
/* Excluded from this release type: _setSchema */
|
|
434
|
+
/* Excluded from this release type: pruneTombstones */
|
|
435
|
+
getSnapshot(): RoomSnapshot;
|
|
436
|
+
private _iterateDocuments;
|
|
437
|
+
private _iterateTombstones;
|
|
438
|
+
}
|
|
439
|
+
|
|
277
440
|
/**
|
|
278
441
|
* Function type for subscribing to events with a callback.
|
|
279
442
|
* Returns an unsubscribe function to clean up the listener.
|
|
@@ -285,6 +448,29 @@ export declare interface RoomStoreMethods<R extends UnknownRecord = UnknownRecor
|
|
|
285
448
|
*/
|
|
286
449
|
export declare type SubscribingFn<T> = (cb: (val: T) => void) => () => void;
|
|
287
450
|
|
|
451
|
+
/**
|
|
452
|
+
* Minimal interface for a synchronous SQLite database.
|
|
453
|
+
*
|
|
454
|
+
* This interface is compatible with:
|
|
455
|
+
* - `node:sqlite` DatabaseSync (Node.js 22.5+)
|
|
456
|
+
* - `better-sqlite3` Database
|
|
457
|
+
*
|
|
458
|
+
* Any SQLite library that provides synchronous `exec` and `prepare` methods
|
|
459
|
+
* with the signatures below can be used with {@link NodeSqliteWrapper}.
|
|
460
|
+
*
|
|
461
|
+
* @public
|
|
462
|
+
*/
|
|
463
|
+
export declare interface SyncSqliteDatabase {
|
|
464
|
+
/** Execute raw SQL without returning results */
|
|
465
|
+
exec(sql: string): void;
|
|
466
|
+
/** Prepare a statement for execution */
|
|
467
|
+
prepare(sql: string): {
|
|
468
|
+
all(...params: unknown[]): unknown[];
|
|
469
|
+
iterate(...params: unknown[]): IterableIterator<unknown>;
|
|
470
|
+
run(...params: unknown[]): unknown;
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
288
474
|
/* Excluded from this release type: TLConnectRequest */
|
|
289
475
|
|
|
290
476
|
/**
|
|
@@ -932,6 +1118,26 @@ export declare type TLSocketStatusChangeEvent = {
|
|
|
932
1118
|
|
|
933
1119
|
/* Excluded from this release type: TLSocketStatusListener */
|
|
934
1120
|
|
|
1121
|
+
/**
|
|
1122
|
+
* Valid input value types for SQLite query parameters.
|
|
1123
|
+
* These are the types that can be passed as bindings to prepared statements.
|
|
1124
|
+
* @public
|
|
1125
|
+
*/
|
|
1126
|
+
export declare type TLSqliteInputValue = bigint | null | number | string | Uint8Array;
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Possible output value types returned from SQLite queries.
|
|
1130
|
+
* Includes all input types plus Uint8Array for BLOB columns.
|
|
1131
|
+
* @public
|
|
1132
|
+
*/
|
|
1133
|
+
export declare type TLSqliteOutputValue = bigint | null | number | string | Uint8Array;
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* A row returned from a SQLite query, mapping column names to their values.
|
|
1137
|
+
* @public
|
|
1138
|
+
*/
|
|
1139
|
+
export declare type TLSqliteRow = Record<string, TLSqliteOutputValue>;
|
|
1140
|
+
|
|
935
1141
|
/**
|
|
936
1142
|
* Main client-side synchronization engine for collaborative tldraw applications.
|
|
937
1143
|
*
|
|
@@ -1004,6 +1210,12 @@ export declare class TLSyncClient<R extends UnknownRecord, S extends Store<R> =
|
|
|
1004
1210
|
*/
|
|
1005
1211
|
private speculativeChanges;
|
|
1006
1212
|
private disposables;
|
|
1213
|
+
/** Separate scheduler instance for network sync operations */
|
|
1214
|
+
private readonly fpsScheduler;
|
|
1215
|
+
/** Send any unsent push requests to the server */
|
|
1216
|
+
private readonly flushPendingPushRequests;
|
|
1217
|
+
/** Schedule a rebase operation */
|
|
1218
|
+
private readonly scheduleRebase;
|
|
1007
1219
|
/* Excluded from this release type: store */
|
|
1008
1220
|
/* Excluded from this release type: socket */
|
|
1009
1221
|
/* Excluded from this release type: presenceState */
|
|
@@ -1099,8 +1311,8 @@ export declare class TLSyncClient<R extends UnknownRecord, S extends Store<R> =
|
|
|
1099
1311
|
private pushPresence;
|
|
1100
1312
|
/** Push a change to the server, or stash it locally if we're offline */
|
|
1101
1313
|
private push;
|
|
1102
|
-
/**
|
|
1103
|
-
private
|
|
1314
|
+
/** Get the target FPS for network operations based on presence mode */
|
|
1315
|
+
private getSyncFps;
|
|
1104
1316
|
/**
|
|
1105
1317
|
* Applies a 'network' diff to the store this does value-based equality checking so that if the
|
|
1106
1318
|
* data is the same (as opposed to merely identical with ===), then no change is made and no
|
|
@@ -1108,7 +1320,6 @@ export declare class TLSyncClient<R extends UnknownRecord, S extends Store<R> =
|
|
|
1108
1320
|
*/
|
|
1109
1321
|
private applyNetworkDiff;
|
|
1110
1322
|
private rebase;
|
|
1111
|
-
private scheduleRebase;
|
|
1112
1323
|
}
|
|
1113
1324
|
|
|
1114
1325
|
/**
|
|
@@ -1235,6 +1446,43 @@ export declare interface TLSyncLog {
|
|
|
1235
1446
|
|
|
1236
1447
|
/* Excluded from this release type: TLSyncRoom */
|
|
1237
1448
|
|
|
1449
|
+
/**
|
|
1450
|
+
* A prepared statement that can be executed multiple times with different bindings.
|
|
1451
|
+
* @public
|
|
1452
|
+
*/
|
|
1453
|
+
export declare interface TLSyncSqliteStatement<TResult extends TLSqliteRow | void, TParams extends TLSqliteInputValue[] = []> {
|
|
1454
|
+
/** Execute the statement and iterate over results one at a time */
|
|
1455
|
+
iterate(...bindings: TParams): IterableIterator<TResult>;
|
|
1456
|
+
/** Execute the statement and return all results as an array */
|
|
1457
|
+
all(...bindings: TParams): TResult[];
|
|
1458
|
+
/** Execute the statement without returning results (for DML) */
|
|
1459
|
+
run(...bindings: TParams): void;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
/**
|
|
1463
|
+
* Interface for SQLite storage with prepare, exec and transaction capabilities.
|
|
1464
|
+
* @public
|
|
1465
|
+
*/
|
|
1466
|
+
export declare interface TLSyncSqliteWrapper {
|
|
1467
|
+
/** Optional configuration for table names. If not provided, defaults are used. */
|
|
1468
|
+
readonly config?: TLSyncSqliteWrapperConfig;
|
|
1469
|
+
/** Prepare a SQL statement for execution */
|
|
1470
|
+
prepare<TResult extends TLSqliteRow | void, TParams extends TLSqliteInputValue[] = []>(sql: string): TLSyncSqliteStatement<TResult, TParams>;
|
|
1471
|
+
/** Execute raw SQL (for DDL, multi-statement scripts) */
|
|
1472
|
+
exec(sql: string): void;
|
|
1473
|
+
/** Execute a callback within a transaction */
|
|
1474
|
+
transaction<T>(callback: () => T): T;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
/**
|
|
1478
|
+
* Configuration for SQLiteSyncStorage.
|
|
1479
|
+
* @public
|
|
1480
|
+
*/
|
|
1481
|
+
export declare interface TLSyncSqliteWrapperConfig {
|
|
1482
|
+
/** Prefix for all table names (default: ''). E.g. 'sync_' creates tables 'sync_documents', 'sync_tombstones', 'sync_metadata' */
|
|
1483
|
+
tablePrefix?: string;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1238
1486
|
/**
|
|
1239
1487
|
* Pluggable synchronous transactional storage layer for TLSyncRoom.
|
|
1240
1488
|
* Provides methods for managing documents, tombstones, and clocks within transactions.
|
package/dist-cjs/index.js
CHANGED
|
@@ -20,11 +20,14 @@ var index_exports = {};
|
|
|
20
20
|
__export(index_exports, {
|
|
21
21
|
ClientWebSocketAdapter: () => import_ClientWebSocketAdapter.ClientWebSocketAdapter,
|
|
22
22
|
DEFAULT_INITIAL_SNAPSHOT: () => import_InMemorySyncStorage.DEFAULT_INITIAL_SNAPSHOT,
|
|
23
|
+
DurableObjectSqliteSyncWrapper: () => import_DurableObjectSqliteSyncWrapper.DurableObjectSqliteSyncWrapper,
|
|
23
24
|
InMemorySyncStorage: () => import_InMemorySyncStorage.InMemorySyncStorage,
|
|
24
25
|
JsonChunkAssembler: () => import_chunk.JsonChunkAssembler,
|
|
26
|
+
NodeSqliteWrapper: () => import_NodeSqliteWrapper.NodeSqliteWrapper,
|
|
25
27
|
ReconnectManager: () => import_ClientWebSocketAdapter.ReconnectManager,
|
|
26
28
|
RecordOpType: () => import_diff.RecordOpType,
|
|
27
29
|
RoomSessionState: () => import_RoomSession.RoomSessionState,
|
|
30
|
+
SQLiteSyncStorage: () => import_SQLiteSyncStorage.SQLiteSyncStorage,
|
|
28
31
|
TLIncompatibilityReason: () => import_protocol.TLIncompatibilityReason,
|
|
29
32
|
TLRemoteSyncError: () => import_TLRemoteSyncError.TLRemoteSyncError,
|
|
30
33
|
TLSocketRoom: () => import_TLSocketRoom.TLSocketRoom,
|
|
@@ -45,9 +48,12 @@ var import_utils = require("@tldraw/utils");
|
|
|
45
48
|
var import_chunk = require("./lib/chunk");
|
|
46
49
|
var import_ClientWebSocketAdapter = require("./lib/ClientWebSocketAdapter");
|
|
47
50
|
var import_diff = require("./lib/diff");
|
|
51
|
+
var import_DurableObjectSqliteSyncWrapper = require("./lib/DurableObjectSqliteSyncWrapper");
|
|
48
52
|
var import_InMemorySyncStorage = require("./lib/InMemorySyncStorage");
|
|
53
|
+
var import_NodeSqliteWrapper = require("./lib/NodeSqliteWrapper");
|
|
49
54
|
var import_protocol = require("./lib/protocol");
|
|
50
55
|
var import_RoomSession = require("./lib/RoomSession");
|
|
56
|
+
var import_SQLiteSyncStorage = require("./lib/SQLiteSyncStorage");
|
|
51
57
|
var import_TLRemoteSyncError = require("./lib/TLRemoteSyncError");
|
|
52
58
|
var import_TLSocketRoom = require("./lib/TLSocketRoom");
|
|
53
59
|
var import_TLSyncClient = require("./lib/TLSyncClient");
|
|
@@ -55,7 +61,7 @@ var import_TLSyncRoom = require("./lib/TLSyncRoom");
|
|
|
55
61
|
var import_TLSyncStorage = require("./lib/TLSyncStorage");
|
|
56
62
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
57
63
|
"@tldraw/sync-core",
|
|
58
|
-
"4.3.0-canary.
|
|
64
|
+
"4.3.0-canary.c08047039e53",
|
|
59
65
|
"cjs"
|
|
60
66
|
);
|
|
61
67
|
//# sourceMappingURL=index.js.map
|
package/dist-cjs/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\nexport { chunk, JsonChunkAssembler } from './lib/chunk'\nexport { ClientWebSocketAdapter, ReconnectManager } from './lib/ClientWebSocketAdapter'\nexport {\n\tapplyObjectDiff,\n\tdiffRecord,\n\tgetNetworkDiff,\n\tRecordOpType,\n\tValueOpType,\n\ttype AppendOp,\n\ttype DeleteOp,\n\ttype NetworkDiff,\n\ttype ObjectDiff,\n\ttype PatchOp,\n\ttype PutOp,\n\ttype RecordOp,\n\ttype ValueOp,\n} from './lib/diff'\nexport { DEFAULT_INITIAL_SNAPSHOT, InMemorySyncStorage } from './lib/InMemorySyncStorage'\nexport {\n\tgetTlsyncProtocolVersion,\n\tTLIncompatibilityReason,\n\ttype TLConnectRequest,\n\ttype TLPingRequest,\n\ttype TLPushRequest,\n\ttype TLSocketClientSentEvent,\n\ttype TLSocketServerSentDataEvent,\n\ttype TLSocketServerSentEvent,\n} from './lib/protocol'\nexport { RoomSessionState, type RoomSession, type RoomSessionBase } from './lib/RoomSession'\nexport type { PersistedRoomSnapshotForSupabase } from './lib/server-types'\nexport type { WebSocketMinimal } from './lib/ServerSocketAdapter'\nexport { TLRemoteSyncError } from './lib/TLRemoteSyncError'\nexport {\n\tTLSocketRoom,\n\ttype OmitVoid,\n\ttype RoomStoreMethods,\n\ttype TLSocketRoomOptions,\n\ttype TLSyncLog,\n} from './lib/TLSocketRoom'\nexport {\n\tTLSyncClient,\n\tTLSyncErrorCloseEventCode,\n\tTLSyncErrorCloseEventReason,\n\ttype SubscribingFn,\n\ttype TLCustomMessageHandler,\n\ttype TLPersistentClientSocket,\n\ttype TLPersistentClientSocketStatus,\n\ttype TLPresenceMode,\n\ttype TLSocketStatusChangeEvent,\n\ttype TLSocketStatusListener,\n} from './lib/TLSyncClient'\nexport {\n\tTLSyncRoom,\n\ttype MinimalDocStore,\n\ttype PresenceStore,\n\ttype RoomSnapshot,\n\ttype TLRoomSocket,\n} from './lib/TLSyncRoom'\nexport {\n\tloadSnapshotIntoStorage,\n\ttype TLSyncForwardDiff,\n\ttype TLSyncStorage,\n\ttype TLSyncStorageGetChangesSinceResult,\n\ttype TLSyncStorageOnChangeCallbackProps,\n\ttype TLSyncStorageTransaction,\n\ttype TLSyncStorageTransactionCallback,\n\ttype TLSyncStorageTransactionOptions,\n\ttype TLSyncStorageTransactionResult,\n} from './lib/TLSyncStorage'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAC7C,mBAA0C;AAC1C,oCAAyD;AACzD,kBAcO;AACP,iCAA8D;AAC9D,sBASO;AACP,yBAAyE;AAGzE,+BAAkC;AAClC,0BAMO;AACP,0BAWO;AACP,wBAMO;AACP,2BAUO;AAAA,IAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
|
|
4
|
+
"sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\nexport { chunk, JsonChunkAssembler } from './lib/chunk'\nexport { ClientWebSocketAdapter, ReconnectManager } from './lib/ClientWebSocketAdapter'\nexport {\n\tapplyObjectDiff,\n\tdiffRecord,\n\tgetNetworkDiff,\n\tRecordOpType,\n\tValueOpType,\n\ttype AppendOp,\n\ttype DeleteOp,\n\ttype NetworkDiff,\n\ttype ObjectDiff,\n\ttype PatchOp,\n\ttype PutOp,\n\ttype RecordOp,\n\ttype ValueOp,\n} from './lib/diff'\nexport { DurableObjectSqliteSyncWrapper } from './lib/DurableObjectSqliteSyncWrapper'\nexport { DEFAULT_INITIAL_SNAPSHOT, InMemorySyncStorage } from './lib/InMemorySyncStorage'\nexport { NodeSqliteWrapper, type SyncSqliteDatabase } from './lib/NodeSqliteWrapper'\nexport {\n\tgetTlsyncProtocolVersion,\n\tTLIncompatibilityReason,\n\ttype TLConnectRequest,\n\ttype TLPingRequest,\n\ttype TLPushRequest,\n\ttype TLSocketClientSentEvent,\n\ttype TLSocketServerSentDataEvent,\n\ttype TLSocketServerSentEvent,\n} from './lib/protocol'\nexport { RoomSessionState, type RoomSession, type RoomSessionBase } from './lib/RoomSession'\nexport type { PersistedRoomSnapshotForSupabase } from './lib/server-types'\nexport type { WebSocketMinimal } from './lib/ServerSocketAdapter'\nexport {\n\tSQLiteSyncStorage,\n\ttype TLSqliteInputValue,\n\ttype TLSqliteOutputValue,\n\ttype TLSqliteRow,\n\ttype TLSyncSqliteStatement,\n\ttype TLSyncSqliteWrapper,\n\ttype TLSyncSqliteWrapperConfig,\n} from './lib/SQLiteSyncStorage'\nexport { TLRemoteSyncError } from './lib/TLRemoteSyncError'\nexport {\n\tTLSocketRoom,\n\ttype OmitVoid,\n\ttype RoomStoreMethods,\n\ttype TLSocketRoomOptions,\n\ttype TLSyncLog,\n} from './lib/TLSocketRoom'\nexport {\n\tTLSyncClient,\n\tTLSyncErrorCloseEventCode,\n\tTLSyncErrorCloseEventReason,\n\ttype SubscribingFn,\n\ttype TLCustomMessageHandler,\n\ttype TLPersistentClientSocket,\n\ttype TLPersistentClientSocketStatus,\n\ttype TLPresenceMode,\n\ttype TLSocketStatusChangeEvent,\n\ttype TLSocketStatusListener,\n} from './lib/TLSyncClient'\nexport {\n\tTLSyncRoom,\n\ttype MinimalDocStore,\n\ttype PresenceStore,\n\ttype RoomSnapshot,\n\ttype TLRoomSocket,\n} from './lib/TLSyncRoom'\nexport {\n\tloadSnapshotIntoStorage,\n\ttype TLSyncForwardDiff,\n\ttype TLSyncStorage,\n\ttype TLSyncStorageGetChangesSinceResult,\n\ttype TLSyncStorageOnChangeCallbackProps,\n\ttype TLSyncStorageTransaction,\n\ttype TLSyncStorageTransactionCallback,\n\ttype TLSyncStorageTransactionOptions,\n\ttype TLSyncStorageTransactionResult,\n} from './lib/TLSyncStorage'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAC7C,mBAA0C;AAC1C,oCAAyD;AACzD,kBAcO;AACP,4CAA+C;AAC/C,iCAA8D;AAC9D,+BAA2D;AAC3D,sBASO;AACP,yBAAyE;AAGzE,+BAQO;AACP,+BAAkC;AAClC,0BAMO;AACP,0BAWO;AACP,wBAMO;AACP,2BAUO;AAAA,IAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
}
|
|
@@ -21,15 +21,35 @@ __export(InMemorySyncStorage_exports, {
|
|
|
21
21
|
DEFAULT_INITIAL_SNAPSHOT: () => DEFAULT_INITIAL_SNAPSHOT,
|
|
22
22
|
InMemorySyncStorage: () => InMemorySyncStorage,
|
|
23
23
|
MAX_TOMBSTONES: () => MAX_TOMBSTONES,
|
|
24
|
-
TOMBSTONE_PRUNE_BUFFER_SIZE: () => TOMBSTONE_PRUNE_BUFFER_SIZE
|
|
24
|
+
TOMBSTONE_PRUNE_BUFFER_SIZE: () => TOMBSTONE_PRUNE_BUFFER_SIZE,
|
|
25
|
+
computeTombstonePruning: () => computeTombstonePruning
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(InMemorySyncStorage_exports);
|
|
27
28
|
var import_state = require("@tldraw/state");
|
|
28
29
|
var import_store = require("@tldraw/store");
|
|
29
30
|
var import_tlschema = require("@tldraw/tlschema");
|
|
30
31
|
var import_utils = require("@tldraw/utils");
|
|
32
|
+
var import_MicrotaskNotifier = require("./MicrotaskNotifier");
|
|
31
33
|
const TOMBSTONE_PRUNE_BUFFER_SIZE = 1e3;
|
|
32
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
|
+
}
|
|
33
53
|
const DEFAULT_INITIAL_SNAPSHOT = {
|
|
34
54
|
documentClock: 0,
|
|
35
55
|
tombstoneHistoryStartsAtClock: 0,
|
|
@@ -60,18 +80,9 @@ class InMemorySyncStorage {
|
|
|
60
80
|
documentClock;
|
|
61
81
|
/** @internal */
|
|
62
82
|
tombstoneHistoryStartsAtClock;
|
|
63
|
-
|
|
83
|
+
notifier = new import_MicrotaskNotifier.MicrotaskNotifier();
|
|
64
84
|
onChange(callback) {
|
|
65
|
-
|
|
66
|
-
queueMicrotask(() => {
|
|
67
|
-
if (didDelete) return;
|
|
68
|
-
this.listeners.add(callback);
|
|
69
|
-
});
|
|
70
|
-
return () => {
|
|
71
|
-
if (didDelete) return;
|
|
72
|
-
didDelete = true;
|
|
73
|
-
this.listeners.delete(callback);
|
|
74
|
-
};
|
|
85
|
+
return this.notifier.register(callback);
|
|
75
86
|
}
|
|
76
87
|
constructor({
|
|
77
88
|
snapshot = DEFAULT_INITIAL_SNAPSHOT,
|
|
@@ -137,19 +148,7 @@ class InMemorySyncStorage {
|
|
|
137
148
|
const clockAfter = this.documentClock.get();
|
|
138
149
|
const didChange = clockAfter > clockBefore;
|
|
139
150
|
if (didChange) {
|
|
140
|
-
|
|
141
|
-
const props = {
|
|
142
|
-
id: opts?.id,
|
|
143
|
-
documentClock: clockAfter
|
|
144
|
-
};
|
|
145
|
-
for (const listener of this.listeners) {
|
|
146
|
-
try {
|
|
147
|
-
listener(props);
|
|
148
|
-
} catch (error) {
|
|
149
|
-
console.error("Error in onChange callback", error);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
});
|
|
151
|
+
this.notifier.notify({ id: opts?.id, documentClock: clockAfter });
|
|
153
152
|
}
|
|
154
153
|
return { documentClock: clockAfter, didChange: clockAfter > clockBefore, result, changes };
|
|
155
154
|
}
|
|
@@ -160,16 +159,15 @@ class InMemorySyncStorage {
|
|
|
160
159
|
pruneTombstones = (0, import_utils.throttle)(
|
|
161
160
|
() => {
|
|
162
161
|
if (this.tombstones.size > MAX_TOMBSTONES) {
|
|
163
|
-
const tombstones = Array.from(this.tombstones);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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);
|
|
168
170
|
}
|
|
169
|
-
const oldestRemaining = tombstones[cutoff];
|
|
170
|
-
this.tombstoneHistoryStartsAtClock.set(oldestRemaining?.[1] ?? this.documentClock.get());
|
|
171
|
-
const toDelete = tombstones.slice(0, cutoff);
|
|
172
|
-
this.tombstones.deleteMany(toDelete.map(([id]) => id));
|
|
173
171
|
}
|
|
174
172
|
},
|
|
175
173
|
1e3,
|