LakeSync

Client SDK

API reference for @lakesync/client — LocalDB, SyncCoordinator, transports, and queues.

The @lakesync/client package provides the browser-side SDK for building local-first applications with LakeSync.

createClient

One-call factory that opens a database, registers schemas, creates an HTTP transport, and starts auto-sync.

function createClient(config: CreateClientConfig): Promise<LakeSyncClient>;

CreateClientConfig

interface CreateClientConfig {
  name: string;
  schemas: TableSchema[];
  clientId: string;
  gateway: {
    url: string;
    gatewayId: string;
    token?: string;
  };
  autoSyncMs?: number;       // default 10000, 0 to disable
  backend?: "idb" | "memory";
  queue?: SyncQueue;
  coordinatorConfig?: Omit<SyncCoordinatorConfig, "queue" | "clientId" | "autoSyncIntervalMs">;
}

LakeSyncClient

interface LakeSyncClient {
  coordinator: SyncCoordinator;
  db: LocalDB;
  transport: TransportWithCapabilities;
  destroy(): Promise<void>;
}

Example:

import { createClient } from "lakesync/client";

const client = await createClient({
  name: "my-app",
  clientId: "client-1",
  schemas: [{ table: "todos", columns: [{ name: "title", type: "string" }] }],
  gateway: { url: "https://gw.example.com", gatewayId: "gw-1", token: "jwt..." },
});

await client.coordinator.tracker.insert("todos", "todo-1", { title: "Hello" });
await client.destroy();

LocalDB

SQLite database running in the browser via sql.js WASM, with optional IndexedDB snapshot persistence.

LocalDB.open(config)

class LocalDB {
  static open(config: DbConfig): Promise<Result<LocalDB, DbError>>;

  readonly name: string;
  readonly backend: "idb" | "memory";

  exec(sql: string, params?: unknown[]): Promise<Result<void, DbError>>;
  query<T>(sql: string, params?: unknown[]): Promise<Result<T[], DbError>>;
  transaction<T>(fn: (tx: Transaction) => T): Promise<Result<T, DbError>>;
  save(): Promise<Result<void, DbError>>;
  close(): Promise<void>;
}
  • open(config) — Initialise sql.js WASM and create a database. When backend is "idb", loads any existing snapshot from IndexedDB.
  • exec(sql, params?) — Execute a SQL statement (DDL or DML).
  • query(sql, params?) — Execute a SQL query and return rows as typed objects.
  • transaction(fn) — Execute a function within a BEGIN/COMMIT transaction with automatic rollback on error.
  • save() — Persist the database snapshot to IndexedDB. No-op for "memory" backend.
  • close() — Save (if IDB) and release resources.

DbConfig

interface DbConfig {
  name: string;
  backend?: "idb" | "memory";
}

When backend is omitted, auto-detects: uses "idb" if indexedDB is available, otherwise "memory".

DbError

class DbError extends LakeSyncError {
  constructor(message: string, cause?: Error);
}

SyncTracker

Tracks local mutations (insert, update, delete) and produces column-level deltas that are pushed to a sync queue.

class SyncTracker {
  constructor(
    db: LocalDB,
    queue: SyncQueue,
    hlc: HLC,
    clientId: string,
  );

  insert(
    table: string,
    rowId: string,
    data: Record<string, unknown>,
  ): Promise<Result<void, LakeSyncError>>;

  update(
    table: string,
    rowId: string,
    data: Record<string, unknown>,
  ): Promise<Result<void, LakeSyncError>>;

  delete(
    table: string,
    rowId: string,
  ): Promise<Result<void, LakeSyncError>>;

  query<T>(
    sql: string,
    params?: unknown[],
  ): Promise<Result<T[], DbError>>;
}

Each mutation applies the change to SQLite, extracts a RowDelta, and pushes it to the queue for eventual upstream delivery.

SyncCoordinator

Orchestrates push/pull sync between the local database and a remote gateway.

Constructor

class SyncCoordinator {
  constructor(db: LocalDB, transport: TransportWithCapabilities, config?: SyncCoordinatorConfig);

  readonly tracker: SyncTracker;
  readonly clientId: string;
  readonly lastSyncTime: Date | null;

  syncOnce(): Promise<void>;
  pushToGateway(): Promise<void>;
  pullFromGateway(source?: string): Promise<number>;
  pullFrom(source: string): Promise<number>;
  executeAction(params: { connector: string; actionType: string; params: Record<string, unknown>; idempotencyKey?: string }): Promise<void>;
  describeActions(): Promise<Result<ActionDiscovery, LakeSyncError>>;
  startAutoSync(): void;
  stopAutoSync(): void;
  queueDepth(): Promise<number>;

  on<K extends keyof SyncEvents>(event: K, listener: SyncEvents[K]): void;
  off<K extends keyof SyncEvents>(event: K, listener: SyncEvents[K]): void;
}
  • syncOnce() — Perform a single sync cycle (pull then push, depending on syncMode). Includes initial checkpoint download on first sync. Also processes pending actions.
  • pushToGateway() — Push pending deltas from the queue to the gateway.
  • pullFromGateway(source?) — Pull remote deltas and apply them locally. Pass a source name for adapter-sourced pull.
  • pullFrom(source) — Convenience wrapper for pullFromGateway(source).
  • executeAction(params) — Queue an imperative action for execution. Generates actionId and HLC automatically. Requires an actionQueue in config. Emits onActionComplete when the gateway responds.
  • describeActions() — Discover available connectors and their supported action types from the gateway. Returns empty connectors when the transport doesn't support discovery.
  • startAutoSync() — Start periodic sync + visibility change handler. Connects realtime transport if available.
  • stopAutoSync() — Stop periodic sync, remove listeners, disconnect transport.

SyncCoordinatorConfig

interface SyncCoordinatorConfig {
  queue?: SyncQueue;
  hlc?: HLC;
  clientId?: string;
  maxRetries?: number;
  syncMode?: SyncMode;
  autoSyncIntervalMs?: number;       // default 10000 (10s)
  realtimeHeartbeatMs?: number;       // default 60000 (60s)
  actionQueue?: ActionQueue;          // required for executeAction()
  maxActionRetries?: number;          // default 5
}
  • autoSyncIntervalMs — Polling interval for HTTP transports. Default 10 seconds.
  • realtimeHeartbeatMs — Polling interval when a realtime transport is active. Default 60 seconds. Acts as a safety net for deltas missed during brief disconnects.
  • actionQueue — Queue for imperative actions. Required to use executeAction(). Use MemoryActionQueue for development or IDBActionQueue for persistence.
  • maxActionRetries — Maximum retries before dead-lettering an action. Default 5.

SyncMode

type SyncMode = "full" | "pushOnly" | "pullOnly";
  • full — Pull then push on each sync cycle (default).
  • pushOnly — Only push local deltas. Useful for write-only producers.
  • pullOnly — Only pull remote deltas. Useful for read-only consumers.

SyncEvents

interface SyncEvents {
  onChange: (count: number) => void;
  onSyncComplete: () => void;
  onError: (error: Error) => void;
  onActionComplete: (actionId: string, result: ActionResult | ActionErrorResult) => void;
}
  • onActionComplete — Fired when an action succeeds or fails non-retryably. Check "data" in result to distinguish ActionResult from ActionErrorResult.

Transports

Transport Interfaces

The transport layer is split into focused capability interfaces. Only SyncTransport (push/pull) is required — checkpoint, real-time, and action capabilities are opt-in.

SyncTransport

interface SyncTransport {
  push(msg: SyncPush): Promise<Result<{ serverHlc: HLCTimestamp; accepted: number }, LakeSyncError>>;
  pull(msg: SyncPull): Promise<Result<SyncResponse, LakeSyncError>>;
}

Core sync transport — push and pull deltas. Every transport must implement this.

CheckpointTransport

interface CheckpointTransport {
  checkpoint(): Promise<Result<CheckpointResponse | null, LakeSyncError>>;
}

Checkpoint downloads for initial sync. Returns null if no checkpoint is available.

RealtimeTransport

interface RealtimeTransport {
  readonly supportsRealtime: boolean;
  onBroadcast(callback: (deltas: RowDelta[], serverHlc: HLCTimestamp) => void): void;
  connect(): void;
  disconnect(): void;
}

Real-time server-initiated broadcast support. Implemented by WebSocketTransport.

ActionTransport

interface ActionTransport {
  executeAction(msg: ActionPush): Promise<Result<ActionResponse, LakeSyncError>>;
  describeActions(): Promise<Result<ActionDiscovery, LakeSyncError>>;
  listConnectorTypes(): Promise<Result<ConnectorDescriptor[], LakeSyncError>>;
}

Imperative action execution and connector discovery. See Actions for usage.

TransportWithCapabilities

type TransportWithCapabilities = SyncTransport &
  Partial<CheckpointTransport> &
  Partial<RealtimeTransport> &
  Partial<ActionTransport>;

Union type used by SyncCoordinator. Transports must implement push/pull; other capabilities are detected at runtime.

LocalTransport

In-process transport for development and testing. Connects directly to a gateway instance.

class LocalTransport implements SyncTransport, CheckpointTransport, ActionTransport {
  constructor(gateway: LocalGateway);
}

interface LocalGateway {
  handlePush(msg: SyncPush): Result<{ serverHlc: HLCTimestamp; accepted: number }, LakeSyncError>;
  handlePull(msg: SyncPull): Result<SyncResponse, LakeSyncError>;
  handleAction?(msg: ActionPush, context?: AuthContext): Promise<Result<ActionResponse, ActionValidationError>>;
  describeActions?(): ActionDiscovery;
}

HttpTransport

Remote transport that communicates with a gateway over HTTP.

class HttpTransport implements SyncTransport, CheckpointTransport, ActionTransport {
  constructor(config: HttpTransportConfig);
}

interface HttpTransportConfig {
  baseUrl: string;
  gatewayId: string;
  token: string;
  fetch?: typeof globalThis.fetch;
}

WebSocketTransport

Real-time transport using a persistent WebSocket connection. Supports server-initiated broadcast for sub-second sync latency.

class WebSocketTransport implements SyncTransport, RealtimeTransport, CheckpointTransport {
  constructor(config: WebSocketTransportConfig);

  /** Whether the WebSocket is currently connected. */
  readonly connected: boolean;
  /** Always true — this transport supports real-time. */
  readonly supportsRealtime: boolean;

  connect(): void;
  disconnect(): void;
  onBroadcast(callback: (deltas: RowDelta[], serverHlc: HLCTimestamp) => void): void;
}

interface WebSocketTransportConfig {
  url: string;
  token: string;
  onBroadcast?: (deltas: RowDelta[], serverHlc: HLCTimestamp) => void;
  reconnectBaseMs?: number;   // default 1000
  reconnectMaxMs?: number;    // default 30000
  httpConfig?: HttpTransportConfig;
}
  • url — WebSocket endpoint, e.g. "wss://gateway.example.com/sync/gw-1/ws".
  • token — JWT token, passed as ?token= query parameter (browser WebSocket cannot set headers).
  • httpConfig — When provided, checkpoint() delegates to an internal HttpTransport. Without it, checkpoint returns null.
  • Auto-reconnect — On disconnect, reconnects with exponential backoff (1s base, 30s max). Resets on successful connection.

See Real-Time Sync for a full guide.

Queues

SyncQueue (interface)

interface SyncQueue {
  push(delta: RowDelta): Promise<Result<QueueEntry, LakeSyncError>>;
  peek(limit: number): Promise<Result<QueueEntry[], LakeSyncError>>;
  markSending(ids: string[]): Promise<Result<void, LakeSyncError>>;
  ack(ids: string[]): Promise<Result<void, LakeSyncError>>;
  nack(ids: string[]): Promise<Result<void, LakeSyncError>>;
  depth(): Promise<Result<number, LakeSyncError>>;
  clear(): Promise<Result<void, LakeSyncError>>;
}

interface QueueEntry {
  id: string;
  delta: RowDelta;
  status: "pending" | "sending" | "acked";
  createdAt: number;
  retryCount: number;
  retryAfter?: number;
}

MemoryQueue

In-memory queue for development and testing. Deltas are lost on page refresh.

class MemoryQueue implements SyncQueue {
  // All SyncQueue methods
}

IDBQueue

IndexedDB-backed queue for production use. Persists deltas across page refreshes.

class IDBQueue implements SyncQueue {
  constructor(dbName?: string);
  // All SyncQueue methods
}

Note: IDBQueue serialises HLCTimestamp to string internally because IndexedDB's structuredClone cannot handle bigint.

ActionQueue (interface)

Outbox-pattern queue for imperative actions. Required by SyncCoordinator.executeAction().

interface ActionQueue {
  push(action: Action): Promise<Result<ActionQueueEntry, LakeSyncError>>;
  peek(limit: number): Promise<Result<ActionQueueEntry[], LakeSyncError>>;
  markSending(ids: string[]): Promise<Result<void, LakeSyncError>>;
  ack(ids: string[]): Promise<Result<void, LakeSyncError>>;
  nack(ids: string[]): Promise<Result<void, LakeSyncError>>;
  depth(): Promise<Result<number, LakeSyncError>>;
  clear(): Promise<Result<void, LakeSyncError>>;
}

MemoryActionQueue

In-memory action queue for development and testing.

class MemoryActionQueue implements ActionQueue {}

IDBActionQueue

IndexedDB-backed action queue for production use. Persists actions across page refreshes.

class IDBActionQueue implements ActionQueue {
  constructor(dbName?: string);
}

Schema Synchronisation

SchemaSynchroniser

Handles client-side schema migrations by comparing local table schemas against the gateway's schema version.

class SchemaSynchroniser {
  constructor(db: LocalDB);
  synchronise(
    table: string,
    serverSchema: TableSchema,
    serverVersion: number,
  ): Promise<Result<void, LakeSyncError>>;
}

Compares the locally stored schema version against the server's version and applies additive migrations (ALTER TABLE ... ADD COLUMN) when the client is behind.