@tldraw/sync-core
Version:
tldraw infinite canvas SDK (multiplayer sync).
365 lines (317 loc) • 13.1 kB
text/typescript
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 { }