@tldraw/sync-core 5.3.0-internal.d205573d66cb → 5.3.0-next.299378752aaf

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 (50) hide show
  1. package/dist-cjs/index.d.ts +3 -76
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/InMemorySyncStorage.js +20 -55
  5. package/dist-cjs/lib/InMemorySyncStorage.js.map +2 -2
  6. package/dist-cjs/lib/RoomSession.js.map +1 -1
  7. package/dist-cjs/lib/SQLiteSyncStorage.js +52 -115
  8. package/dist-cjs/lib/SQLiteSyncStorage.js.map +2 -2
  9. package/dist-cjs/lib/TLSocketRoom.js +2 -26
  10. package/dist-cjs/lib/TLSocketRoom.js.map +2 -2
  11. package/dist-cjs/lib/TLSyncClient.js +1 -6
  12. package/dist-cjs/lib/TLSyncClient.js.map +2 -2
  13. package/dist-cjs/lib/TLSyncRoom.js +5 -43
  14. package/dist-cjs/lib/TLSyncRoom.js.map +2 -2
  15. package/dist-cjs/lib/TLSyncStorage.js.map +2 -2
  16. package/dist-cjs/lib/protocol.js.map +2 -2
  17. package/dist-esm/index.d.mts +3 -76
  18. package/dist-esm/index.mjs +1 -1
  19. package/dist-esm/index.mjs.map +2 -2
  20. package/dist-esm/lib/InMemorySyncStorage.mjs +20 -55
  21. package/dist-esm/lib/InMemorySyncStorage.mjs.map +2 -2
  22. package/dist-esm/lib/RoomSession.mjs.map +1 -1
  23. package/dist-esm/lib/SQLiteSyncStorage.mjs +52 -115
  24. package/dist-esm/lib/SQLiteSyncStorage.mjs.map +2 -2
  25. package/dist-esm/lib/TLSocketRoom.mjs +2 -26
  26. package/dist-esm/lib/TLSocketRoom.mjs.map +2 -2
  27. package/dist-esm/lib/TLSyncClient.mjs +1 -6
  28. package/dist-esm/lib/TLSyncClient.mjs.map +2 -2
  29. package/dist-esm/lib/TLSyncRoom.mjs +5 -43
  30. package/dist-esm/lib/TLSyncRoom.mjs.map +2 -2
  31. package/dist-esm/lib/TLSyncStorage.mjs.map +2 -2
  32. package/dist-esm/lib/protocol.mjs.map +2 -2
  33. package/package.json +6 -6
  34. package/src/index.ts +0 -1
  35. package/src/lib/InMemorySyncStorage.ts +21 -74
  36. package/src/lib/RoomSession.ts +2 -7
  37. package/src/lib/SQLiteSyncStorage.test.ts +2 -29
  38. package/src/lib/SQLiteSyncStorage.ts +59 -158
  39. package/src/lib/TLSocketRoom.ts +2 -53
  40. package/src/lib/TLSyncClient.test.ts +2 -9
  41. package/src/lib/TLSyncClient.ts +3 -15
  42. package/src/lib/TLSyncRoom.ts +4 -76
  43. package/src/lib/TLSyncStorage.ts +0 -8
  44. package/src/lib/protocol.ts +0 -17
  45. package/src/test/TLSocketRoom.test.ts +2 -37
  46. package/src/test/TLSyncRoom.test.ts +0 -25
  47. package/src/test/TestServer.ts +4 -14
  48. package/src/test/storageContractSuite.ts +0 -79
  49. package/src/test/upgradeDowngrade.test.ts +0 -47
  50. package/src/test/objectStore.test.ts +0 -387
@@ -108,20 +108,13 @@ export declare class DurableObjectSqliteSyncWrapper implements TLSyncSqliteWrapp
108
108
  */
109
109
  export declare class InMemorySyncStorage<R extends UnknownRecord> implements TLSyncStorage<R> {
110
110
  /* Excluded from this release type: documents */
111
- /* Excluded from this release type: objects */
112
- /* Excluded from this release type: objectTypes */
113
111
  /* Excluded from this release type: tombstones */
114
112
  /* Excluded from this release type: schema */
115
113
  /* Excluded from this release type: documentClock */
116
114
  /* Excluded from this release type: tombstoneHistoryStartsAtClock */
117
115
  private notifier;
118
116
  onChange(callback: (arg: TLSyncStorageOnChangeCallbackProps) => unknown): () => void;
119
- constructor({ snapshot, objectTypes, onChange }?: {
120
- /**
121
- * Record type names stored in the object-store lane. Records of these types are routed to
122
- * a separate partition: excluded from `getSnapshot()`, returned by `getObjectsSnapshot()`.
123
- */
124
- objectTypes?: readonly string[];
117
+ constructor({ snapshot, onChange }?: {
125
118
  onChange?(arg: TLSyncStorageOnChangeCallbackProps): unknown;
126
119
  snapshot?: RoomSnapshot;
127
120
  });
@@ -129,7 +122,6 @@ export declare class InMemorySyncStorage<R extends UnknownRecord> implements TLS
129
122
  getClock(): number;
130
123
  /* Excluded from this release type: pruneTombstones */
131
124
  getSnapshot(): RoomSnapshot;
132
- getObjectsSnapshot(): RoomSnapshot['documents'];
133
125
  }
134
126
 
135
127
  /**
@@ -391,11 +383,6 @@ export declare interface RoomStoreMethods<R extends UnknownRecord = UnknownRecor
391
383
  export declare interface SessionStateSnapshot {
392
384
  serializedSchema: SerializedSchema;
393
385
  isReadonly: boolean;
394
- /**
395
- * Write access for the record types listed in `objectTypes`. Optional so snapshots persisted
396
- * before this field existed resume cleanly (they fail closed to 'read').
397
- */
398
- objectAccess?: TLObjectStoreAccess;
399
386
  presenceId: null | string;
400
387
  presenceRecord: null | UnknownRecord;
401
388
  requiresLegacyRejection: boolean;
@@ -451,14 +438,7 @@ export declare class SQLiteSyncStorage<R extends UnknownRecord> implements TLSyn
451
438
  static getDocumentClock(storage: TLSyncSqliteWrapper): null | number;
452
439
  private readonly stmts;
453
440
  private readonly sql;
454
- /* Excluded from this release type: objectTypes */
455
- constructor({ sql, snapshot, objectTypes, onChange }: {
456
- /**
457
- * Record type names stored in the object-store lane. Records of these types are routed to
458
- * a separate table: excluded from `getSnapshot()`, returned by `getObjectsSnapshot()`.
459
- * They share the documents' clock, tombstones, and transactions.
460
- */
461
- objectTypes?: readonly string[];
441
+ constructor({ sql, snapshot, onChange }: {
462
442
  onChange?(arg: TLSyncStorageOnChangeCallbackProps): unknown;
463
443
  snapshot?: RoomSnapshot | StoreSnapshot<R>;
464
444
  sql: TLSyncSqliteWrapper;
@@ -472,8 +452,7 @@ export declare class SQLiteSyncStorage<R extends UnknownRecord> implements TLSyn
472
452
  /* Excluded from this release type: _setSchema */
473
453
  /* Excluded from this release type: pruneTombstones */
474
454
  getSnapshot(): RoomSnapshot;
475
- getObjectsSnapshot(): RoomSnapshot['documents'];
476
- private _iterateRecords;
455
+ private _iterateDocuments;
477
456
  private _iterateTombstones;
478
457
  }
479
458
 
@@ -541,17 +520,6 @@ export declare type TLCustomMessageHandler = (this: null, data: any) => void;
541
520
 
542
521
  /* Excluded from this release type: TLIncompatibilityReason */
543
522
 
544
- /**
545
- * Access level for object-store lane record types (see `objectTypes` on the room).
546
- *
547
- * Object-lane records (e.g. comments) are gated by this per-session value instead of the
548
- * document-lane `isReadonly` flag, so a session can be allowed to write objects without
549
- * being allowed to edit the document ("can comment but not edit"), or vice versa.
550
- *
551
- * @public
552
- */
553
- export declare type TLObjectStoreAccess = 'read' | 'write';
554
-
555
523
  /**
556
524
  * Interface for persistent WebSocket-like connections used by TLSyncClient.
557
525
  * Handles automatic reconnection and provides event-based communication with the sync server.
@@ -789,9 +757,6 @@ export declare class TLSocketRoom<R extends UnknownRecord = UnknownRecord, Sessi
789
757
  * - sessionId - Unique identifier for the client session (typically from browser tab)
790
758
  * - socket - WebSocket-like object for client communication
791
759
  * - isReadonly - Whether the client can modify the document (defaults to false)
792
- * - objectAccess - Write access for object-store lane record types (defaults to 'write').
793
- * Independent of isReadonly, so a session can be allowed to write objects (e.g. comments)
794
- * without being allowed to edit the document.
795
760
  * - meta - Additional session metadata (required if SessionMeta is not void)
796
761
  *
797
762
  * @example
@@ -816,7 +781,6 @@ export declare class TLSocketRoom<R extends UnknownRecord = UnknownRecord, Sessi
816
781
  */
817
782
  handleSocketConnect(opts: {
818
783
  isReadonly?: boolean;
819
- objectAccess?: TLObjectStoreAccess;
820
784
  sessionId: string;
821
785
  socket: WebSocketMinimal;
822
786
  } & (SessionMeta extends void ? object : {
@@ -1000,7 +964,6 @@ export declare class TLSocketRoom<R extends UnknownRecord = UnknownRecord, Sessi
1000
964
  isConnected: boolean;
1001
965
  isReadonly: boolean;
1002
966
  meta: SessionMeta;
1003
- objectAccess: TLObjectStoreAccess;
1004
967
  sessionId: string;
1005
968
  }>;
1006
969
  /**
@@ -1023,12 +986,6 @@ export declare class TLSocketRoom<R extends UnknownRecord = UnknownRecord, Sessi
1023
986
  * ```
1024
987
  */
1025
988
  getCurrentSnapshot(): RoomSnapshot;
1026
- /**
1027
- * Returns a snapshot of the object-store lane (see `objectTypes`), for persisting
1028
- * object-lane records separately from the document. Same entry shape as
1029
- * {@link RoomSnapshot.documents}.
1030
- */
1031
- getCurrentObjectsSnapshot(): RoomSnapshot['documents'];
1032
989
  /* Excluded from this release type: getPresenceRecords */
1033
990
  /**
1034
991
  * Loads a document snapshot, completely replacing the current room state.
@@ -1216,26 +1173,6 @@ export declare interface TLSocketRoomOptions<R extends UnknownRecord, SessionMet
1216
1173
  stringified: string;
1217
1174
  }) => void;
1218
1175
  /* Excluded from this release type: onPresenceChange */
1219
- /**
1220
- * Called once after a client push commits, with the committed document diff. Use to react to
1221
- * document changes as soon as they commit — e.g. project certain record types to an external
1222
- * store. Best-effort: do not throw and do not block.
1223
- *
1224
- * Only fires for client pushes. Server-initiated changes — `updateStore`, `loadSnapshot`, or
1225
- * writing to the storage directly — do not trigger it, so anything mirroring room state through
1226
- * this callback must handle those paths itself.
1227
- */
1228
- onCommittedChanges?: (args: {
1229
- diff: TLSyncForwardDiff<R>;
1230
- documentClock: number;
1231
- }) => void;
1232
- /**
1233
- * Record type names to serve through the object-store lane instead of the document lane.
1234
- * Each must be a document-scoped type registered in the schema. Object-lane writes are gated
1235
- * per session by `objectAccess` (see {@link TLSocketRoom.handleSocketConnect}) rather than
1236
- * `isReadonly`, so e.g. a session can be allowed to comment without being allowed to edit.
1237
- */
1238
- objectTypes?: readonly string[];
1239
1176
  /**
1240
1177
  * When set, the room will call {@link TLSocketRoom.getSessionSnapshot} after
1241
1178
  * no message activity for a session for 5s and pass the result to this callback.
@@ -1390,8 +1327,6 @@ export declare class TLSyncClient<R extends UnknownRecord, S extends Store<R> =
1390
1327
  * @param self - The TLSyncClient instance that connected
1391
1328
  * @param details - Connection details
1392
1329
  * - isReadonly - Whether the connection is in read-only mode
1393
- * - objectAccess - Write access for object-store lane record types (defaults to 'write'
1394
- * when the server doesn't send it, e.g. older servers or rooms with no object lane)
1395
1330
  */
1396
1331
  private readonly onAfterConnect?;
1397
1332
  private readonly onCustomMessageReceived?;
@@ -1419,7 +1354,6 @@ export declare class TLSyncClient<R extends UnknownRecord, S extends Store<R> =
1419
1354
  didCancel?(): boolean;
1420
1355
  onAfterConnect?(self: TLSyncClient<R, S>, details: {
1421
1356
  isReadonly: boolean;
1422
- objectAccess: TLObjectStoreAccess;
1423
1357
  }): void;
1424
1358
  onCustomMessageReceived?: TLCustomMessageHandler;
1425
1359
  onLoad(self: TLSyncClient<R, S>): void;
@@ -1646,14 +1580,7 @@ export declare interface TLSyncStorage<R extends UnknownRecord> {
1646
1580
  transaction<T>(callback: TLSyncStorageTransactionCallback<R, T>, opts?: TLSyncStorageTransactionOptions): TLSyncStorageTransactionResult<T, R>;
1647
1581
  getClock(): number;
1648
1582
  onChange(callback: (arg: TLSyncStorageOnChangeCallbackProps) => unknown): () => void;
1649
- /** A snapshot of the document lane only — never contains object-store lane records. */
1650
1583
  getSnapshot?(): RoomSnapshot;
1651
- /**
1652
- * A snapshot of the object-store lane (see `objectTypes` on the storage), for hosts that
1653
- * persist object-lane records separately from the document. Same entry shape as
1654
- * `RoomSnapshot['documents']` so a lane can be persisted and re-seeded via a merged snapshot.
1655
- */
1656
- getObjectsSnapshot?(): RoomSnapshot['documents'];
1657
1584
  }
1658
1585
 
1659
1586
  /**
package/dist-cjs/index.js CHANGED
@@ -61,7 +61,7 @@ var import_TLSyncRoom = require("./lib/TLSyncRoom");
61
61
  var import_TLSyncStorage = require("./lib/TLSyncStorage");
62
62
  (0, import_utils.registerTldrawLibraryVersion)(
63
63
  "@tldraw/sync-core",
64
- "5.3.0-internal.d205573d66cb",
64
+ "5.3.0-next.299378752aaf",
65
65
  "cjs"
66
66
  );
67
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 { DurableObjectSqliteSyncWrapper } from './lib/DurableObjectSqliteSyncWrapper'\nexport { DEFAULT_INITIAL_SNAPSHOT, InMemorySyncStorage } from './lib/InMemorySyncStorage'\nexport { NodeSqliteWrapper, type SyncSqliteDatabase } from './lib/NodeSqliteWrapper'\nexport {\n\tgetTlsyncProtocolVersion,\n\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\tTLIncompatibilityReason,\n\ttype TLConnectRequest,\n\ttype TLObjectStoreAccess,\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\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\ttype RoomStoreMethods,\n\ttype SessionStateSnapshot,\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,sBAWO;AACP,yBAAyE;AAGzE,+BAQO;AACP,+BAAkC;AAClC,0BAQO;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\t// eslint-disable-next-line @typescript-eslint/no-deprecated\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\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\ttype RoomStoreMethods,\n\ttype SessionStateSnapshot,\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,sBAUO;AACP,yBAAyE;AAGzE,+BAQO;AACP,+BAAkC;AAClC,0BAQO;AACP,0BAWO;AACP,wBAMO;AACP,2BAUO;AAAA,IAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
6
6
  "names": []
7
7
  }
@@ -72,14 +72,6 @@ const DEFAULT_INITIAL_SNAPSHOT = {
72
72
  class InMemorySyncStorage {
73
73
  /** @internal */
74
74
  documents;
75
- /**
76
- * Object-store lane records, partitioned out of `documents` so they never appear in the
77
- * document snapshot. They share the same clock, tombstones, and transactions as documents.
78
- * @internal
79
- */
80
- objects;
81
- /** @internal */
82
- objectTypes;
83
75
  /** @internal */
84
76
  tombstones;
85
77
  /** @internal */
@@ -94,26 +86,19 @@ class InMemorySyncStorage {
94
86
  }
95
87
  constructor({
96
88
  snapshot = DEFAULT_INITIAL_SNAPSHOT,
97
- objectTypes,
98
89
  onChange
99
90
  } = {}) {
100
- this.objectTypes = new Set(objectTypes ?? []);
101
91
  const maxClockValue = Math.max(
102
92
  0,
103
93
  ...Object.values(snapshot.tombstones ?? {}),
104
94
  ...Object.values(snapshot.documents.map((d) => d.lastChangedClock))
105
95
  );
106
- const toEntry = (d) => [
107
- d.state.id,
108
- { state: (0, import_store.devFreeze)(d.state), lastChangedClock: d.lastChangedClock }
109
- ];
110
96
  this.documents = new import_store.AtomMap(
111
97
  "room documents",
112
- snapshot.documents.filter((d) => !this.objectTypes.has(d.state.typeName)).map(toEntry)
113
- );
114
- this.objects = new import_store.AtomMap(
115
- "room objects",
116
- snapshot.documents.filter((d) => this.objectTypes.has(d.state.typeName)).map(toEntry)
98
+ snapshot.documents.map((d) => [
99
+ d.state.id,
100
+ { state: (0, import_store.devFreeze)(d.state), lastChangedClock: d.lastChangedClock }
101
+ ])
117
102
  );
118
103
  const documentClock = Math.max(maxClockValue, snapshot.documentClock ?? snapshot.clock ?? 0);
119
104
  this.documentClock = (0, import_state.atom)("document clock", documentClock);
@@ -198,9 +183,6 @@ class InMemorySyncStorage {
198
183
  schema: this.schema.get()
199
184
  };
200
185
  }
201
- getObjectsSnapshot() {
202
- return Array.from(this.objects.values());
203
- }
204
186
  }
205
187
  class InMemorySyncStorageTransaction {
206
188
  constructor(storage) {
@@ -228,15 +210,9 @@ class InMemorySyncStorageTransaction {
228
210
  }
229
211
  return this._clock;
230
212
  }
231
- /** The partition a record with this id currently lives in, if any. */
232
- mapContaining(id) {
233
- if (this.storage.documents.has(id)) return this.storage.documents;
234
- if (this.storage.objects.has(id)) return this.storage.objects;
235
- return void 0;
236
- }
237
213
  get(id) {
238
214
  this.assertNotClosed();
239
- return (this.storage.documents.get(id) ?? this.storage.objects.get(id))?.state;
215
+ return this.storage.documents.get(id)?.state;
240
216
  }
241
217
  set(id, record) {
242
218
  this.assertNotClosed();
@@ -245,47 +221,38 @@ class InMemorySyncStorageTransaction {
245
221
  if (this.storage.tombstones.has(id)) {
246
222
  this.storage.tombstones.delete(id);
247
223
  }
248
- const map = this.storage.objectTypes.has(record.typeName) ? this.storage.objects : this.storage.documents;
249
- map.set(id, {
224
+ this.storage.documents.set(id, {
250
225
  state: (0, import_store.devFreeze)(record),
251
226
  lastChangedClock: clock
252
227
  });
253
228
  }
254
229
  delete(id) {
255
230
  this.assertNotClosed();
256
- const map = this.mapContaining(id);
257
- if (!map) return;
231
+ if (!this.storage.documents.has(id)) return;
258
232
  const clock = this.getNextClock();
259
- map.delete(id);
233
+ this.storage.documents.delete(id);
260
234
  this.storage.tombstones.set(id, clock);
261
235
  this.storage.pruneTombstones();
262
236
  }
263
- // iteration spans both partitions so schema migrations cover object-lane records too
264
237
  *entries() {
265
238
  this.assertNotClosed();
266
- for (const map of [this.storage.documents, this.storage.objects]) {
267
- for (const [id, record] of map.entries()) {
268
- this.assertNotClosed();
269
- yield [id, record.state];
270
- }
239
+ for (const [id, record] of this.storage.documents.entries()) {
240
+ this.assertNotClosed();
241
+ yield [id, record.state];
271
242
  }
272
243
  }
273
244
  *keys() {
274
245
  this.assertNotClosed();
275
- for (const map of [this.storage.documents, this.storage.objects]) {
276
- for (const key of map.keys()) {
277
- this.assertNotClosed();
278
- yield key;
279
- }
246
+ for (const key of this.storage.documents.keys()) {
247
+ this.assertNotClosed();
248
+ yield key;
280
249
  }
281
250
  }
282
251
  *values() {
283
252
  this.assertNotClosed();
284
- for (const map of [this.storage.documents, this.storage.objects]) {
285
- for (const record of map.values()) {
286
- this.assertNotClosed();
287
- yield record.state;
288
- }
253
+ for (const record of this.storage.documents.values()) {
254
+ this.assertNotClosed();
255
+ yield record.state;
289
256
  }
290
257
  }
291
258
  getSchema() {
@@ -305,11 +272,9 @@ class InMemorySyncStorageTransaction {
305
272
  }
306
273
  const diff = { puts: {}, deletes: [] };
307
274
  const wipeAll = sinceClock < this.storage.tombstoneHistoryStartsAtClock.get();
308
- for (const map of [this.storage.documents, this.storage.objects]) {
309
- for (const doc of map.values()) {
310
- if (wipeAll || doc.lastChangedClock > sinceClock) {
311
- diff.puts[doc.state.id] = doc.state;
312
- }
275
+ for (const doc of this.storage.documents.values()) {
276
+ if (wipeAll || doc.lastChangedClock > sinceClock) {
277
+ diff.puts[doc.state.id] = doc.state;
313
278
  }
314
279
  }
315
280
  if (!wipeAll) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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/**\n\t * Object-store lane records, partitioned out of `documents` so they never appear in the\n\t * document snapshot. They share the same clock, tombstones, and transactions as documents.\n\t * @internal\n\t */\n\tobjects: AtomMap<string, { state: R; lastChangedClock: number }>\n\t/** @internal */\n\treadonly objectTypes: ReadonlySet<string>\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\tobjectTypes,\n\t\tonChange,\n\t}: {\n\t\tsnapshot?: RoomSnapshot\n\t\t/**\n\t\t * Record type names stored in the object-store lane. Records of these types are routed to\n\t\t * a separate partition: excluded from `getSnapshot()`, returned by `getObjectsSnapshot()`.\n\t\t */\n\t\tobjectTypes?: readonly string[]\n\t\tonChange?(arg: TLSyncStorageOnChangeCallbackProps): unknown\n\t} = {}) {\n\t\tthis.objectTypes = new Set(objectTypes ?? [])\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\t// route snapshot entries into their partitions (a seed snapshot may carry object-lane\n\t\t// records merged in, e.g. loaded from a separate persistence lane)\n\t\tconst toEntry = (\n\t\t\td: RoomSnapshot['documents'][number]\n\t\t): [string, { state: R; lastChangedClock: number }] => [\n\t\t\td.state.id,\n\t\t\t{ state: devFreeze(d.state) as R, lastChangedClock: d.lastChangedClock },\n\t\t]\n\t\tthis.documents = new AtomMap(\n\t\t\t'room documents',\n\t\t\tsnapshot.documents.filter((d) => !this.objectTypes.has(d.state.typeName)).map(toEntry)\n\t\t)\n\t\tthis.objects = new AtomMap(\n\t\t\t'room objects',\n\t\t\tsnapshot.documents.filter((d) => this.objectTypes.has(d.state.typeName)).map(toEntry)\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\t// object-lane records live in their own partition, so the document snapshot is\n\t\t// pure-document by construction\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\tgetObjectsSnapshot(): RoomSnapshot['documents'] {\n\t\treturn Array.from(this.objects.values())\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<\n\tR extends UnknownRecord,\n> implements TLSyncStorageTransaction<R> {\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\t/** The partition a record with this id currently lives in, if any. */\n\tprivate mapContaining(id: string) {\n\t\tif (this.storage.documents.has(id)) return this.storage.documents\n\t\tif (this.storage.objects.has(id)) return this.storage.objects\n\t\treturn undefined\n\t}\n\n\tget(id: string): R | undefined {\n\t\tthis.assertNotClosed()\n\t\treturn (this.storage.documents.get(id) ?? this.storage.objects.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\t// route by type: object-lane records live in their own partition\n\t\tconst map = this.storage.objectTypes.has(record.typeName)\n\t\t\t? this.storage.objects\n\t\t\t: this.storage.documents\n\t\tmap.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\tconst map = this.mapContaining(id)\n\t\tif (!map) return\n\t\tconst clock = this.getNextClock()\n\t\tmap.delete(id)\n\t\tthis.storage.tombstones.set(id, clock)\n\t\tthis.storage.pruneTombstones()\n\t}\n\n\t// iteration spans both partitions so schema migrations cover object-lane records too\n\n\t*entries(): IterableIterator<[string, R]> {\n\t\tthis.assertNotClosed()\n\t\tfor (const map of [this.storage.documents, this.storage.objects]) {\n\t\t\tfor (const [id, record] of map.entries()) {\n\t\t\t\tthis.assertNotClosed()\n\t\t\t\tyield [id, record.state]\n\t\t\t}\n\t\t}\n\t}\n\n\t*keys(): IterableIterator<string> {\n\t\tthis.assertNotClosed()\n\t\tfor (const map of [this.storage.documents, this.storage.objects]) {\n\t\t\tfor (const key of map.keys()) {\n\t\t\t\tthis.assertNotClosed()\n\t\t\t\tyield key\n\t\t\t}\n\t\t}\n\t}\n\n\t*values(): IterableIterator<R> {\n\t\tthis.assertNotClosed()\n\t\tfor (const map of [this.storage.documents, this.storage.objects]) {\n\t\t\tfor (const record of map.values()) {\n\t\t\t\tthis.assertNotClosed()\n\t\t\t\tyield record.state\n\t\t\t}\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\t// both partitions share the clock and tombstones, so the change feed spans both\n\t\tfor (const map of [this.storage.documents, this.storage.objects]) {\n\t\t\tfor (const doc of map.values()) {\n\t\t\t\tif (wipeAll || doc.lastChangedClock > sinceClock) {\n\t\t\t\t\t// For historical changes, we don't have \"from\" state, so use added\n\t\t\t\t\tdiff.puts[doc.state.id] = doc.state as R\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!wipeAll) {\n\t\t\tfor (const [id, clock] of this.storage.tombstones.entries()) {\n\t\t\t\tif (clock > sinceClock) {\n\t\t\t\t\t// For tombstones, we don't have the removed record, use placeholder\n\t\t\t\t\tdiff.deletes.push(id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { diff, wipeAll }\n\t}\n}\n"],
5
- "mappings": "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",
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<\n\tR extends UnknownRecord,\n> implements TLSyncStorageTransaction<R> {\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\tif (!wipeAll) {\n\t\t\tfor (const [id, clock] of this.storage.tombstones.entries()) {\n\t\t\t\tif (clock > sinceClock) {\n\t\t\t\t\t// For tombstones, we don't have the removed record, use placeholder\n\t\t\t\t\tdiff.deletes.push(id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { diff, wipeAll }\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["clock"]
7
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 { TLObjectStoreAccess, TLSocketServerSentDataEvent } from './protocol'\nimport { TLRoomSocket } from './TLSyncRoom'\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 for document-lane records */\n\tisReadonly: boolean\n\t/**\n\t * Write access for object-store lane record types (see `objectTypes` on the room).\n\t * Deliberately independent of `isReadonly` so \"can comment but not edit\" is expressible.\n\t */\n\tobjectAccess: TLObjectStoreAccess\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"],
4
+ "sourcesContent": ["import { SerializedSchema, UnknownRecord } from '@tldraw/store'\nimport { TLSocketServerSentDataEvent } from './protocol'\nimport { TLRoomSocket } from './TLSyncRoom'\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
  }