@tldraw/sync-core 4.2.0-canary.ff738678e7e0 → 4.2.0-next.094f21ae35eb

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.
@@ -31,10 +31,16 @@ import {
31
31
  * @param cb - Callback function that receives the event value
32
32
  * @returns Function to call when you want to unsubscribe from the events
33
33
  *
34
- * @internal
34
+ * @public
35
35
  */
36
36
  export type SubscribingFn<T> = (cb: (val: T) => void) => () => void
37
37
 
38
+ /** Network sync frame rate when in solo mode (no collaborators) @internal */
39
+ const SOLO_MODE_FPS = 1
40
+
41
+ /** Network sync frame rate when in collaborative mode (with collaborators) @internal */
42
+ const COLLABORATIVE_MODE_FPS = 30
43
+
38
44
  /**
39
45
  * WebSocket close code used by the server to signal a non-recoverable sync error.
40
46
  * This close code indicates that the connection is being terminated due to an error
@@ -149,9 +155,9 @@ export type TLCustomMessageHandler = (this: null, data: any) => void
149
155
  * Event object describing changes in socket connection status.
150
156
  * Contains either a basic status change or an error with details.
151
157
  *
152
- * @internal
158
+ * @public
153
159
  */
154
- export type TlSocketStatusChangeEvent =
160
+ export type TLSocketStatusChangeEvent =
155
161
  | {
156
162
  /** Connection came online or went offline */
157
163
  status: 'online' | 'offline'
@@ -169,7 +175,7 @@ export type TlSocketStatusChangeEvent =
169
175
  *
170
176
  * @internal
171
177
  */
172
- export type TLSocketStatusListener = (params: TlSocketStatusChangeEvent) => void
178
+ export type TLSocketStatusListener = (params: TLSocketStatusChangeEvent) => void
173
179
 
174
180
  /**
175
181
  * Possible connection states for a persistent client socket.
@@ -183,7 +189,7 @@ export type TLPersistentClientSocketStatus = 'online' | 'offline' | 'error'
183
189
  * Mode for handling presence information in sync sessions.
184
190
  * Controls whether presence data (cursors, selections) is shared with other clients.
185
191
  *
186
- * @internal
192
+ * @public
187
193
  */
188
194
  export type TLPresenceMode =
189
195
  /** No presence sharing - client operates independently */
@@ -217,9 +223,12 @@ export type TLPresenceMode =
217
223
  * }
218
224
  * ```
219
225
  *
220
- * @internal
226
+ * @public
221
227
  */
222
- export interface TLPersistentClientSocket<R extends UnknownRecord = UnknownRecord> {
228
+ export interface TLPersistentClientSocket<
229
+ ClientSentMessage extends object = object,
230
+ ServerSentMessage extends object = object,
231
+ > {
223
232
  /** Current connection state - online means actively connected and ready */
224
233
  connectionStatus: 'online' | 'offline' | 'error'
225
234
 
@@ -227,27 +236,32 @@ export interface TLPersistentClientSocket<R extends UnknownRecord = UnknownRecor
227
236
  * Send a protocol message to the sync server
228
237
  * @param msg - Message to send (connect, push, ping, etc.)
229
238
  */
230
- sendMessage(msg: TLSocketClientSentEvent<R>): void
239
+ sendMessage(msg: ClientSentMessage): void
231
240
 
232
241
  /**
233
242
  * Subscribe to messages received from the server
234
243
  * @param callback - Function called for each received message
235
244
  * @returns Cleanup function to remove the listener
236
245
  */
237
- onReceiveMessage: SubscribingFn<TLSocketServerSentEvent<R>>
246
+ onReceiveMessage: SubscribingFn<ServerSentMessage>
238
247
 
239
248
  /**
240
249
  * Subscribe to connection status changes
241
250
  * @param callback - Function called when connection status changes
242
251
  * @returns Cleanup function to remove the listener
243
252
  */
244
- onStatusChange: SubscribingFn<TlSocketStatusChangeEvent>
253
+ onStatusChange: SubscribingFn<TLSocketStatusChangeEvent>
245
254
 
246
255
  /**
247
256
  * Force a connection restart (disconnect then reconnect)
248
257
  * Used for error recovery or when connection health checks fail
249
258
  */
250
259
  restart(): void
260
+
261
+ /**
262
+ * Close the connection
263
+ */
264
+ close(): void
251
265
  }
252
266
 
253
267
  const PING_INTERVAL = 5000
@@ -312,7 +326,7 @@ const MAX_TIME_TO_WAIT_FOR_SERVER_INTERACTION_BEFORE_RESETTING_CONNECTION = PING
312
326
  * })
313
327
  * ```
314
328
  *
315
- * @internal
329
+ * @public
316
330
  */
317
331
  export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>> {
318
332
  /** The last clock time from the most recent server update */
@@ -335,14 +349,19 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
335
349
 
336
350
  private disposables: Array<() => void> = []
337
351
 
352
+ /** @internal */
338
353
  readonly store: S
339
- readonly socket: TLPersistentClientSocket<R>
354
+ /** @internal */
355
+ readonly socket: TLPersistentClientSocket<TLSocketClientSentEvent<R>, TLSocketServerSentEvent<R>>
340
356
 
357
+ /** @internal */
341
358
  readonly presenceState: Signal<R | null> | undefined
359
+ /** @internal */
342
360
  readonly presenceMode: Signal<TLPresenceMode> | undefined
343
361
 
344
362
  // isOnline is true when we have an open socket connection and we have
345
363
  // established a connection with the server room (i.e. we have received a 'connect' message)
364
+ /** @internal */
346
365
  isConnectedToRoom = false
347
366
 
348
367
  /**
@@ -366,7 +385,7 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
366
385
  * @param details - Connection details
367
386
  * - isReadonly - Whether the connection is in read-only mode
368
387
  */
369
- public readonly onAfterConnect?: (self: this, details: { isReadonly: boolean }) => void
388
+ private readonly onAfterConnect?: (self: this, details: { isReadonly: boolean }) => void
370
389
 
371
390
  private readonly onCustomMessageReceived?: TLCustomMessageHandler
372
391
 
@@ -380,7 +399,7 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
380
399
 
381
400
  private readonly presenceType: R['typeName'] | null
382
401
 
383
- didCancel?: () => boolean
402
+ private didCancel?: () => boolean
384
403
 
385
404
  /**
386
405
  * Creates a new TLSyncClient instance to manage synchronization with a remote server.
@@ -400,7 +419,7 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
400
419
  */
401
420
  constructor(config: {
402
421
  store: S
403
- socket: TLPersistentClientSocket<R>
422
+ socket: TLPersistentClientSocket<any, any>
404
423
  presence: Signal<R | null>
405
424
  presenceMode?: Signal<TLPresenceMode>
406
425
  onLoad(self: TLSyncClient<R, S>): void
@@ -516,6 +535,7 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
516
535
  }
517
536
  }
518
537
 
538
+ /** @internal */
519
539
  latestConnectRequestId: string | null = null
520
540
 
521
541
  /**
@@ -641,7 +661,7 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
641
661
  this.lastServerClock = event.serverClock
642
662
  }
643
663
 
644
- incomingDiffBuffer: TLSocketServerSentDataEvent<R>[] = []
664
+ private incomingDiffBuffer: TLSocketServerSentDataEvent<R>[] = []
645
665
 
646
666
  /** Handle events received from the server */
647
667
  private handleServerEvent(event: TLSocketServerSentEvent<R>) {
@@ -701,7 +721,7 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
701
721
  this.scheduleRebase.cancel?.()
702
722
  }
703
723
 
704
- lastPushedPresenceState: R | null = null
724
+ private lastPushedPresenceState: R | null = null
705
725
 
706
726
  private pushPresence(nextPresence: R | null) {
707
727
  // make sure we push any document changes first
@@ -786,6 +806,11 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
786
806
  this.flushPendingPushRequests()
787
807
  }
788
808
 
809
+ /** Get the target FPS for network operations based on presence mode */
810
+ private getSyncFps(): number {
811
+ return this.presenceMode?.get() === 'solo' ? SOLO_MODE_FPS : COLLABORATIVE_MODE_FPS
812
+ }
813
+
789
814
  /** Send any unsent push requests to the server */
790
815
  private flushPendingPushRequests = fpsThrottle(() => {
791
816
  this.debug('flushing pending push requests', {
@@ -805,7 +830,7 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
805
830
  pendingPushRequest.sent = true
806
831
  }
807
832
  }
808
- })
833
+ }, this.getSyncFps.bind(this))
809
834
 
810
835
  /**
811
836
  * Applies a 'network' diff to the store this does value-based equality checking so that if the
@@ -913,5 +938,5 @@ export class TLSyncClient<R extends UnknownRecord, S extends Store<R> = Store<R>
913
938
  }
914
939
  }
915
940
 
916
- private scheduleRebase = fpsThrottle(this.rebase)
941
+ private scheduleRebase = fpsThrottle(this.rebase, this.getSyncFps.bind(this))
917
942
  }
@@ -62,7 +62,7 @@ export class TestSocketPair<R extends UnknownRecord> {
62
62
  }
63
63
  didReceiveFromClient?: (msg: TLSocketClientSentEvent<R>) => void = undefined
64
64
  clientDisconnected?: () => void = undefined
65
- clientSocket: TLPersistentClientSocket<R> = {
65
+ clientSocket: TLPersistentClientSocket<TLSocketClientSentEvent<R>, TLSocketServerSentEvent<R>> = {
66
66
  connectionStatus: 'offline',
67
67
  onStatusChange: (cb) => {
68
68
  this.callbacks.onStatusChange = cb
@@ -76,7 +76,7 @@ export class TestSocketPair<R extends UnknownRecord> {
76
76
  this.callbacks.onReceiveMessage = null
77
77
  }
78
78
  },
79
- sendMessage: (msg: TLSocketClientSentEvent<R>) => {
79
+ sendMessage: (msg) => {
80
80
  if (this.clientSocket.connectionStatus !== 'online') {
81
81
  throw new Error('trying to send before open')
82
82
  }
@@ -87,6 +87,9 @@ export class TestSocketPair<R extends UnknownRecord> {
87
87
  this.disconnect()
88
88
  this.connect()
89
89
  },
90
+ close: () => {
91
+ this.disconnect()
92
+ },
90
93
  }
91
94
 
92
95
  callbacks = {