UNPKG

@tldraw/sync-core

Version:

tldraw infinite canvas SDK (multiplayer sync).

365 lines (317 loc) • 13.1 kB
import { Atom } from '@tldraw/state'; import { Emitter } from 'nanoevents'; import { RecordsDiff } from '@tldraw/store'; import { RecordType } from '@tldraw/store'; import { Result } from '@tldraw/utils'; import { SerializedSchema } from '@tldraw/store'; import { Signal } from '@tldraw/state'; import { Store } from '@tldraw/store'; import { StoreSchema } from '@tldraw/store'; import { TLRecord } from '@tldraw/tlschema'; import { TLStoreSnapshot } from '@tldraw/tlschema'; import { UnknownRecord } from '@tldraw/store'; /* Excluded from this release type: AppendOp */ /* Excluded from this release type: applyObjectDiff */ /* Excluded from this release type: chunk */ /* Excluded from this release type: ClientWebSocketAdapter */ /* Excluded from this release type: DeleteOp */ /* Excluded from this release type: diffRecord */ /* Excluded from this release type: DocumentState */ /* Excluded from this release type: getNetworkDiff */ /* Excluded from this release type: getTlsyncProtocolVersion */ /* Excluded from this release type: NetworkDiff */ /* Excluded from this release type: ObjectDiff */ /** @public */ export declare type OmitVoid<T, KS extends keyof T = keyof T> = { [K in KS extends any ? (void extends T[KS] ? never : KS) : never]: T[K]; }; /* Excluded from this release type: PatchOp */ /* Excluded from this release type: PersistedRoomSnapshotForSupabase */ /* Excluded from this release type: PutOp */ /* Excluded from this release type: ReconnectManager */ /* Excluded from this release type: RecordOp */ /* Excluded from this release type: RecordOpType */ /* Excluded from this release type: RoomSession */ /* Excluded from this release type: RoomSessionState */ /** @public */ export declare interface RoomSnapshot { clock: number; documents: Array<{ lastChangedClock: number; state: UnknownRecord; }>; tombstones?: Record<string, number>; schema?: SerializedSchema; } /** * @public */ export declare interface RoomStoreMethods<R extends UnknownRecord = UnknownRecord> { put(record: R): void; delete(recordOrId: R | string): void; get(id: string): null | R; getAll(): R[]; } /* Excluded from this release type: SubscribingFn */ /* Excluded from this release type: TLConnectRequest */ /* Excluded from this release type: TLIncompatibilityReason */ /* Excluded from this release type: TLPersistentClientSocket */ /* Excluded from this release type: TLPersistentClientSocketStatus */ /* Excluded from this release type: TLPingRequest */ /* Excluded from this release type: TLPushRequest */ /** @public */ export declare class TLRemoteSyncError extends Error { readonly reason: string | TLSyncErrorCloseEventReason; name: string; constructor(reason: string | TLSyncErrorCloseEventReason); } /* Excluded from this release type: TLRoomSocket */ /* Excluded from this release type: TLSocketClientSentEvent */ /** @public */ export declare class TLSocketRoom<R extends UnknownRecord = UnknownRecord, SessionMeta = void> { readonly opts: { /* Excluded from this release type: onPresenceChange */ clientTimeout?: number; initialSnapshot?: RoomSnapshot | TLStoreSnapshot; log?: TLSyncLog; onAfterReceiveMessage?: (args: { /* Excluded from this release type: message */ meta: SessionMeta; sessionId: string; stringified: string; }) => void; onBeforeSendMessage?: (args: { /* Excluded from this release type: message */ meta: SessionMeta; sessionId: string; stringified: string; }) => void; onDataChange?(): void; onSessionRemoved?: (room: TLSocketRoom<R, SessionMeta>, args: { meta: SessionMeta; numSessionsRemaining: number; sessionId: string; }) => void; schema?: StoreSchema<R, any>; }; private room; private readonly sessions; readonly log?: TLSyncLog; private readonly syncCallbacks; constructor(opts: { /* Excluded from this release type: onPresenceChange */ clientTimeout?: number; initialSnapshot?: RoomSnapshot | TLStoreSnapshot; log?: TLSyncLog; onAfterReceiveMessage?: (args: { /* Excluded from this release type: message */ meta: SessionMeta; sessionId: string; stringified: string; }) => void; onBeforeSendMessage?: (args: { /* Excluded from this release type: message */ meta: SessionMeta; sessionId: string; stringified: string; }) => void; onDataChange?(): void; onSessionRemoved?: (room: TLSocketRoom<R, SessionMeta>, args: { meta: SessionMeta; numSessionsRemaining: number; sessionId: string; }) => void; schema?: StoreSchema<R, any>; }); /** * Returns the number of active sessions. * Note that this is not the same as the number of connected sockets! * Sessions time out a few moments after sockets close, to smooth over network hiccups. * * @returns the number of active sessions */ getNumActiveSessions(): number; /** * Call this when a client establishes a new socket connection. * * - `sessionId` is a unique ID for a browser tab. This is passed as a query param by the useSync hook. * - `socket` is a WebSocket-like object that the server uses to communicate with the client. * - `isReadonly` is an optional boolean that can be set to true if the client should not be able to make changes to the document. They will still be able to send presence updates. * - `meta` is an optional object that can be used to store additional information about the session. * * @param opts - The options object */ handleSocketConnect(opts: { isReadonly?: boolean; sessionId: string; socket: WebSocketMinimal; } & (SessionMeta extends void ? object : { meta: SessionMeta; })): void; /** * If executing in a server environment where sockets do not have instance-level listeners * (e.g. Bun.serve, Cloudflare Worker with WebSocket hibernation), you should call this * method when messages are received. See our self-hosting example for Bun.serve for an example. * * @param sessionId - The id of the session. (should match the one used when calling handleSocketConnect) * @param message - The message received from the client. */ handleSocketMessage(sessionId: string, message: AllowSharedBufferSource | string): void; /** * If executing in a server environment where sockets do not have instance-level listeners, * call this when a socket error occurs. * @param sessionId - The id of the session. (should match the one used when calling handleSocketConnect) */ handleSocketError(sessionId: string): void; /** * If executing in a server environment where sockets do not have instance-level listeners, * call this when a socket is closed. * @param sessionId - The id of the session. (should match the one used when calling handleSocketConnect) */ handleSocketClose(sessionId: string): void; /** * Returns the current 'clock' of the document. * The clock is an integer that increments every time the document changes. * The clock is stored as part of the snapshot of the document for consistency purposes. * * @returns The clock */ getCurrentDocumentClock(): number; /** * Returns a deeply cloned record from the store, if available. * @param id - The id of the record * @returns the cloned record */ getRecord(id: string): R; /** * Returns a list of the sessions in the room. */ getSessions(): Array<{ isConnected: boolean; isReadonly: boolean; meta: SessionMeta; sessionId: string; }>; /** * Return a snapshot of the document state, including clock-related bookkeeping. * You can store this and load it later on when initializing a TLSocketRoom. * You can also pass a snapshot to {@link TLSocketRoom#loadSnapshot} if you need to revert to a previous state. * @returns The snapshot */ getCurrentSnapshot(): RoomSnapshot; /* Excluded from this release type: getPresenceRecords */ /* Excluded from this release type: getCurrentSerializedSnapshot */ /** * Load a snapshot of the document state, overwriting the current state. * @param snapshot - The snapshot to load */ loadSnapshot(snapshot: RoomSnapshot | TLStoreSnapshot): void; /** * Allow applying changes to the store inside of a transaction. * * You can get values from the store by id with `store.get(id)`. * These values are safe to mutate, but to commit the changes you must call `store.put(...)` with the updated value. * You can get all values in the store with `store.getAll()`. * You can also delete values with `store.delete(id)`. * * @example * ```ts * room.updateStore(store => { * const shape = store.get('shape:abc123') * shape.meta.approved = true * store.put(shape) * }) * ``` * * Changes to the store inside the callback are isolated from changes made by other clients until the transaction commits. * * @param updater - A function that will be called with a store object that can be used to make changes. * @returns A promise that resolves when the transaction is complete. */ updateStore(updater: (store: RoomStoreMethods<R>) => Promise<void> | void): Promise<void>; /** * Immediately remove a session from the room, and close its socket if not already closed. * * The client will attempt to reconnect unless you provide a `fatalReason` parameter. * * The `fatalReason` parameter will be available in the return value of the `useSync` hook as `useSync().error.reason`. * * @param sessionId - The id of the session to remove * @param fatalReason - The reason message to use when calling .close on the underlying websocket */ closeSession(sessionId: string, fatalReason?: string | TLSyncErrorCloseEventReason): void; /** * Close the room and disconnect all clients. Call this before discarding the room instance or shutting down the server. */ close(): void; /** * @returns true if the room is closed */ isClosed(): boolean; } /* Excluded from this release type: TLSocketServerSentDataEvent */ /* Excluded from this release type: TLSocketServerSentEvent */ /* Excluded from this release type: TlSocketStatusChangeEvent */ /* Excluded from this release type: TLSocketStatusListener */ /* Excluded from this release type: TLSyncClient */ /** * This the close code that we use on the server to signal to a socket that * the connection is being closed because of a non-recoverable error. * * You should use this if you need to close a connection. * * @example * ```ts * socket.close(TLSyncErrorCloseEventCode, TLSyncErrorCloseEventReason.NOT_FOUND) * ``` * * The `reason` parameter that you pass to `socket.close()` will be made available at `useSync().error.reason` * * @public */ export declare const TLSyncErrorCloseEventCode: 4099; /** * The set of reasons that a connection can be closed by the server * @public */ export declare const TLSyncErrorCloseEventReason: { readonly CLIENT_TOO_OLD: "CLIENT_TOO_OLD"; readonly FORBIDDEN: "FORBIDDEN"; readonly INVALID_RECORD: "INVALID_RECORD"; readonly NOT_AUTHENTICATED: "NOT_AUTHENTICATED"; readonly NOT_FOUND: "NOT_FOUND"; readonly RATE_LIMITED: "RATE_LIMITED"; readonly ROOM_FULL: "ROOM_FULL"; readonly SERVER_TOO_OLD: "SERVER_TOO_OLD"; readonly UNKNOWN_ERROR: "UNKNOWN_ERROR"; }; /** * The set of reasons that a connection can be closed by the server * @public */ export declare type TLSyncErrorCloseEventReason = (typeof TLSyncErrorCloseEventReason)[keyof typeof TLSyncErrorCloseEventReason]; /** @public */ export declare interface TLSyncLog { warn?(...args: any[]): void; error?(...args: any[]): void; } /* Excluded from this release type: TLSyncRoom */ /* Excluded from this release type: ValueOp */ /* Excluded from this release type: ValueOpType */ /** * Minimal server-side WebSocket interface that is compatible with * * - The standard WebSocket interface (cloudflare, deno, some node setups) * - The 'ws' WebSocket interface (some node setups) * - The Bun.serve socket implementation * * @public */ export declare interface WebSocketMinimal { addEventListener?: (type: 'close' | 'error' | 'message', listener: (event: any) => void) => void; removeEventListener?: (type: 'close' | 'error' | 'message', listener: (event: any) => void) => void; send: (data: string) => void; close: (code?: number, reason?: string) => void; readyState: number; } export { }