> ## Documentation Index
> Fetch the complete documentation index at: https://chahine.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# MeridianClient

> Create and manage a Meridian client connection.

## Create a client

`MeridianClient.create()` parses and validates the token before opening the WebSocket. It returns an `Effect`.

```ts theme={null}
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

| Option | Type | Default | Description |
| - | - | - | - |
| `url` | `string` | — | Server base URL (`http://` or `ws://`, both work) |
| `namespace` | `string` | — | Namespace to connect to |
| `token` | `string` | — | Meridian token |
| `autoConnect` | `boolean` | `true` | Open WebSocket immediately |
| `offline` | `boolean` | `false` | Never auto-connect — work from local cache, call `connect()` manually |
| `persistence` | `PersistenceConfig` | — | Opt-in local persistence — see [Offline-first](https://chahine.mintlify.site/sdk/offline.md) |

## Properties

```ts theme={null}
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.

```ts theme={null}
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](https://chahine.mintlify.site/sdk/crdts/awareness.md).

## HTTP client

```ts theme={null}
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):

```ts theme={null}
// 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:

```ts theme={null}
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](https://chahine.mintlify.site/self-hosting/tokens.md) for the equivalent `curl` call and full field reference.

## Wait for connection

```ts theme={null}
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.

```ts theme={null}
// 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

```ts theme={null}
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

```ts theme={null}
client.close(); // gracefully closes the WebSocket, clears the pending queue
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.