Github|...

Client config

SyncedDbConfig, the object you hand to new SyncedDb(): connection, local store, log level and sync timings.

SyncedDbConfig is the client-side counterpart to sp00ky.yml. It is passed to new SyncedDb(...) in your app and controls the database connection, where data is cached locally, and how aggressively sync batches its work. Older docs sometimes call it Sp00kyConfig.

Configuration Interface

interface SyncedDbConfig<Schema> {
  /** The runtime schema generated by the CLI */
  schema: Schema;

  /** The raw SURQL schema string (also generated) */
  schemaSurql: string;

  /** Logging verbosity level */
  logLevel: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';

  /** Database connection configuration */
  database: {
    /** The SurrealDB endpoint URL */
    endpoint?: string;

    /** The namespace to use */
    namespace: string;

    /** The database name */
    database: string;

    /** The local store type implementation */
    store?: 'memory' | 'indexeddb';

    /** Authentication token (optional) */
    token?: string;
  };

  /** Persistence client: 'surrealdb' (default), 'localstorage', or custom */
  persistenceClient?: 'surrealdb' | 'localstorage' | PersistenceClient;

  /** A pino browser transmit object for forwarding logs (e.g. via @spooky-sync/core/otel) */
  otelTransmit?: PinoTransmit;

  /** Debounce window for outgoing DBSP stream updates (default: 50ms) */
  streamDebounceTime?: number;

  /** Debounce window for CRDT field UPSERTs to upstream (default: 500ms) */
  crdtDebounceMs?: number;

  /**
   * Cadence (ms) for the `_00_list_ref` poll that catches cross-session
   * UPDATEs missed by the SurrealDB v3 LIVE-permission gap. Defaults
   * to 500ms; non-positive values fall back to the default.
   */
  refSyncIntervalMs?: number;
}

Example Configuration

import { SyncedDb, type SyncedDbConfig } from '@spooky-sync/client-solid';
import { schema, SURQL_SCHEMA } from './schema.gen';

import { createOtelTransmit } from '@spooky-sync/core/otel';

const dbConfig: SyncedDbConfig<typeof schema> = {
  logLevel: 'info',
  schema: schema,
  schemaSurql: SURQL_SCHEMA,
  database: {
    namespace: 'main',
    database: 'main',
    endpoint: 'ws://localhost:8666/rpc',
    store: 'indexeddb', // or 'memory' for transient storage
  },
  // Optional: forward logs to OpenTelemetry
  otelTransmit: createOtelTransmit('http://localhost:4318/v1/logs'),
};

export const db = new SyncedDb<typeof schema>(dbConfig);

Options Detail

database.endpoint

The WebSocket URL for your SurrealDB instance (e.g., ws://localhost:8666/rpc when running locally with the Sp00ky CLI’s bundled dev stack). The Sp00ky client connects directly to the database. Optional, and when omitted the client connects in offline-only mode and queues mutations until a remote is configured.

database.store

  • 'indexeddb' (default): Persistent local storage in the browser
  • 'memory': Transient in-memory storage (clears on page reload)

With 'indexeddb', the local store is bucketed per user: each account gets its own IndexedDB database (sp00ky-<userId>, with a shared sp00ky-anon bucket for signed-out sessions), and the client switches buckets automatically on sign-in/sign-out. Cached rows, query state, and the offline mutation outbox never leak between accounts on a shared device, and a user’s un-pushed offline mutations resume the next time they sign in. See Architecture → Per-User Local Buckets. With 'memory', an auth change resets the store instead (same isolation, no persistence).

logLevel

Controls the verbosity of logging output:

  • 'trace': Most verbose, includes all internal operations
  • 'debug': Detailed debugging information
  • 'info': General lifecycle events (connected, synced)
  • 'warn': Recoverable errors and warnings
  • 'error': Critical failures
  • 'fatal': Fatal errors that stop execution

persistenceClient

Determines where small metadata values (the auth token, the stream processor’s persisted state) are stored:

  • 'localstorage' (default): Use browser localStorage
  • 'surrealdb': Store in the local SurrealDB instance (inside the current user’s local bucket)
  • Custom implementation of PersistenceClient interface

Note: the boot-time bucket hint (sp00ky:last_bucket) is always written to plain localStorage regardless of this setting, because the client needs it to pick the right per-user bucket before any bucket is open.

streamDebounceTime

Window (ms) the client-side Stream Processor uses to coalesce DBSP stream updates per query before they fan out to useQuery subscribers, so a burst of synced records (e.g. the initial library sync) repaints affected views once per window instead of row-by-row. Lower values feel snappier; higher values reduce re-render thrash on bursty writes. Defaults to 50.

crdtDebounceMs

Window (ms) used to coalesce CRDT field UPSERTs to upstream. Local writes are applied to the LoroDoc immediately on every keystroke (so reload and offline work), but the remote _00_crdt UPSERT is debounced. Defaults to 500.

refSyncIntervalMs

Cadence (ms) for the _00_list_ref poll that catches cross-session UPDATEs that the SurrealDB v3 LIVE-permission gap drops. Defaults to 500; non-positive values fall back to the default. The poll self-throttles to a longer interval when LIVE is delivering events healthily (see Architecture).

Diagnostics

The client exposes a small number of read-only getters intended for e2e regression guards and runtime instrumentation.

pendingMutationCount and subscribeToPendingMutations

Live count of mutations queued for upstream sync. The subscribe variant fires on enqueue/dequeue so a UI can render a “saving…” indicator without polling.

liveRetryCount

Number of times the initial _00_list_ref[_user_*] LIVE SELECT subscription had to retry on the most recent setCurrentUserId call. Stays at 0 when the SSP’s pre-emptive per-user table creation got there first (the default path); a value above 0 means the LIVE registration hit a “table not found” race and the client fell through to the poll fallback while retrying. The e2e suite’s z-auth-bootstrap.spec.ts asserts this stays 0 after a clean signup so the pre-emptive path can’t silently regress.