@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.
Files changed (84) hide show
  1. package/dist-cjs/index.d.ts +483 -58
  2. package/dist-cjs/index.js +13 -3
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/DurableObjectSqliteSyncWrapper.js +55 -0
  5. package/dist-cjs/lib/DurableObjectSqliteSyncWrapper.js.map +7 -0
  6. package/dist-cjs/lib/InMemorySyncStorage.js +287 -0
  7. package/dist-cjs/lib/InMemorySyncStorage.js.map +7 -0
  8. package/dist-cjs/lib/MicrotaskNotifier.js +50 -0
  9. package/dist-cjs/lib/MicrotaskNotifier.js.map +7 -0
  10. package/dist-cjs/lib/NodeSqliteWrapper.js +48 -0
  11. package/dist-cjs/lib/NodeSqliteWrapper.js.map +7 -0
  12. package/dist-cjs/lib/RoomSession.js.map +1 -1
  13. package/dist-cjs/lib/SQLiteSyncStorage.js +428 -0
  14. package/dist-cjs/lib/SQLiteSyncStorage.js.map +7 -0
  15. package/dist-cjs/lib/TLSocketRoom.js +117 -69
  16. package/dist-cjs/lib/TLSocketRoom.js.map +2 -2
  17. package/dist-cjs/lib/TLSyncClient.js +7 -0
  18. package/dist-cjs/lib/TLSyncClient.js.map +2 -2
  19. package/dist-cjs/lib/TLSyncRoom.js +357 -688
  20. package/dist-cjs/lib/TLSyncRoom.js.map +3 -3
  21. package/dist-cjs/lib/TLSyncStorage.js +76 -0
  22. package/dist-cjs/lib/TLSyncStorage.js.map +7 -0
  23. package/dist-cjs/lib/chunk.js +2 -2
  24. package/dist-cjs/lib/chunk.js.map +1 -1
  25. package/dist-cjs/lib/recordDiff.js +52 -0
  26. package/dist-cjs/lib/recordDiff.js.map +7 -0
  27. package/dist-esm/index.d.mts +483 -58
  28. package/dist-esm/index.mjs +20 -5
  29. package/dist-esm/index.mjs.map +2 -2
  30. package/dist-esm/lib/DurableObjectSqliteSyncWrapper.mjs +35 -0
  31. package/dist-esm/lib/DurableObjectSqliteSyncWrapper.mjs.map +7 -0
  32. package/dist-esm/lib/InMemorySyncStorage.mjs +272 -0
  33. package/dist-esm/lib/InMemorySyncStorage.mjs.map +7 -0
  34. package/dist-esm/lib/MicrotaskNotifier.mjs +30 -0
  35. package/dist-esm/lib/MicrotaskNotifier.mjs.map +7 -0
  36. package/dist-esm/lib/NodeSqliteWrapper.mjs +28 -0
  37. package/dist-esm/lib/NodeSqliteWrapper.mjs.map +7 -0
  38. package/dist-esm/lib/RoomSession.mjs.map +1 -1
  39. package/dist-esm/lib/SQLiteSyncStorage.mjs +414 -0
  40. package/dist-esm/lib/SQLiteSyncStorage.mjs.map +7 -0
  41. package/dist-esm/lib/TLSocketRoom.mjs +121 -70
  42. package/dist-esm/lib/TLSocketRoom.mjs.map +2 -2
  43. package/dist-esm/lib/TLSyncClient.mjs +7 -0
  44. package/dist-esm/lib/TLSyncClient.mjs.map +2 -2
  45. package/dist-esm/lib/TLSyncRoom.mjs +370 -702
  46. package/dist-esm/lib/TLSyncRoom.mjs.map +3 -3
  47. package/dist-esm/lib/TLSyncStorage.mjs +56 -0
  48. package/dist-esm/lib/TLSyncStorage.mjs.map +7 -0
  49. package/dist-esm/lib/chunk.mjs +2 -2
  50. package/dist-esm/lib/chunk.mjs.map +1 -1
  51. package/dist-esm/lib/recordDiff.mjs +32 -0
  52. package/dist-esm/lib/recordDiff.mjs.map +7 -0
  53. package/package.json +12 -11
  54. package/src/index.ts +32 -3
  55. package/src/lib/ClientWebSocketAdapter.test.ts +3 -0
  56. package/src/lib/DurableObjectSqliteSyncWrapper.ts +95 -0
  57. package/src/lib/InMemorySyncStorage.ts +387 -0
  58. package/src/lib/MicrotaskNotifier.test.ts +429 -0
  59. package/src/lib/MicrotaskNotifier.ts +38 -0
  60. package/src/lib/NodeSqliteSyncWrapper.integration.test.ts +270 -0
  61. package/src/lib/NodeSqliteSyncWrapper.test.ts +272 -0
  62. package/src/lib/NodeSqliteWrapper.ts +99 -0
  63. package/src/lib/RoomSession.test.ts +1 -0
  64. package/src/lib/RoomSession.ts +2 -0
  65. package/src/lib/SQLiteSyncStorage.ts +627 -0
  66. package/src/lib/TLSocketRoom.ts +228 -114
  67. package/src/lib/TLSyncClient.ts +12 -0
  68. package/src/lib/TLSyncRoom.ts +473 -913
  69. package/src/lib/TLSyncStorage.ts +216 -0
  70. package/src/lib/chunk.ts +2 -2
  71. package/src/lib/computeTombstonePruning.test.ts +352 -0
  72. package/src/lib/recordDiff.ts +73 -0
  73. package/src/test/FuzzEditor.ts +4 -5
  74. package/src/test/InMemorySyncStorage.test.ts +1684 -0
  75. package/src/test/SQLiteSyncStorage.test.ts +1378 -0
  76. package/src/test/TLSocketRoom.test.ts +255 -49
  77. package/src/test/TLSyncRoom.test.ts +1024 -534
  78. package/src/test/TestServer.ts +12 -1
  79. package/src/test/customMessages.test.ts +1 -1
  80. package/src/test/presenceMode.test.ts +6 -6
  81. package/src/test/syncFuzz.test.ts +2 -4
  82. package/src/test/upgradeDowngrade.test.ts +290 -8
  83. package/src/test/validation.test.ts +15 -10
  84. package/src/test/pruneTombstones.test.ts +0 -178
package/dist-cjs/index.js CHANGED
@@ -19,11 +19,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var index_exports = {};
20
20
  __export(index_exports, {
21
21
  ClientWebSocketAdapter: () => import_ClientWebSocketAdapter.ClientWebSocketAdapter,
22
- DocumentState: () => import_TLSyncRoom.DocumentState,
22
+ DEFAULT_INITIAL_SNAPSHOT: () => import_InMemorySyncStorage.DEFAULT_INITIAL_SNAPSHOT,
23
+ DurableObjectSqliteSyncWrapper: () => import_DurableObjectSqliteSyncWrapper.DurableObjectSqliteSyncWrapper,
24
+ InMemorySyncStorage: () => import_InMemorySyncStorage.InMemorySyncStorage,
23
25
  JsonChunkAssembler: () => import_chunk.JsonChunkAssembler,
26
+ NodeSqliteWrapper: () => import_NodeSqliteWrapper.NodeSqliteWrapper,
24
27
  ReconnectManager: () => import_ClientWebSocketAdapter.ReconnectManager,
25
28
  RecordOpType: () => import_diff.RecordOpType,
26
29
  RoomSessionState: () => import_RoomSession.RoomSessionState,
30
+ SQLiteSyncStorage: () => import_SQLiteSyncStorage.SQLiteSyncStorage,
27
31
  TLIncompatibilityReason: () => import_protocol.TLIncompatibilityReason,
28
32
  TLRemoteSyncError: () => import_TLRemoteSyncError.TLRemoteSyncError,
29
33
  TLSocketRoom: () => import_TLSocketRoom.TLSocketRoom,
@@ -36,22 +40,28 @@ __export(index_exports, {
36
40
  chunk: () => import_chunk.chunk,
37
41
  diffRecord: () => import_diff.diffRecord,
38
42
  getNetworkDiff: () => import_diff.getNetworkDiff,
39
- getTlsyncProtocolVersion: () => import_protocol.getTlsyncProtocolVersion
43
+ getTlsyncProtocolVersion: () => import_protocol.getTlsyncProtocolVersion,
44
+ loadSnapshotIntoStorage: () => import_TLSyncStorage.loadSnapshotIntoStorage
40
45
  });
41
46
  module.exports = __toCommonJS(index_exports);
42
47
  var import_utils = require("@tldraw/utils");
43
48
  var import_chunk = require("./lib/chunk");
44
49
  var import_ClientWebSocketAdapter = require("./lib/ClientWebSocketAdapter");
45
50
  var import_diff = require("./lib/diff");
51
+ var import_DurableObjectSqliteSyncWrapper = require("./lib/DurableObjectSqliteSyncWrapper");
52
+ var import_InMemorySyncStorage = require("./lib/InMemorySyncStorage");
53
+ var import_NodeSqliteWrapper = require("./lib/NodeSqliteWrapper");
46
54
  var import_protocol = require("./lib/protocol");
47
55
  var import_RoomSession = require("./lib/RoomSession");
56
+ var import_SQLiteSyncStorage = require("./lib/SQLiteSyncStorage");
48
57
  var import_TLRemoteSyncError = require("./lib/TLRemoteSyncError");
49
58
  var import_TLSocketRoom = require("./lib/TLSocketRoom");
50
59
  var import_TLSyncClient = require("./lib/TLSyncClient");
51
60
  var import_TLSyncRoom = require("./lib/TLSyncRoom");
61
+ var import_TLSyncStorage = require("./lib/TLSyncStorage");
52
62
  (0, import_utils.registerTldrawLibraryVersion)(
53
63
  "@tldraw/sync-core",
54
- "4.2.1",
64
+ "4.2.2",
55
65
  "cjs"
56
66
  );
57
67
  //# sourceMappingURL=index.js.map
@@ -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 {\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 { TLSocketRoom, type OmitVoid, type TLSyncLog } 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\tDocumentState,\n\tTLSyncRoom,\n\ttype RoomSnapshot,\n\ttype RoomStoreMethods,\n\ttype TLRoomSocket,\n} from './lib/TLSyncRoom'\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,mBAA6C;AAC7C,mBAA0C;AAC1C,oCAAyD;AACzD,kBAcO;AACP,sBASO;AACP,yBAAyE;AAGzE,+BAAkC;AAClC,0BAA4D;AAC5D,0BAWO;AACP,wBAMO;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
+ }
@@ -0,0 +1,287 @@
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
@@ -0,0 +1,7 @@
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
+ }
@@ -0,0 +1,50 @@
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
@@ -0,0 +1,7 @@
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
+ }
@@ -0,0 +1,48 @@
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
@@ -0,0 +1,7 @@
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
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/RoomSession.ts"],
4
- "sourcesContent": ["import { SerializedSchema, UnknownRecord } from '@tldraw/store'\nimport { TLRoomSocket } from './TLSyncRoom'\nimport { TLSocketServerSentDataEvent } from './protocol'\n\n/**\n * Enumeration of possible states for a room session during its lifecycle.\n *\n * Room sessions progress through these states as clients connect, authenticate,\n * and disconnect from collaborative rooms.\n *\n * @internal\n */\nexport const RoomSessionState = {\n\t/** Session is waiting for the initial connect message from the client */\n\tAwaitingConnectMessage: 'awaiting-connect-message',\n\t/** Session is disconnected but waiting for final cleanup before removal */\n\tAwaitingRemoval: 'awaiting-removal',\n\t/** Session is fully connected and actively synchronizing */\n\tConnected: 'connected',\n} as const\n\n/**\n * Type representing the possible states a room session can be in.\n *\n * @example\n * ```ts\n * const sessionState: RoomSessionState = RoomSessionState.Connected\n * if (sessionState === RoomSessionState.AwaitingConnectMessage) {\n * console.log('Session waiting for connect message')\n * }\n * ```\n *\n * @internal\n */\nexport type RoomSessionState = (typeof RoomSessionState)[keyof typeof RoomSessionState]\n\n/**\n * Maximum time in milliseconds to wait for a connect message after socket connection.\n *\n * If a client connects but doesn't send a connect message within this time,\n * the session will be terminated.\n *\n * @public\n */\nexport const SESSION_START_WAIT_TIME = 10000\n\n/**\n * Time in milliseconds to wait before completely removing a disconnected session.\n *\n * This grace period allows for quick reconnections without losing session state,\n * which is especially helpful for brief network interruptions.\n *\n * @public\n */\nexport const SESSION_REMOVAL_WAIT_TIME = 5000\n\n/**\n * Maximum time in milliseconds a connected session can remain idle before cleanup.\n *\n * Sessions that don't receive any messages or interactions for this duration\n * may be considered for cleanup to free server resources.\n *\n * @public\n */\nexport const SESSION_IDLE_TIMEOUT = 20000\n\n/**\n * Base properties shared by all room session states.\n *\n * @internal\n */\nexport interface RoomSessionBase<R extends UnknownRecord, Meta> {\n\t/** Unique identifier for this session */\n\tsessionId: string\n\t/** Presence identifier for live cursor/selection tracking, if available */\n\tpresenceId: string | null\n\t/** WebSocket connection wrapper for this session */\n\tsocket: TLRoomSocket<R>\n\t/** Custom metadata associated with this session */\n\tmeta: Meta\n\t/** Whether this session has read-only permissions */\n\tisReadonly: boolean\n\t/** Whether this session requires legacy protocol rejection handling */\n\trequiresLegacyRejection: boolean\n\t/** Whether this session supports string append operations */\n\tsupportsStringAppend: boolean\n}\n\n/**\n * Represents a client session within a collaborative room, tracking the connection\n * state, permissions, and synchronization details for a single user.\n *\n * Each session corresponds to one WebSocket connection and progresses through\n * different states during its lifecycle. The session type is a discriminated union\n * based on the current state, ensuring type safety when accessing state-specific properties.\n *\n * @example\n * ```ts\n * // Check session state and access appropriate properties\n * function handleSession(session: RoomSession<MyRecord, UserMeta>) {\n * switch (session.state) {\n * case RoomSessionState.AwaitingConnectMessage:\n * console.log(`Session ${session.sessionId} started at ${session.sessionStartTime}`)\n * break\n * case RoomSessionState.Connected:\n * console.log(`Connected session has ${session.outstandingDataMessages.length} pending messages`)\n * break\n * case RoomSessionState.AwaitingRemoval:\n * console.log(`Session will be removed at ${session.cancellationTime}`)\n * break\n * }\n * }\n * ```\n *\n * @internal\n */\nexport type RoomSession<R extends UnknownRecord, Meta> =\n\t| (RoomSessionBase<R, Meta> & {\n\t\t\t/** Current state of the session */\n\t\t\tstate: typeof RoomSessionState.AwaitingConnectMessage\n\t\t\t/** Timestamp when the session was created */\n\t\t\tsessionStartTime: number\n\t })\n\t| (RoomSessionBase<R, Meta> & {\n\t\t\t/** Current state of the session */\n\t\t\tstate: typeof RoomSessionState.AwaitingRemoval\n\t\t\t/** Timestamp when the session was marked for removal */\n\t\t\tcancellationTime: number\n\t })\n\t| (RoomSessionBase<R, Meta> & {\n\t\t\t/** Current state of the session */\n\t\t\tstate: typeof RoomSessionState.Connected\n\t\t\t/** Serialized schema information for this connected session */\n\t\t\tserializedSchema: SerializedSchema\n\t\t\t/** Timestamp of the last interaction or message from this session */\n\t\t\tlastInteractionTime: number\n\t\t\t/** Timer for debouncing operations, if active */\n\t\t\tdebounceTimer: ReturnType<typeof setTimeout> | null\n\t\t\t/** Queue of data messages waiting to be sent to this session */\n\t\t\toutstandingDataMessages: TLSocketServerSentDataEvent<R>[]\n\t })\n"],
4
+ "sourcesContent": ["import { SerializedSchema, UnknownRecord } from '@tldraw/store'\nimport { TLRoomSocket } from './TLSyncRoom'\nimport { TLSocketServerSentDataEvent } from './protocol'\n\n/**\n * Enumeration of possible states for a room session during its lifecycle.\n *\n * Room sessions progress through these states as clients connect, authenticate,\n * and disconnect from collaborative rooms.\n *\n * @internal\n */\nexport const RoomSessionState = {\n\t/** Session is waiting for the initial connect message from the client */\n\tAwaitingConnectMessage: 'awaiting-connect-message',\n\t/** Session is disconnected but waiting for final cleanup before removal */\n\tAwaitingRemoval: 'awaiting-removal',\n\t/** Session is fully connected and actively synchronizing */\n\tConnected: 'connected',\n} as const\n\n/**\n * Type representing the possible states a room session can be in.\n *\n * @example\n * ```ts\n * const sessionState: RoomSessionState = RoomSessionState.Connected\n * if (sessionState === RoomSessionState.AwaitingConnectMessage) {\n * console.log('Session waiting for connect message')\n * }\n * ```\n *\n * @internal\n */\nexport type RoomSessionState = (typeof RoomSessionState)[keyof typeof RoomSessionState]\n\n/**\n * Maximum time in milliseconds to wait for a connect message after socket connection.\n *\n * If a client connects but doesn't send a connect message within this time,\n * the session will be terminated.\n *\n * @public\n */\nexport const SESSION_START_WAIT_TIME = 10000\n\n/**\n * Time in milliseconds to wait before completely removing a disconnected session.\n *\n * This grace period allows for quick reconnections without losing session state,\n * which is especially helpful for brief network interruptions.\n *\n * @public\n */\nexport const SESSION_REMOVAL_WAIT_TIME = 5000\n\n/**\n * Maximum time in milliseconds a connected session can remain idle before cleanup.\n *\n * Sessions that don't receive any messages or interactions for this duration\n * may be considered for cleanup to free server resources.\n *\n * @public\n */\nexport const SESSION_IDLE_TIMEOUT = 20000\n\n/**\n * Base properties shared by all room session states.\n *\n * @internal\n */\nexport interface RoomSessionBase<R extends UnknownRecord, Meta> {\n\t/** Unique identifier for this session */\n\tsessionId: string\n\t/** Presence identifier for live cursor/selection tracking, if available */\n\tpresenceId: string | null\n\t/** WebSocket connection wrapper for this session */\n\tsocket: TLRoomSocket<R>\n\t/** Custom metadata associated with this session */\n\tmeta: Meta\n\t/** Whether this session has read-only permissions */\n\tisReadonly: boolean\n\t/** Whether this session requires legacy protocol rejection handling */\n\trequiresLegacyRejection: boolean\n\t/** Whether this session supports string append operations */\n\tsupportsStringAppend: boolean\n}\n\n/**\n * Represents a client session within a collaborative room, tracking the connection\n * state, permissions, and synchronization details for a single user.\n *\n * Each session corresponds to one WebSocket connection and progresses through\n * different states during its lifecycle. The session type is a discriminated union\n * based on the current state, ensuring type safety when accessing state-specific properties.\n *\n * @example\n * ```ts\n * // Check session state and access appropriate properties\n * function handleSession(session: RoomSession<MyRecord, UserMeta>) {\n * switch (session.state) {\n * case RoomSessionState.AwaitingConnectMessage:\n * console.log(`Session ${session.sessionId} started at ${session.sessionStartTime}`)\n * break\n * case RoomSessionState.Connected:\n * console.log(`Connected session has ${session.outstandingDataMessages.length} pending messages`)\n * break\n * case RoomSessionState.AwaitingRemoval:\n * console.log(`Session will be removed at ${session.cancellationTime}`)\n * break\n * }\n * }\n * ```\n *\n * @internal\n */\nexport type RoomSession<R extends UnknownRecord, Meta> =\n\t| (RoomSessionBase<R, Meta> & {\n\t\t\t/** Current state of the session */\n\t\t\tstate: typeof RoomSessionState.AwaitingConnectMessage\n\t\t\t/** Timestamp when the session was created */\n\t\t\tsessionStartTime: number\n\t })\n\t| (RoomSessionBase<R, Meta> & {\n\t\t\t/** Current state of the session */\n\t\t\tstate: typeof RoomSessionState.AwaitingRemoval\n\t\t\t/** Timestamp when the session was marked for removal */\n\t\t\tcancellationTime: number\n\t })\n\t| (RoomSessionBase<R, Meta> & {\n\t\t\t/** Current state of the session */\n\t\t\tstate: typeof RoomSessionState.Connected\n\t\t\t/** Serialized schema information for this connected session */\n\t\t\tserializedSchema: SerializedSchema\n\t\t\t/** Whether this session requires down migrations */\n\t\t\trequiresDownMigrations: boolean\n\t\t\t/** Timestamp of the last interaction or message from this session */\n\t\t\tlastInteractionTime: number\n\t\t\t/** Timer for debouncing operations, if active */\n\t\t\tdebounceTimer: ReturnType<typeof setTimeout> | null\n\t\t\t/** Queue of data messages waiting to be sent to this session */\n\t\t\toutstandingDataMessages: TLSocketServerSentDataEvent<R>[]\n\t })\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYO,MAAM,mBAAmB;AAAA;AAAA,EAE/B,wBAAwB;AAAA;AAAA,EAExB,iBAAiB;AAAA;AAAA,EAEjB,WAAW;AACZ;AAyBO,MAAM,0BAA0B;AAUhC,MAAM,4BAA4B;AAUlC,MAAM,uBAAuB;",
6
6
  "names": []
7
7
  }