import { Atom } from '@tldraw/state'; import { AtomMap } from '@tldraw/store'; import { DebouncedFunc } from 'lodash'; import { DebouncedFuncLeading } from 'lodash'; import { Emitter } from 'nanoevents'; import { RecordsDiff } from '@tldraw/store'; import { RecordType } from '@tldraw/store'; import { SerializedSchema } from '@tldraw/store'; import { SerializedSchemaV2 } from 'tldraw'; import { Signal } from '@tldraw/state'; import { Store } from '@tldraw/store'; import { StoreSchema } from '@tldraw/store'; import { StoreSnapshot } from '@tldraw/store'; import { SynchronousStorage } from '@tldraw/store'; import { TLDocument } from 'tldraw'; import { TLPage } from 'tldraw'; 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 */ /** * Default initial snapshot for a new room. * @public */ export declare const DEFAULT_INITIAL_SNAPSHOT: { documentClock: number; documents: ({ lastChangedClock: number; state: TLDocument; } | { lastChangedClock: number; state: TLPage; })[]; schema: SerializedSchemaV2; tombstoneHistoryStartsAtClock: number; }; /* Excluded from this release type: DeleteOp */ /* Excluded from this release type: diffRecord */ /** * A wrapper around Cloudflare Durable Object's SqlStorage that implements TLSyncSqliteWrapper. * * Use this wrapper with SQLiteSyncStorage to persist tldraw sync state using * Cloudflare Durable Object's built-in SQLite storage. This provides automatic * persistence that survives Durable Object hibernation and restarts. * * @example * ```ts * import { SQLiteSyncStorage, DurableObjectSqliteSyncWrapper } from '@tldraw/sync-core' * * // In your Durable Object class: * class MyDurableObject extends DurableObject { * private storage: SQLiteSyncStorage * * constructor(ctx: DurableObjectState, env: Env) { * super(ctx, env) * const sql = new DurableObjectSqliteSyncWrapper(ctx.storage) * this.storage = new SQLiteSyncStorage({ sql }) * } * } * ``` * * @example * ```ts * // With table prefix to avoid conflicts with other tables * const sql = new DurableObjectSqliteSyncWrapper(this.ctx.storage, { tablePrefix: 'tldraw_' }) * // Creates tables: tldraw_documents, tldraw_tombstones, tldraw_metadata * ``` * * @public */ export declare class DurableObjectSqliteSyncWrapper implements TLSyncSqliteWrapper { private storage; config?: TLSyncSqliteWrapperConfig | undefined; constructor(storage: { sql: { exec(sql: string, ...bindings: unknown[]): Iterable & { toArray(): any[]; }; }; transactionSync(callback: () => any): any; }, config?: TLSyncSqliteWrapperConfig | undefined); exec(sql: string): void; prepare(sql: string): TLSyncSqliteStatement; transaction(callback: () => T): T; } /* Excluded from this release type: getNetworkDiff */ /* Excluded from this release type: getTlsyncProtocolVersion */ /** * In-memory implementation of TLSyncStorage using AtomMap for documents and tombstones, * and atoms for clock values. This is the default storage implementation used by TLSyncRoom. * * @public */ export declare class InMemorySyncStorage implements TLSyncStorage { /* Excluded from this release type: documents */ /* Excluded from this release type: objects */ /* Excluded from this release type: objectTypes */ /* Excluded from this release type: tombstones */ /* Excluded from this release type: schema */ /* Excluded from this release type: documentClock */ /* Excluded from this release type: tombstoneHistoryStartsAtClock */ private notifier; onChange(callback: (arg: TLSyncStorageOnChangeCallbackProps) => unknown): () => void; constructor({ snapshot, objectTypes, onChange, }?: { /** * Record type names stored in the object-store lane. Records of these types are routed to * a separate partition: excluded from `getSnapshot()`, returned by `getObjectsSnapshot()`. */ objectTypes?: readonly string[]; onChange?(arg: TLSyncStorageOnChangeCallbackProps): unknown; snapshot?: RoomSnapshot; }); transaction(callback: TLSyncStorageTransactionCallback, opts?: TLSyncStorageTransactionOptions): TLSyncStorageTransactionResult; getClock(): number; /* Excluded from this release type: pruneTombstones */ getSnapshot(): RoomSnapshot; getObjectsSnapshot(): RoomSnapshot['documents']; getObjectsByIds(ids: Iterable): RoomSnapshot['documents']; } /** * Assembles chunked JSON messages back into complete objects. * Handles both regular JSON messages and chunked messages created by the chunk() function. * Maintains internal state to track partially received chunked messages. * * @example * ```ts * const assembler = new JsonChunkAssembler() * * // Handle regular JSON message * const result1 = assembler.handleMessage('{"hello": "world"}') * // Returns: { data: { hello: "world" }, stringified: '{"hello": "world"}' } * * // Handle chunked message * assembler.handleMessage('1_hello') // Returns: null (partial) * const result2 = assembler.handleMessage('0_ world') * // Returns: { data: "hello world", stringified: "hello world" } * ``` * * @public */ export declare class JsonChunkAssembler { /** * Current assembly state - either 'idle' or tracking chunks being received */ state: 'idle' | { charsReceived: number; chunksReceived: string[]; totalChunks: number; }; /** * Processes a single message, which can be either a complete JSON object or a chunk. * For complete JSON objects (starting with '\{'), parses immediately. * For chunks (prefixed with "\{number\}_"), accumulates until all chunks received. * * @param msg - The message to process, either JSON or chunk format * @returns Result object with data/stringified on success, error object on failure, or null for incomplete chunks * - `\{ data: object, stringified: string \}` - Successfully parsed complete message * - `\{ error: Error \}` - Parse error, invalid chunk sequence, or a message exceeding * MAX_ASSEMBLED_MESSAGE_CHARS / MAX_CHUNK_COUNT * - `null` - Chunk received but more chunks expected * * @example * ```ts * const assembler = new JsonChunkAssembler() * * // Complete JSON message * const result = assembler.handleMessage('{"key": "value"}') * if (result && 'data' in result) { * console.log(result.data) // { key: "value" } * } * * // Chunked message sequence * assembler.handleMessage('2_hel') // null - more chunks expected * assembler.handleMessage('1_lo ') // null - more chunks expected * assembler.handleMessage('0_wor') // { data: "hello wor", stringified: "hello wor" } * ``` */ handleMessage(msg: string): { data: object; stringified: string; } | { error: Error; } | null; } /** * Loads a snapshot into storage during a transaction. * Migrates the snapshot to the current schema and loads it into storage. * * @public * @param txn - The transaction to load the snapshot into * @param schema - The current schema * @param snapshot - The snapshot to load */ export declare function loadSnapshotIntoStorage(txn: TLSyncStorageTransaction, schema: StoreSchema, snapshot: RoomSnapshot | TLStoreSnapshot): void; /* Excluded from this release type: MinimalDocStore */ /* Excluded from this release type: NetworkDiff */ /** * A wrapper around synchronous SQLite databases that implements TLSyncSqliteWrapper. * Works with both `node:sqlite` DatabaseSync (Node.js 22.5+) and `better-sqlite3` Database. * * Use this wrapper with SQLiteSyncStorage to persist tldraw sync state to a SQLite database * in Node.js environments. * * @example * ```ts * // With node:sqlite (Node.js 22.5+) * import { DatabaseSync } from 'node:sqlite' * import { SQLiteSyncStorage, NodeSqliteWrapper } from '@tldraw/sync-core' * * const db = new DatabaseSync(':memory:') * const sql = new NodeSqliteWrapper(db) * const storage = new SQLiteSyncStorage({ sql }) * ``` * * @example * ```ts * // With better-sqlite3 * import Database from 'better-sqlite3' * import { SQLiteSyncStorage, NodeSqliteWrapper } from '@tldraw/sync-core' * * const db = new Database(':memory:') * const sql = new NodeSqliteWrapper(db) * const storage = new SQLiteSyncStorage({ sql }) * ``` * * @example * ```ts * // With table prefix to avoid conflicts with other tables * const sql = new NodeSqliteWrapper(db, { tablePrefix: 'tldraw_' }) * // Creates tables: tldraw_documents, tldraw_tombstones, tldraw_metadata * ``` * * @public */ export declare class NodeSqliteWrapper implements TLSyncSqliteWrapper { private db; config?: TLSyncSqliteWrapperConfig | undefined; constructor(db: SyncSqliteDatabase, config?: TLSyncSqliteWrapperConfig | undefined); exec(sql: string): void; prepare(sql: string): TLSyncSqliteStatement; transaction(callback: () => T): T; } /* Excluded from this release type: ObjectDiff */ /** * Utility type that removes properties with void values from an object type. * This is used internally to conditionally require session metadata based on * whether SessionMeta extends void. * * @example * ```ts * type Example = { a: string, b: void, c: number } * type Result = OmitVoid // { a: string, c: number } * ``` * * @public */ export declare type OmitVoid = { [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: PresenceStore */ /* 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: RoomSessionBase */ /* Excluded from this release type: RoomSessionState */ /** * Snapshot of a room's complete state that can be persisted and restored. * Contains all documents, tombstones, and metadata needed to reconstruct the room. * * @public */ export declare interface RoomSnapshot { /** * The current logical clock value for the room */ clock?: number; /** * Clock value when document data was last changed (optional for backwards compatibility) */ documentClock?: number; /** * Array of all document records with their last modification clocks */ documents: Array<{ lastChangedClock: number; state: UnknownRecord; }>; /** * Map of deleted record IDs to their deletion clock values (optional) */ tombstones?: Record; /** * Clock value where tombstone history begins - older deletions are not tracked (optional) */ tombstoneHistoryStartsAtClock?: number; /** * Serialized schema used when creating this snapshot (optional) */ schema?: SerializedSchema; } /** * Interface for making transactional changes to room store data. Used within * updateStore transactions to modify documents atomically. * * @example * ```ts * await room.updateStore((store) => { * const shape = store.get('shape:123') * if (shape) { * store.put({ ...shape, x: shape.x + 10 }) * } * store.delete('shape:456') * }) * ``` * * @public * @deprecated use the storage.transaction method instead */ export declare interface RoomStoreMethods { /** * Add or update a record in the store. * * @param record - The record to store */ put(record: R): void; /** * Delete a record from the store. * * @param recordOrId - The record or record ID to delete */ delete(recordOrId: R | string): void; /** * Get a record by its ID. * * @param id - The record ID * @returns The record or null if not found */ get(id: string): null | R; /** * Get all records in the store. * * @returns Array of all records */ getAll(): R[]; } /** * A snapshot of per-session state that can be persisted and used to resume a session * after the server restarts (e.g., after Cloudflare Durable Object hibernation). * * Obtain via {@link TLSocketRoom.getSessionSnapshot} and restore via * {@link TLSocketRoom.handleSocketResume}. * * @public */ export declare interface SessionStateSnapshot { serializedSchema: SerializedSchema; isReadonly: boolean; /** * Write access for the record types listed in `objectTypes`. Optional so snapshots persisted * before this field existed resume cleanly (they fail closed to 'read'). */ objectAccess?: TLObjectStoreAccess; presenceId: null | string; presenceRecord: null | UnknownRecord; requiresLegacyRejection: boolean; supportsStringAppend: boolean; } /** * SQLite-based implementation of TLSyncStorage. * Stores documents, tombstones, metadata, and clock values in SQLite tables. * * This storage backend provides persistent synchronization state that survives * process restarts, unlike InMemorySyncStorage which loses data when the process ends. * * @example * ```ts * // With Cloudflare Durable Objects * import { SQLiteSyncStorage, DurableObjectSqliteSyncWrapper } from '@tldraw/sync-core' * * const sql = new DurableObjectSqliteSyncWrapper(this.ctx.storage) * const storage = new SQLiteSyncStorage({ sql }) * ``` * * @example * ```ts * // With Node.js sqlite (Node 22.5+) * import { DatabaseSync } from 'node:sqlite' * import { SQLiteSyncStorage, NodeSqliteWrapper } from '@tldraw/sync-core' * * const db = new DatabaseSync('sync-state.db') * const sql = new NodeSqliteWrapper(db) * const storage = new SQLiteSyncStorage({ sql }) * ``` * * @example * ```ts * // Initialize with an existing snapshot * const storage = new SQLiteSyncStorage({ sql, snapshot: existingSnapshot }) * ``` * * @public */ export declare class SQLiteSyncStorage implements TLSyncStorage { /** * Check if the storage has been initialized (has data in the clock table). * Useful for determining whether to load from an external source on first access. */ static hasBeenInitialized(storage: TLSyncSqliteWrapper): boolean; /** * Get the current document clock value from storage without fully initializing. * Returns null if storage has not been initialized. * Useful for comparing storage freshness against external sources. */ static getDocumentClock(storage: TLSyncSqliteWrapper): null | number; private readonly stmts; private readonly sql; /* Excluded from this release type: objectTypes */ constructor({ sql, snapshot, objectTypes, onChange, }: { /** * Record type names stored in the object-store lane. Records of these types are routed to * a separate table: excluded from `getSnapshot()`, returned by `getObjectsSnapshot()`. * They share the documents' clock, tombstones, and transactions. */ objectTypes?: readonly string[]; onChange?(arg: TLSyncStorageOnChangeCallbackProps): unknown; snapshot?: RoomSnapshot | StoreSnapshot; sql: TLSyncSqliteWrapper; }); private notifier; onChange(callback: (arg: TLSyncStorageOnChangeCallbackProps) => void): () => void; transaction(callback: TLSyncStorageTransactionCallback, opts?: TLSyncStorageTransactionOptions): TLSyncStorageTransactionResult; getClock(): number; /* Excluded from this release type: _getTombstoneHistoryStartsAtClock */ /* Excluded from this release type: _getSchema */ /* Excluded from this release type: _setSchema */ /* Excluded from this release type: pruneTombstones */ getSnapshot(): RoomSnapshot; getObjectsSnapshot(): RoomSnapshot['documents']; getObjectsByIds(ids: Iterable): RoomSnapshot['documents']; private _iterateRecords; private _iterateTombstones; } /** * Function type for subscribing to events with a callback. * Returns an unsubscribe function to clean up the listener. * * @param cb - Callback function that receives the event value * @returns Function to call when you want to unsubscribe from the events * * @public */ export declare type SubscribingFn = (cb: (val: T) => void) => () => void; /** * Minimal interface for a synchronous SQLite database. * * This interface is compatible with: * - `node:sqlite` DatabaseSync (Node.js 22.5+) * - `better-sqlite3` Database * * Any SQLite library that provides synchronous `exec` and `prepare` methods * with the signatures below can be used with {@link NodeSqliteWrapper}. * * @public */ export declare interface SyncSqliteDatabase { /** Execute raw SQL without returning results */ exec(sql: string): void; /** Prepare a statement for execution */ prepare(sql: string): { all(...params: unknown[]): unknown[]; iterate(...params: unknown[]): IterableIterator; run(...params: unknown[]): unknown; }; } /* Excluded from this release type: TLConnectRequest */ /** * Handler function for custom application messages sent through the sync protocol. * These are user-defined messages that can be sent between clients via the sync server, * separate from the standard document synchronization messages. * * @param data - Custom message payload (application-defined structure) * * @example * ```ts * const customMessageHandler: TLCustomMessageHandler = (data) => { * if (data.type === 'user_joined') { * console.log(`${data.username} joined the session`) * showToast(`${data.username} is now collaborating`) * } * } * * const syncClient = new TLSyncClient({ * // ... other config * onCustomMessageReceived: customMessageHandler * }) * ``` * * @public */ export declare type TLCustomMessageHandler = (this: null, data: any) => void; /* Excluded from this release type: TLIncompatibilityReason */ /** * Access level for object-store lane record types (see `objectTypes` on the room). * * Object-lane records (e.g. comments) are gated by this per-session value instead of the * document-lane `isReadonly` flag, so a session can be allowed to write objects without * being allowed to edit the document ("can comment but not edit"), or vice versa. * * @public */ export declare type TLObjectStoreAccess = 'read' | 'write'; /** * Interface for persistent WebSocket-like connections used by TLSyncClient. * Handles automatic reconnection and provides event-based communication with the sync server. * Implementations should maintain connection resilience and handle network interruptions gracefully. * * @example * ```ts * class MySocketAdapter implements TLPersistentClientSocket { * connectionStatus: 'offline' | 'online' | 'error' = 'offline' * * sendMessage(msg: TLSocketClientSentEvent) { * if (this.ws && this.ws.readyState === WebSocket.OPEN) { * this.ws.send(JSON.stringify(msg)) * } * } * * onReceiveMessage = (callback) => { * // Set up message listener and return cleanup function * } * * restart() { * this.disconnect() * this.connect() * } * } * ``` * * @public */ export declare interface TLPersistentClientSocket { /** Current connection state - online means actively connected and ready */ connectionStatus: 'error' | 'offline' | 'online'; /** * Send a protocol message to the sync server * @param msg - Message to send (connect, push, ping, etc.) */ sendMessage(msg: ClientSentMessage): void; /** * Subscribe to messages received from the server * @param callback - Function called for each received message * @returns Cleanup function to remove the listener */ onReceiveMessage: SubscribingFn; /** * Subscribe to connection status changes * @param callback - Function called when connection status changes * @returns Cleanup function to remove the listener */ onStatusChange: SubscribingFn; /** * Force a connection restart (disconnect then reconnect) * Used for error recovery or when connection health checks fail */ restart(): void; /** * Close the connection */ close(): void; } /* Excluded from this release type: TLPersistentClientSocketStatus */ /* Excluded from this release type: TLPingRequest */ /** * Mode for handling presence information in sync sessions. * Controls whether presence data (cursors, selections) is shared with other clients. * * @public */ export declare type TLPresenceMode = /** No presence sharing - client operates independently */ 'full' | 'solo' /** Full presence sharing - cursors and selections visible to others */; /* Excluded from this release type: TLPushRequest */ /** * Authorizes a single record write from a client: any per-record, per-session rule the host wants * to enforce server-side — veto writes the session isn't allowed to make, or rewrite the record on * create. The session's `meta` carries whatever the rule needs (identity, roles, …); for example, * force a comment's `authorId` to the signed-in user so nobody can post in someone else's name. * * Called on **create**, **update**, and **delete** of records whose `typeName` it's registered for * (see {@link TLRecordAuthorizers}), and only for client pushes — never for server-initiated writes. * * `prev` and `next` are always at the **server's** schema version: client writes are migrated * before the authorizer runs, so guarding or stamping a field never requires knowing what older * clients call it. On create, the record you return is what gets stored (after validation) — no * migration runs afterwards, so stamped fields can't be clobbered. * * Return `null` to reject the write — it's skipped and the client self-corrects, exactly like the * `objectAccess` gate. Otherwise the write is allowed, and: * * - on **create**, the record you return is what gets stored, so stamp identity fields here (e.g. * set `authorId` from `session.meta`); * - on **update** and **delete**, only allow-vs-reject is used (the returned record's contents are * ignored), so use them to veto changes to immutable fields or unauthorized deletes — return * `next`/`prev` to allow, `null` to reject. * * ⚠︎ Runs synchronously inside the commit transaction, on the same path as every document edit — it * must be fast and do **no** I/O. `next`/`prev` are client-controlled records, so treat their * contents as untrusted; prefer returning `null` to reject over throwing, though a throw is * caught, logged, and treated as a rejection (fail closed) rather than crashing the push. For * expensive, async checks (e.g. resolving mentions against who can access a file), react after the * fact via `onCommittedChanges`. * * @public */ export declare type TLRecordAuthorizer = (args: { /** The session performing the write: its host-provided `meta` (e.g. the authenticated user * id) and the canvas-lane `isReadonly` state, for hosts whose object-lane policy follows * canvas access. */ session: { isReadonly: boolean; meta: SessionMeta; sessionId: string; }; } & ({ next: null; prev: Rec; type: 'delete'; } | { next: Rec; prev: null; type: 'create'; } | { next: Rec; prev: Rec; type: 'update'; })) => null | Rec; /** * A map from record `typeName` to a {@link TLRecordAuthorizer} for that record type. Only listed * types are authorized; every other record writes through untouched, so this stays off the hot path * for the vast majority of writes (shape drags etc.). * * Each authorizer is typed to its record — e.g. `next` in the `comment` entry is a `TLComment` — so * renaming a field on the record makes the authorizer that reads it fail to compile, rather than * silently stamp or guard the wrong field. * * Presence records are never authorized (presence is per-session and ephemeral); registering the * presence typeName is a construction-time error. * * @public */ export declare type TLRecordAuthorizers = { [K in R['typeName']]?: TLRecordAuthorizer, SessionMeta>; }; /** * Specialized error class for synchronization-related failures in tldraw collaboration. * * This error is thrown when the sync client encounters fatal errors that prevent * successful synchronization with the server. It captures both the error message * and the specific reason code that triggered the failure. * * Common scenarios include schema version mismatches, authentication failures, * network connectivity issues, and server-side validation errors. * * @example * ```ts * import { TLRemoteSyncError, TLSyncErrorCloseEventReason } from '@tldraw/sync-core' * * // Handle sync errors in your application * syncClient.onSyncError((error) => { * if (error instanceof TLRemoteSyncError) { * switch (error.reason) { * case TLSyncErrorCloseEventReason.NOT_AUTHENTICATED: * // Redirect user to login * break * case TLSyncErrorCloseEventReason.CLIENT_TOO_OLD: * // Show update required message * break * default: * console.error('Sync error:', error.message) * } * } * }) * ``` * * @example * ```ts * // Server-side: throwing a sync error * if (!hasPermission(userId, roomId)) { * throw new TLRemoteSyncError(TLSyncErrorCloseEventReason.FORBIDDEN) * } * ``` * * @public */ export declare class TLRemoteSyncError extends Error { readonly reason: string | TLSyncErrorCloseEventReason; name: string; /** * Creates a new TLRemoteSyncError with the specified reason. * * reason - The specific reason code or custom string describing why the sync failed. * When using predefined reasons from TLSyncErrorCloseEventReason, the client * can handle specific error types appropriately. Custom strings allow for * application-specific error details. */ constructor(reason: string | TLSyncErrorCloseEventReason); } /* Excluded from this release type: TLRoomSocket */ /* Excluded from this release type: TLSocketClientSentEvent */ /** * A server-side room that manages WebSocket connections and synchronizes tldraw document state * between multiple clients in real-time. Each room represents a collaborative document space * where users can work together on drawings with automatic conflict resolution. * * TLSocketRoom handles: * - WebSocket connection lifecycle management * - Real-time synchronization of document changes * - Session management and presence tracking * - Message chunking for large payloads * - Automatic client timeout and cleanup * * @example * ```ts * // Basic room setup * const room = new TLSocketRoom({ * onSessionRemoved: (room, { sessionId, numSessionsRemaining }) => { * console.log(`Client ${sessionId} disconnected, ${numSessionsRemaining} remaining`) * if (numSessionsRemaining === 0) { * room.close() * } * }, * onDataChange: () => { * console.log('Document data changed, consider persisting') * } * }) * * // Handle new client connections * room.handleSocketConnect({ * sessionId: 'user-session-123', * socket: webSocket, * isReadonly: false * }) * ``` * * @example * ```ts * // Room with initial snapshot and schema * const room = new TLSocketRoom({ * initialSnapshot: existingSnapshot, * schema: myCustomSchema, * clientTimeout: 30000, * log: { * warn: (...args) => logger.warn('SYNC:', ...args), * error: (...args) => logger.error('SYNC:', ...args) * } * }) * * // Update document programmatically * await room.updateStore(store => { * const shape = store.get('shape:abc123') * if (shape) { * shape.x = 100 * store.put(shape) * } * }) * ``` * * @public */ export declare class TLSocketRoom { readonly opts: TLSocketRoomOptions; private room; private readonly sessions; readonly log?: TLSyncLog; storage: TLSyncStorage; private disposables; private readonly snapshotTimers; /** * Creates a new TLSocketRoom instance for managing collaborative document synchronization. * * opts - Configuration options for the room * - initialSnapshot - Optional initial document state to load * - schema - Store schema defining record types and validation * - clientTimeout - Milliseconds to wait before disconnecting inactive clients * - log - Optional logger for warnings and errors * - onSessionRemoved - Called when a client session is removed * - onBeforeSendMessage - Called before sending messages to clients * - onAfterReceiveMessage - Called after receiving messages from clients * - onDataChange - Called when document data changes * - onPresenceChange - Called when presence data changes */ constructor(opts: TLSocketRoomOptions); /** * 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; /** * Handles a new client WebSocket connection, creating a session within the room. * This should be called whenever a client establishes a WebSocket connection to join * the collaborative document. * * @param opts - Connection options * - sessionId - Unique identifier for the client session (typically from browser tab) * - socket - WebSocket-like object for client communication * - isReadonly - Whether the client can modify the document (defaults to false) * - objectAccess - Write access for object-store lane record types (defaults to 'write'). * Independent of isReadonly, so a session can be allowed to write objects (e.g. comments) * without being allowed to edit the document. * - meta - Additional session metadata (required if SessionMeta is not void) * * @example * ```ts * // Handle new WebSocket connection * room.handleSocketConnect({ * sessionId: 'user-session-abc123', * socket: webSocketConnection, * isReadonly: !userHasEditPermission * }) * ``` * * @example * ```ts * // With session metadata * room.handleSocketConnect({ * sessionId: 'session-xyz', * socket: ws, * meta: { userId: 'user-123', name: 'Alice' } * }) * ``` */ handleSocketConnect(opts: { isReadonly?: boolean; objectAccess?: TLObjectStoreAccess; sessionId: string; socket: WebSocketMinimal; } & (SessionMeta extends void ? object : { meta: SessionMeta; })): void; private clearSnapshotTimer; private scheduleDebouncedSnapshot; /** * Processes a message received from a client WebSocket. Use this method in server * environments where WebSocket event listeners cannot be attached directly to socket * instances (e.g., Bun.serve, Cloudflare Workers with WebSocket hibernation). * * The method handles message chunking/reassembly and forwards complete messages * to the underlying sync room for processing. * * @param sessionId - Session identifier matching the one used in handleSocketConnect * @param message - Raw message data from the client (string or binary) * * @example * ```ts * // In a Bun.serve handler * server.upgrade(req, { * data: { sessionId, room }, * upgrade(res, req) { * // Connection established * }, * message(ws, message) { * const { sessionId, room } = ws.data * room.handleSocketMessage(sessionId, message) * } * }) * ``` */ handleSocketMessage(sessionId: string, message: AllowSharedBufferSource | string): void; /** * Handles a WebSocket error for the specified session. Use this in server environments * where socket event listeners cannot be attached directly. This will initiate cleanup * and session removal for the affected client. * * @param sessionId - Session identifier matching the one used in handleSocketConnect * * @example * ```ts * // In a custom WebSocket handler * socket.addEventListener('error', () => { * room.handleSocketError(sessionId) * }) * ``` */ handleSocketError(sessionId: string): void; /** * Handles a WebSocket close event for the specified session. Use this in server * environments where socket event listeners cannot be attached directly. This will * initiate cleanup and session removal for the disconnected client. * * @param sessionId - Session identifier matching the one used in handleSocketConnect * * @example * ```ts * // In a custom WebSocket handler * socket.addEventListener('close', () => { * room.handleSocketClose(sessionId) * }) * ``` */ handleSocketClose(sessionId: string): void; /** * Resumes a previously-connected session directly into `Connected` state, bypassing * the connect handshake. Use this after server hibernation (e.g., Cloudflare Durable * Object hibernation) when WebSocket connections survived but all in-memory state was lost. * * The session is restored using a {@link SessionStateSnapshot} previously obtained * via {@link TLSocketRoom.getSessionSnapshot}. The client is unaware the server restarted and * continues sending messages normally. * * Unlike {@link TLSocketRoom.handleSocketConnect}, this method does NOT attach WebSocket event * listeners. In hibernation environments, events are delivered via class methods * (e.g., `webSocketMessage`) rather than `addEventListener`. * * @param opts - Resume options * - sessionId - Unique identifier for the client session * - socket - WebSocket-like object for client communication * - snapshot - Session state snapshot from {@link TLSocketRoom.getSessionSnapshot} * - meta - Additional session metadata (required if SessionMeta is not void) * * @example * ```ts * // After Cloudflare DO hibernation wake * for (const ws of ctx.getWebSockets()) { * const data = ws.deserializeAttachment() * room.handleSocketResume({ * sessionId: data.sessionId, * socket: ws, * snapshot: data.snapshot, * }) * } * ``` */ handleSocketResume(opts: { sessionId: string; snapshot: SessionStateSnapshot; socket: WebSocketMinimal; } & (SessionMeta extends void ? object : { meta: SessionMeta; })): void; /** * Returns a snapshot of a connected session's state that can be persisted and later * used with {@link TLSocketRoom.handleSocketResume} to restore the session after hibernation. * * Returns `null` if the session doesn't exist or isn't in the `Connected` state. * * @param sessionId - The session to snapshot * * @example * ```ts * // Store snapshot in a Cloudflare WebSocket attachment * const snapshot = room.getSessionSnapshot(sessionId) * if (snapshot) { * ws.serializeAttachment({ sessionId, snapshot }) * } * ``` */ getSessionSnapshot(sessionId: string): null | SessionStateSnapshot; /** * Returns the current document clock value. The clock is a monotonically increasing * integer that increments with each document change, providing a consistent ordering * of changes across the distributed system. * * @returns The current document clock value * * @example * ```ts * const clock = room.getCurrentDocumentClock() * console.log(`Document is at version ${clock}`) * ``` */ getCurrentDocumentClock(): number; /** * Retrieves a deeply cloned copy of a record from the document store. * Returns undefined if the record doesn't exist. The returned record is * safe to mutate without affecting the original store data. * * @param id - Unique identifier of the record to retrieve * @returns Deep clone of the record, or undefined if not found * * @example * ```ts * const shape = room.getRecord('shape:abc123') * if (shape) { * console.log('Shape position:', shape.x, shape.y) * // Safe to modify without affecting store * shape.x = 100 * } * ``` */ getRecord(id: string): R; /** * Returns information about all active sessions in the room. Each session * represents a connected client with their current connection status and metadata. * * @returns Array of session information objects containing: * - sessionId - Unique session identifier * - isConnected - Whether the session has an active WebSocket connection * - isReadonly - Whether the session can modify the document * - meta - Custom session metadata * * @example * ```ts * const sessions = room.getSessions() * console.log(`Room has ${sessions.length} active sessions`) * * for (const session of sessions) { * console.log(`${session.sessionId}: ${session.isConnected ? 'online' : 'offline'}`) * if (session.isReadonly) { * console.log(' (read-only access)') * } * } * ``` */ getSessions(): Array<{ isConnected: boolean; isReadonly: boolean; meta: SessionMeta; objectAccess: TLObjectStoreAccess; sessionId: string; }>; /** * Creates a complete snapshot of the current document state, including all records * and synchronization metadata. This snapshot can be persisted to storage and used * to restore the room state later or revert to a previous version. * * @returns Complete room snapshot including documents, clock values, and tombstones * @deprecated if you need to do this use * * @example * ```ts * // Capture current state for persistence * const snapshot = room.getCurrentSnapshot() * await saveToDatabase(roomId, JSON.stringify(snapshot)) * * // Later, restore from snapshot * const savedSnapshot = JSON.parse(await loadFromDatabase(roomId)) * const newRoom = new TLSocketRoom({ initialSnapshot: savedSnapshot }) * ``` */ getCurrentSnapshot(): RoomSnapshot; /** * Returns a snapshot of the object-store lane (see `objectTypes`), for persisting * object-lane records separately from the document. Same entry shape as * {@link RoomSnapshot.documents}. */ getCurrentObjectsSnapshot(): RoomSnapshot['documents']; /* Excluded from this release type: getPresenceRecords */ /** * Loads a document snapshot, completely replacing the current room state. * This will disconnect all current clients and update the document to match * the provided snapshot. Use this for restoring from backups or implementing * document versioning. * * @param snapshot - Room or store snapshot to load * * @example * ```ts * // Restore from a saved snapshot * const backup = JSON.parse(await loadBackup(roomId)) * room.loadSnapshot(backup) * * // All clients will be disconnected and need to reconnect * // to see the restored document state * ``` */ loadSnapshot(snapshot: RoomSnapshot | TLStoreSnapshot): void; /** * Executes a transaction to modify the document store. Changes made within the * transaction are atomic and will be synchronized to all connected clients. * The transaction provides isolation from concurrent changes until it commits. * * @param updater - Function that receives store methods to make changes * - store.get(id) - Retrieve a record (safe to mutate, but must call put() to commit) * - store.put(record) - Save a modified record * - store.getAll() - Get all records in the store * - store.delete(id) - Remove a record from the store * @returns Promise that resolves when the transaction completes * * @example * ```ts * // Update multiple shapes in a single transaction * await room.updateStore(store => { * const shape1 = store.get('shape:abc123') * const shape2 = store.get('shape:def456') * * if (shape1) { * shape1.x = 100 * store.put(shape1) * } * * if (shape2) { * shape2.meta.approved = true * store.put(shape2) * } * }) * ``` * * @example * ```ts * // Async transaction with external API call * await room.updateStore(async store => { * const doc = store.get('document:main') * if (doc) { * doc.lastModified = await getCurrentTimestamp() * store.put(doc) * } * }) * ``` * @deprecated use the storage.transaction method instead */ updateStore(updater: (store: RoomStoreMethods) => Promise | void): Promise; /** * Sends a custom message to a specific client session. This allows sending * application-specific data that doesn't modify the document state, such as * notifications, chat messages, or custom commands. * * @param sessionId - Target session identifier * @param data - Custom payload to send (will be JSON serialized) * * @example * ```ts * // Send a notification to a specific user * room.sendCustomMessage('session-123', { * type: 'notification', * message: 'Your changes have been saved' * }) * * // Send a chat message * room.sendCustomMessage('session-456', { * type: 'chat', * from: 'Alice', * text: 'Great work on this design!' * }) * ``` */ sendCustomMessage(sessionId: string, data: any): void; /** * Immediately removes a session from the room and closes its WebSocket connection. * The client will attempt to reconnect automatically unless a fatal reason is provided. * * @param sessionId - Session identifier to remove * @param fatalReason - Optional fatal error reason that prevents reconnection. WebSocket close * reasons are capped at 123 UTF-8 bytes, so a longer reason is truncated and ends with `... (+N bytes)`. * * @example * ```ts * // Kick a user (they can reconnect) * room.closeSession('session-troublemaker') * * // Permanently ban a user * room.closeSession('session-banned', 'PERMISSION_DENIED') * * // Close session due to inactivity * room.closeSession('session-idle', 'TIMEOUT') * ``` */ closeSession(sessionId: string, fatalReason?: string | TLSyncErrorCloseEventReason): void; /** * Closes the room and disconnects all connected clients. This should be called * when shutting down the room permanently, such as during server shutdown or * when the room is no longer needed. Once closed, the room cannot be reopened. * * @example * ```ts * // Clean shutdown when no users remain * if (room.getNumActiveSessions() === 0) { * await persistSnapshot(room.getCurrentSnapshot()) * room.close() * } * * // Server shutdown * process.on('SIGTERM', () => { * for (const room of activeRooms.values()) { * room.close() * } * }) * ``` */ close(): void; /** * Checks whether the room has been permanently closed. Closed rooms cannot * accept new connections or process further changes. * * @returns True if the room is closed, false if still active * * @example * ```ts * if (room.isClosed()) { * console.log('Room has been shut down') * // Create a new room or redirect users * } else { * // Room is still accepting connections * room.handleSocketConnect({ sessionId, socket }) * } * ``` */ isClosed(): boolean; } /** * Base options for TLSocketRoom. * @public */ export declare interface TLSocketRoomOptions { storage?: TLSyncStorage; /** * @deprecated use the storage option instead */ initialSnapshot?: RoomSnapshot | TLStoreSnapshot; /** * @deprecated use the storage option with an onChange callback instead */ onDataChange?(): void; schema?: StoreSchema; clientTimeout?: number; log?: TLSyncLog; onSessionRemoved?: (room: TLSocketRoom, args: { meta: SessionMeta; numSessionsRemaining: number; sessionId: string; }) => void; onBeforeSendMessage?: (args: { /* Excluded from this release type: message */ meta: SessionMeta; sessionId: string; stringified: string; }) => void; onAfterReceiveMessage?: (args: { /* Excluded from this release type: message */ meta: SessionMeta; sessionId: string; stringified: string; }) => void; /* Excluded from this release type: onPresenceChange */ /** * Called once after a client push commits, with the committed document diff. Use to react to * document changes as soon as they commit — e.g. project certain record types to an external * store. Best-effort: do not throw and do not block. * * Only fires for client pushes. Server-initiated changes — `updateStore`, `loadSnapshot`, or * writing to the storage directly — do not trigger it, so anything mirroring room state through * this callback must handle those paths itself. */ onCommittedChanges?: (args: { diff: TLSyncForwardDiff; documentClock: number; }) => void; /** * Record type names to serve through the object-store lane instead of the document lane. * Each must be a document-scoped type registered in the schema. Object-lane writes are gated * per session by `objectAccess` (see {@link TLSocketRoom.handleSocketConnect}) rather than * `isReadonly`, so e.g. a session can be allowed to comment without being allowed to edit. */ objectTypes?: readonly string[]; /** * Per-type authorizers for client record writes (create, update, delete): veto writes the * session isn't allowed to make, or rewrite the record on create — e.g. force a comment's * `authorId` to the signed-in user. See {@link TLRecordAuthorizers}. */ authorizeRecord?: TLRecordAuthorizers; /** * When set, the room will call {@link TLSocketRoom.getSessionSnapshot} after * no message activity for a session for 5s and pass the result to this callback. * Use for persisting snapshots to WebSocket attachments (e.g. Cloudflare hibernation). * The room clears any pending snapshot when the session closes. */ onSessionSnapshot?: (sessionId: string, snapshot: SessionStateSnapshot) => void; } /* Excluded from this release type: TLSocketServerSentDataEvent */ /* Excluded from this release type: TLSocketServerSentEvent */ /** * Event object describing changes in socket connection status. * Contains either a basic status change or an error with details. * * @public */ export declare type TLSocketStatusChangeEvent = { /** Connection came online or went offline */ status: 'offline' | 'online'; } | { /** Connection encountered an error */ status: 'error'; /** Description of the error that occurred */ reason: string; }; /* Excluded from this release type: TLSocketStatusListener */ /** * Valid input value types for SQLite query parameters. * These are the types that can be passed as bindings to prepared statements. * @public */ export declare type TLSqliteInputValue = bigint | null | number | string | Uint8Array; /** * Possible output value types returned from SQLite queries. * Includes all input types plus Uint8Array for BLOB columns. * @public */ export declare type TLSqliteOutputValue = bigint | null | number | string | Uint8Array; /** * A row returned from a SQLite query, mapping column names to their values. * @public */ export declare type TLSqliteRow = Record; /** * Main client-side synchronization engine for collaborative tldraw applications. * * TLSyncClient manages bidirectional synchronization between a local tldraw Store * and a remote sync server. It uses an optimistic update model where local changes * are immediately applied for responsive UI, then sent to the server for validation * and distribution to other clients. * * The synchronization follows a git-like push/pull/rebase model: * - **Push**: Local changes are sent to server as diff operations * - **Pull**: Server changes are received and applied locally * - **Rebase**: Conflicting changes are resolved by undoing local changes, * applying server changes, then re-applying local changes on top * * @example * ```ts * import { TLSyncClient, ClientWebSocketAdapter } from '@tldraw/sync-core' * import { createTLStore } from '@tldraw/store' * * // Create store and socket * const store = createTLStore({ schema: mySchema }) * const socket = new ClientWebSocketAdapter('ws://localhost:3000/sync') * * // Create sync client * const syncClient = new TLSyncClient({ * store, * socket, * presence: atom(null), * onLoad: () => console.log('Connected and loaded'), * onSyncError: (reason) => console.error('Sync failed:', reason) * }) * * // Changes to store are now automatically synchronized * store.put([{ id: 'shape1', type: 'geo', x: 100, y: 100 }]) * ``` * * @example * ```ts * // Advanced usage with presence and custom messages * const syncClient = new TLSyncClient({ * store, * socket, * presence: atom({ cursor: { x: 0, y: 0 }, userName: 'Alice' }), * presenceMode: atom('full'), * onCustomMessageReceived: (data) => { * if (data.type === 'chat') { * showChatMessage(data.message, data.from) * } * }, * onAfterConnect: (client, { isReadonly }) => { * if (isReadonly) { * showNotification('Connected in read-only mode') * } * } * }) * ``` * * @public */ export declare class TLSyncClient = Store> { /** The last clock time from the most recent server update */ private lastServerClock; private lastServerInteractionTimestamp; /** * Send time of the oldest outstanding ping; answered iff `lastServerInteractionTimestamp >= it` * (any server message counts). Null until the first ping after (re)connect. */ private firstUnansweredPingAt; /** The queue of in-flight push requests that have not yet been acknowledged by the server */ private pendingPushRequests; private unsentChanges; /** * The diff of 'unconfirmed', 'optimistic' changes that have been made locally by the user if we * take this diff, reverse it, and apply that to the store, our store will match exactly the most * recent state of the server that we know about */ private speculativeChanges; private disposables; /** Separate scheduler instance for network sync operations */ private readonly fpsScheduler; /** Send any unsent push requests to the server */ private readonly sendUnsentChanges; /** Schedule a rebase operation */ private readonly scheduleRebase; /* Excluded from this release type: store */ /* Excluded from this release type: socket */ /* Excluded from this release type: presenceState */ /* Excluded from this release type: presenceMode */ /* Excluded from this release type: isConnectedToRoom */ /** * The client clock is essentially a counter for push requests Each time a push request is created * the clock is incremented. This clock is sent with the push request to the server, and the * server returns it with the response so that we can match up the response with the request. * * The clock may also be used at one point in the future to allow the client to re-send push * requests idempotently (i.e. the server will keep track of each client's clock and not execute * requests it has already handled), but at the time of writing this is neither needed nor * implemented. */ private clientClock; /** * Callback executed immediately after successful connection to sync room. * Use this to perform any post-connection setup required for your application, * such as initializing default content or updating UI state. * * @param self - The TLSyncClient instance that connected * @param details - Connection details * - isReadonly - Whether the connection is in read-only mode * - objectAccess - Write access for object-store lane record types (defaults to 'write' * when the server doesn't send it, e.g. older servers or rooms with no object lane) */ private readonly onAfterConnect?; private readonly onCustomMessageReceived?; private isDebugging; private debug; private readonly presenceType; private didCancel?; /** * Creates a new TLSyncClient instance to manage synchronization with a remote server. * * @param config - Configuration object for the sync client * - store - The local tldraw store to synchronize * - socket - WebSocket adapter for server communication * - presence - Reactive signal containing current user's presence data * - presenceMode - Optional signal controlling presence sharing (defaults to 'full') * - onLoad - Callback fired when initial sync completes successfully * - onSyncError - Callback fired when sync fails with error reason * - onCustomMessageReceived - Optional handler for custom messages * - onAfterConnect - Optional callback fired after successful connection * - self - The TLSyncClient instance * - details - Connection details including readonly status * - didCancel - Optional function to check if sync should be cancelled */ constructor(config: { didCancel?(): boolean; onAfterConnect?(self: TLSyncClient, details: { isReadonly: boolean; objectAccess: TLObjectStoreAccess; }): void; onCustomMessageReceived?: TLCustomMessageHandler; onLoad(self: TLSyncClient): void; onSyncError(reason: string): void; presence: Signal; presenceMode?: Signal; socket: TLPersistentClientSocket; store: S; }); /* Excluded from this release type: latestConnectRequestId */ /** * This is the first message that is sent over a newly established socket connection. And we need * to wait for the response before this client can be used. */ private sendConnectMessage; /** Switch to offline mode */ private resetConnection; /** * Invoked when the socket connection comes online, either for the first time or as the result of * a reconnect. The goal is to rebase on the server's state and fire off a new push request for * any local changes that were made while offline. */ private didReconnect; private incomingDiffBuffer; /** Handle events received from the server */ private handleServerEvent; /** * Closes the sync client and cleans up all resources. * * Call this method when you no longer need the sync client to prevent * memory leaks and close the WebSocket connection. After calling close(), * the client cannot be reused. * * @example * ```ts * // Clean shutdown * syncClient.close() * ``` */ close(): void; private lastPushedPresenceState; private pushPresence; /** Push a change to the server, or stash it locally if we're offline */ private push; /** Get the target FPS for network operations based on presence mode */ private getSyncFps; /** * Applies a 'network' diff to the store this does value-based equality checking so that if the * data is the same (as opposed to merely identical with ===), then no change is made and no * changes will be propagated back to store listeners */ private applyNetworkDiff; private rebase; } /** * WebSocket close code used by the server to signal a non-recoverable sync error. * This close code indicates that the connection is being terminated due to an error * that cannot be automatically recovered from, such as authentication failures, * incompatible client versions, or invalid data. * * @example * ```ts * // Server-side: Close connection with specific error reason * socket.close(TLSyncErrorCloseEventCode, TLSyncErrorCloseEventReason.NOT_FOUND) * * // Client-side: Handle the error in your sync error handler * const syncClient = new TLSyncClient({ * // ... other config * onSyncError: (reason) => { * console.error('Sync failed:', reason) // Will receive 'NOT_FOUND' * } * }) * ``` * * @public */ export declare const TLSyncErrorCloseEventCode: 4099; /** * Predefined reasons for server-initiated connection closures. * These constants represent different error conditions that can cause * the sync server to terminate a WebSocket connection. * * @example * ```ts * // Server usage * if (!user.hasPermission(roomId)) { * socket.close(TLSyncErrorCloseEventCode, TLSyncErrorCloseEventReason.FORBIDDEN) * } * * // Client error handling * syncClient.onSyncError((reason) => { * switch (reason) { * case TLSyncErrorCloseEventReason.NOT_FOUND: * showError('Room does not exist') * break * case TLSyncErrorCloseEventReason.FORBIDDEN: * showError('Access denied') * break * case TLSyncErrorCloseEventReason.CLIENT_TOO_OLD: * showError('Please update your app') * break * } * }) * ``` * * @public */ export declare const TLSyncErrorCloseEventReason: { /** A single message exceeded the size the server will assemble */ readonly MESSAGE_TOO_LARGE: 'MESSAGE_TOO_LARGE'; /** Client exceeded rate limits */ readonly RATE_LIMITED: 'RATE_LIMITED'; /** Client protocol version too old */ readonly CLIENT_TOO_OLD: 'CLIENT_TOO_OLD'; /** Client sent invalid or corrupted record data */ readonly INVALID_RECORD: 'INVALID_RECORD'; /** Room has reached maximum capacity */ readonly ROOM_FULL: 'ROOM_FULL'; /** Room or resource not found */ readonly NOT_FOUND: 'NOT_FOUND'; /** Server protocol version too old */ readonly SERVER_TOO_OLD: 'SERVER_TOO_OLD'; /** Unexpected server error occurred */ readonly UNKNOWN_ERROR: 'UNKNOWN_ERROR'; /** User authentication required or invalid */ readonly NOT_AUTHENTICATED: 'NOT_AUTHENTICATED'; /** User lacks permission to access the room */ readonly FORBIDDEN: 'FORBIDDEN'; }; /** * Union type of all possible server connection close reasons. * Represents the string values that can be passed when a server closes * a sync connection due to an error condition. * * @public */ export declare type TLSyncErrorCloseEventReason = (typeof TLSyncErrorCloseEventReason)[keyof typeof TLSyncErrorCloseEventReason]; /** * Respresents a diff of puts and deletes. * @public */ export declare interface TLSyncForwardDiff { puts: Record; deletes: string[]; } /** * Logging interface for TLSocketRoom operations. Provides optional methods * for warning and error logging during synchronization operations. * * @example * ```ts * const logger: TLSyncLog = { * warn: (...args) => console.warn('[SYNC]', ...args), * error: (...args) => console.error('[SYNC]', ...args) * } * * const room = new TLSocketRoom({ log: logger }) * ``` * * @public */ export declare interface TLSyncLog { /** * Optional warning logger for non-fatal sync issues * @param args - Arguments to log */ warn?(...args: any[]): void; /** * Optional error logger for sync errors and failures * @param args - Arguments to log */ error?(...args: any[]): void; } /* Excluded from this release type: TLSyncRoom */ /** * A prepared statement that can be executed multiple times with different bindings. * @public */ export declare interface TLSyncSqliteStatement { /** Execute the statement and iterate over results one at a time */ iterate(...bindings: TParams): IterableIterator; /** Execute the statement and return all results as an array */ all(...bindings: TParams): TResult[]; /** Execute the statement without returning results (for DML) */ run(...bindings: TParams): void; } /** * Interface for SQLite storage with prepare, exec and transaction capabilities. * @public */ export declare interface TLSyncSqliteWrapper { /** Optional configuration for table names. If not provided, defaults are used. */ readonly config?: TLSyncSqliteWrapperConfig; /** Prepare a SQL statement for execution */ prepare(sql: string): TLSyncSqliteStatement; /** Execute raw SQL (for DDL, multi-statement scripts) */ exec(sql: string): void; /** Execute a callback within a transaction */ transaction(callback: () => T): T; } /** * Configuration for SQLiteSyncStorage. * @public */ export declare interface TLSyncSqliteWrapperConfig { /** Prefix for all table names (default: ''). E.g. 'sync_' creates tables 'sync_documents', 'sync_tombstones', 'sync_metadata' */ tablePrefix?: string; } /** * Pluggable synchronous transactional storage layer for TLSyncRoom. * Provides methods for managing documents, tombstones, and clocks within transactions. * * @public */ export declare interface TLSyncStorage { transaction(callback: TLSyncStorageTransactionCallback, opts?: TLSyncStorageTransactionOptions): TLSyncStorageTransactionResult; getClock(): number; onChange(callback: (arg: TLSyncStorageOnChangeCallbackProps) => unknown): () => void; /** A snapshot of the document lane only — never contains object-store lane records. */ getSnapshot?(): RoomSnapshot; /** * A snapshot of the object-store lane (see `objectTypes` on the storage), for hosts that * persist object-lane records separately from the document. Same entry shape as * `RoomSnapshot['documents']` so a lane can be persisted and re-seeded via a merged snapshot. */ getObjectsSnapshot?(): RoomSnapshot['documents']; /** * The same entries for the given ids, for callers that need a handful of records rather than the * whole (parsed) lane. Ids with no object-lane record are omitted — document-lane ids included — * and duplicates yield one entry. */ getObjectsByIds?(ids: Iterable): RoomSnapshot['documents']; } /** * Result returned from getChangesSince, containing all changes since a given clock time. * @public */ export declare interface TLSyncStorageGetChangesSinceResult { /** * The changes as a TLSyncForwardDiff. */ diff: TLSyncForwardDiff; /** * If true, the client should wipe all local data and replace with the server's state. * This happens when the client's clock is too old and we've lost tombstone history. */ wipeAll: boolean; } /** * Properties passed to the onChange callback. * @public */ export declare interface TLSyncStorageOnChangeCallbackProps { /** * The ID of the transaction that caused the change. * This is useful for ignoring certain changes in onChange callbacks. */ id?: string; documentClock: number; } /** * Transaction interface for storage operations. Provides methods to read and modify * documents, tombstones, and metadata within a transaction. * * @public */ export declare interface TLSyncStorageTransaction extends SynchronousStorage { /** * Get the current clock value. * If the clock has incremented during the transaction, * the incremented value will be returned. * * @returns The current clock value */ getClock(): number; /** * Get all changes (document updates and deletions) since a given clock time. * This is the main method for calculating diffs for client sync. * * @param sinceClock - The clock time to get changes since * @returns Changes since the specified clock time */ getChangesSince(sinceClock: number): TLSyncStorageGetChangesSinceResult | undefined; } /** * Callback type for a transaction. * The conditional return type ensures that the callback is synchronous. * @public */ export declare type TLSyncStorageTransactionCallback = (txn: TLSyncStorageTransaction) => T extends Promise ? { __error: 'Transaction callbacks cannot be async. Use synchronous operations only.'; } : T; /** * Options for a transaction. * @public */ export declare interface TLSyncStorageTransactionOptions { /** * Use this if you need to identify the transaction for logging or debugging purposes * or for ignoring certain changes in onChange callbacks */ id?: string; /** * Controls when the storage layer should emit the actual changes that occurred during the transaction. * * - `'always'` - Always emit the changes, regardless of whether they were applied verbatim * - `'when-different'` - Only emit changes if the storage layer modified/embellished the records * (e.g., added server timestamps, normalized data, etc.) * * When changes are emitted, they will be available in the `changes` field of the transaction result. * This is useful when the storage layer may transform records and the caller needs to know * what actually changed rather than what was requested. */ emitChanges?: 'always' | 'when-different'; } /** * Result returned from a storage transaction. * @public */ export declare interface TLSyncStorageTransactionResult { documentClock: number; didChange: boolean; result: T; /** * The actual changes that occurred during the transaction, if requested via `emitChanges` option. * This is a RecordsDiff where: * - `added` contains records that were put (we don't have "from" state for emitted changes) * - `removed` contains records that were deleted (with placeholder values since we only have IDs) * - `updated` is empty (emitted changes don't track before/after pairs) * * Only populated when: * - `emitChanges: 'always'` was specified, or * - `emitChanges: 'when-different'` was specified and the storage layer modified records */ changes?: TLSyncForwardDiff; } /* Excluded from this release type: ValueOp */ /* Excluded from this release type: ValueOpType */ /** * Minimal server-side WebSocket interface that is compatible with various WebSocket implementations. * This interface abstracts over different WebSocket libraries and platforms to provide a consistent * API for the ServerSocketAdapter. * * Supports: * - The standard WebSocket interface (Cloudflare, Deno, some Node.js setups) * - The 'ws' WebSocket interface (Node.js ws library) * - The Bun.serve socket implementation * * @public * @example * ```ts * // Standard WebSocket * const standardWs: WebSocketMinimal = new WebSocket('ws://localhost:8080') * * // Node.js 'ws' library WebSocket * import WebSocket from 'ws' * const nodeWs: WebSocketMinimal = new WebSocket('ws://localhost:8080') * * // Bun WebSocket (in server context) * // const bunWs: WebSocketMinimal = server.upgrade(request) * ``` */ export declare interface WebSocketMinimal { /** * Optional method to add event listeners for WebSocket events. * Not all WebSocket implementations provide this method. * * @param type - The event type to listen for * @param listener - The event handler function */ addEventListener?: (type: 'close' | 'error' | 'message', listener: (event: any) => void) => void; /** * Optional method to remove event listeners for WebSocket events. * Not all WebSocket implementations provide this method. * * @param type - The event type to stop listening for * @param listener - The event handler function to remove */ removeEventListener?: (type: 'close' | 'error' | 'message', listener: (event: any) => void) => void; /** * Sends a string message through the WebSocket connection. * * @param data - The string data to send */ send: (data: string) => void; /** * Closes the WebSocket connection. * * @param code - Optional close code (default: 1000 for normal closure) * @param reason - Optional human-readable close reason */ close: (code?: number, reason?: string) => void; /** * The current state of the WebSocket connection. * - 0: CONNECTING * - 1: OPEN * - 2: CLOSING * - 3: CLOSED */ readyState: number; } export { }