Skip to main content

Create a client

MeridianClient.create() parses and validates the token before opening the WebSocket. It returns an Effect.
import { Effect } from "effect";
import { MeridianClient } from "meridian-sdk";

const client = await Effect.runPromise(
  MeridianClient.create({
    url: "http://localhost:3000",
    namespace: "my-room",
    token: process.env.MERIDIAN_TOKEN!,
  })
);

Config

OptionTypeDefaultDescription
urlstringServer base URL (http:// or ws://, both work)
namespacestringNamespace to connect to
tokenstringMeridian token
autoConnectbooleantrueOpen WebSocket immediately
offlinebooleanfalseNever auto-connect — work from local cache, call connect() manually
persistencePersistenceConfigOpt-in local persistence — see Offline-first

Properties

client.namespace  // string
client.clientId   // number
client.claims     // TokenClaims — full parsed token claims
client.http       // HttpClient — REST API access

CRDT handles

All handles are cached by crdtId — calling the same method twice returns the same instance.
client.gcounter("id")               // GCounterHandle
client.pncounter("id")              // PNCounterHandle
client.orset("id", schema?)         // ORSetHandle<T>
client.lwwregister("id", schema?)   // LwwRegisterHandle<T>
client.presence("id", schema?)      // PresenceHandle<T>
client.crdtmap("id")                // CRDTMapHandle
client.awareness("key", schema?)    // AwarenessHandle<T>
For orset, lwwregister, presence, and awareness, passing an Effect Schema enables runtime validation of incoming payloads. Without a schema, T = unknown. awareness handles are keyed by a logical channel name (e.g. "cursors", "selection:doc-1") rather than a CRDT id — they are ephemeral and not persisted. See Awareness.

HTTP client

client.http.getCrdt(ns, id)           // → Effect<CrdtGetResponse, HttpError | NetworkError>
client.http.postOp(ns, id, op)        // → Effect<CrdtOpResponse, HttpError | NetworkError>
client.http.syncCrdt(ns, id, sinceVc) // → Effect<CrdtGetResponse, HttpError | NetworkError>
client.http.issueToken(ns, opts)      // → Effect<TokenIssueResponse, HttpError | NetworkError>
client.http.tokenMe(ns)               // → Effect<TokenClaims, HttpError | NetworkError>

Issuing tokens

issueToken accepts both V1 (glob lists) and V2 (fine-grained rules):
// V1
client.http.issueToken("shop", {
  client_id: 42,
  ttl_ms: 3_600_000,
  permissions: { read: ["*"], write: ["or:cart-42"], admin: false },
});

// V2 — op masks, per-rule TTLs, {clientId} template, rate limit
client.http.issueToken("shop", {
  client_id: 42,
  ttl_ms: 3_600_000,
  rules: {
    r: [{ p: "*" }],
    w: [{ p: "or:cart-{clientId}", o: 0x01 | 0x02 }],
    rl: 200,
  },
});

Inspecting the current token

tokenMe decodes and returns the claims of the caller’s own token — useful for debugging without msgpack tooling:
const claims = await Effect.runPromise(client.http.tokenMe("shop"));
console.log(claims.client_id);    // 42
console.log(claims.expires_at);   // Unix ms
console.log(claims.permissions);  // V1 or V2 object
See Tokens for the equivalent curl call and full field reference.

Wait for connection

await client.waitForConnected();          // default 5s timeout
await client.waitForConnected(10_000);    // custom timeout in ms
Resolves when the WebSocket reaches CONNECTED state. Useful in tests or when you need to send an operation immediately after creating the client (before the connection handshake completes). Throws Error: WsTransport: connect timeout if the timeout elapses.

Offline queue

Operations sent while disconnected are buffered automatically and flushed in order on reconnect — no configuration needed.
// Number of ops waiting to be sent
client.pendingOpCount; // number

// Subscribe to connection state changes (returns an unsubscribe function)
const unsub = client.onStateChange(state => {
  // state: "CONNECTED" | "DISCONNECTED" | "CONNECTING" | "CLOSING"
});
unsub();
The queue holds up to 500 ops. If the limit is reached, the oldest op is dropped to make room.

Op latency

const stats = client.getLatencyStats();
// { p50: 12.4, p99: 87.1, count: 64 } | null
Returns P50 and P99 round-trip latency in milliseconds from the last 128 acknowledged ops, or null if fewer than 2 samples have been collected. Latency is measured from send to server Ack receipt — no clock synchronisation required. The meridian-devtools panel displays this automatically in the Connection section.

Close

client.close(); // gracefully closes the WebSocket, clears the pending queue