Github|...

Configuration

Sp00ky has two configuration surfaces. sp00ky.yml lives at your project root and is read by the spky CLI to provision SurrealDB, the SSP, codegen, and your apps. SyncedDbConfig lives in your client app and is passed to new SyncedDb(...) to customize logging, persistence, and sync timings.

The sp00ky.yml file

sp00ky.yml is the single source of truth for the spky CLI: it tells spky dev, spky deploy, and spky doctor how to run your stack, which SurrealDB version to spin up, where your schema and buckets live, which client types to generate, and how your frontend and backend apps boot. Run spky doctor at any time to validate the file.

Add the schema comment as the first line of sp00ky.yml to get autocomplete and validation in editors that support yaml-language-server:

yaml
# yaml-language-server: $schema=https://sp00ky.cloud/schema/sp00ky.schema.json

Minimal example

The smallest viable file to get spky dev running:

yaml
mode: singlenode
surrealdb:
  namespace: main
  database: main
schema: ./schema
buckets:
  - ./schema/src/buckets/profile.surql
clientTypes:
  - format: typescript
    output: ./src/schema.gen.ts
  - format: dart
    output: ../app/lib/sync/app_db.g.dart

For a fully annotated reference covering every option, see example/sp00ky.yml in the repo.

Top-level settings

FieldTypeDefaultPurpose
modesinglenode | cluster | surrealismsinglenodeDeployment topology. singlenode runs one SSP, cluster runs multiple, surrealism is for embedded setups.
slugstring(none)Project slug used by spky deploy and the other cloud commands.
surrealdbobject(none)DB connection. Sub-fields: namespace, database, username, password, hosting (cloud or external), endpoint (required when hosting: external).
schemapath(none)Directory containing your .surql schema sources. See Schema.
bucketspath[][]Bucket definition files referenced from your schema. See Buckets.
clientTypesarray[]Codegen targets. Each entry has format (typescript or dart), an output path, and (dart only) an optional workdir. See Client types.
versionstring | objectlatest stablePin SSP and scheduler image tags. Accepts a single tag, a {dev, cloud} split, or a path: override pointing at a local binary.
logLevelstring | objectinfoLog verbosity. Accepts trace, debug, info, warn, error, off, target=level directives, or a {dev, cloud} split.
refModededicated | singlededicatedStorage layout for the SSP’s _00_list_ref table. dedicated gives each user their own table and works around a SurrealDB v3 LIVE-permission gap.
appsmap{}Your frontend and backend services. See Apps below.
deploymentobject(none)Cloud deployment knobs: sspCount, backup. See Cloud Deployment.

Apps

Each entry under apps is a service spky knows how to run locally and deploy to the cloud. Set type: frontend, type: backend, or type: docker, then describe how it boots (dev) and how it deploys (deploy). Backends additionally need an OpenAPI spec, a baseUrl, an optional auth block, and a method (typically outbox) so the SSP can deliver jobs.

yaml
apps:
  web:
    type: frontend
    dev:
      type: npm
      script: dev:app
    deploy:
      dockerfile: ./app/Dockerfile
      context: ../
      port: 80
  api:
    type: backend
    spec: ./api/openapi.yml
    baseUrl: http://host.docker.internal:3660
    dev:
      type: npm
      script: dev
      workdir: ./api
    deploy:
      dockerfile: ./api/Dockerfile
      context: ../
      port: 3660
      healthcheck: /health
    auth:
      type: token
      token: THIS_IS_TOP_SECRET
    method:
      type: outbox
      table: job
      schema: ./schema/src/outbox/api.surql
  livekit:
    type: docker
    scope: devOnly
    image: livekit/livekit-server:latest
    ports: [7880, 7881, "7882/udp"]
    args: ["--dev", "--bind", "0.0.0.0"]

dev accepts either a raw command string or a typed form: {type: npm, script, workdir?}, {type: docker, file, workdir?, port?} (builds a Dockerfile for this app’s dev server), or {type: uv, script, workdir?}. deploy accepts dockerfile, context, port, healthcheck, resources (vcpus, memory, disk), and a timeout.

scope — where an app runs

Every app accepts an optional scope controlling where it runs:

scopeBehavior
all (default)Started by spky dev and deployed to the cloud.
devOnlyLocal-only — started by spky dev, never deployed. Validation is relaxed (no spec/method/deploy required), so it’s handy for a local sidecar.
cloudOnlyDeployed to the cloud but skipped by spky dev.

type: docker — run a prebuilt image

A docker app runs a prebuilt image directly (no Dockerfile build) — useful for local infra like a LiveKit SFU, a mock service, or a cache. Fields:

  • image (required) — the image to run, e.g. livekit/livekit-server:latest.
  • ports — published ports. A bare port (7880) maps host→container 1:1 (7880:7880); use "host:container" to remap and a /proto suffix for UDP ("7882/udp").
  • args — appended after the image as the container command (e.g. ["--dev"], or ["go", "run", "."] to run a service from source).
  • volumes — bind/volume mounts (docker run -v), e.g. ["/var/run/docker.sock:/var/run/docker.sock", "${PROJECT_DIR}/../..:/src", "gomod:/go"]. ${PROJECT_DIR} (the absolute directory of sp00ky.yml) is expanded in the host portion.
  • workdir — working directory inside the container (docker run -w).
  • dependsOn — names of other docker apps that must be ready before this one starts. spky dev launches apps in dependency order; an unknown name, a self-dependency, or a cycle is rejected at config load (caught by spky lint/spky doctor).
  • healthcheck — an HTTP path (e.g. /health) polled on the app’s first published host port until it returns 200. A dependsOn waits for this — i.e. for the service to be genuinely up, not just for the container to have started. Without it, a dependency is “ready” as soon as its container is running.

Under spky dev it runs as the container sp00ky-dev-<name> on the dev network (reachable from sibling apps by its name) with --rm teardown. ${PROJECT_DIR} is also expanded in env values, and user env overrides the auto-injected SPKY_* vars. With scope left as all/cloudOnly, deploys pull the image and ship it through the same image pipeline as a backend; pair it with scope: devOnly for a purely local service (dependsOn/healthcheck are spky dev-only). env (below) is passed through as container environment variables.

For per-app environment variables (env: { dev, cloud, vault }), see Environment Variables.

Client types

clientTypes is a list of codegen targets; spky generate (run with no args) regenerates every entry from your schema. Each entry has a format and an output path (relative to sp00ky.yml):

  • typescript — the CLI’s built-in generator emits a typed schema module (e.g. schema.gen.ts).
  • dart — runs spooky_core’s richer generator (dart run spooky_core:spooky_gen), which emits the typed client, Patch classes, and spookySchema/surqlSchema. Because that tool resolves spooky_core from the enclosing Dart package, it runs from the package directory — auto-derived from output’s nearest ancestor pubspec.yaml. Set the optional workdir only to override that. (This replaces a manual dart run spooky_core:spooky_gen / Makefile step.)

A project can list several outputs — e.g. a TypeScript web client plus one or more Dart clients (a Flutter app, a renderer) — and keep them all in sync from one command.

Per-environment overrides

version, logLevel, and apps.<name>.env each accept a {dev, cloud} split so your local stack can differ from what runs in Sp00ky Cloud. A common pattern is to run host-built binaries during spky dev and a pinned image tag in the cloud:

yaml
version:
  dev:
    ssp:
      path: ../target/debug/ssp-server
    scheduler:
      path: ../target/debug/scheduler
  cloud: canary

logLevel:
  dev: trace
  cloud: info

apps:
  api:
    env:
      dev: '.env.local'
      cloud:
        vault:
          - 'API_AUTH_TOKEN'

Client SDK (SyncedDbConfig)

SyncedDbConfig (sometimes referred to as Sp00kyConfig in older docs) is the object you pass to new SyncedDb(...) in your client app to customize logging, persistence, and sync timings.

Configuration Interface

TypeScript
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

TypeScript
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)

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 metadata like query states are persisted:

  • 'surrealdb' (default): Store in the local SurrealDB instance
  • 'localstorage': Use browser localStorage
  • Custom implementation of PersistenceClient interface

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.