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

# WebSocket

> Call your Confect functions and subscribe to reactive queries over a persistent WebSocket connection.

`WebSocketClient` is an [Effect service](https://effect.website/docs/requirements-management/services/) that wraps Convex's [`ConvexClient`](https://docs.convex.dev/api/classes/browser.ConvexClient). It provides the same `query`, `mutation`, and `action` methods as [`HttpClient`](/clients/js/http), plus a `reactiveQuery` method that returns a [`Stream`](https://effect.website/docs/stream/introduction/) of live results. It works in any JavaScript environment that supports `WebSocket`.

The WebSocket connection is managed as a [scoped resource](https://effect.website/docs/resource-management/scope/)—it is opened when the layer is provided and closed automatically when the scope ends.

## Setup

Create the `WebSocketClient` layer by passing your Convex deployment URL.

```ts theme={null}
import { WebSocketClient } from "@confect/js";

const WebSocketClientLive = WebSocketClient.layer(
  "https://example-123.convex.cloud",
);
```

The layer can then be provided to any Effect that uses the `WebSocketClient` service. The underlying WebSocket connection is closed automatically when the layer's scope ends—there is no need to close it manually.

## Calling functions

Use the `WebSocketClient` service inside `Effect.gen` to call your functions with refs, the same way you would with `HttpClient`, `@confect/react` hooks, or `@confect/test`.

```ts theme={null}
import { WebSocketClient } from "@confect/js";
import * as Effect from "effect/Effect";

import refs from "./confect/_generated/refs";

const program = Effect.gen(function* () {
  const client = yield* WebSocketClient.WebSocketClient;

  const notes = yield* client.query(refs.public.notes.list);

  const noteId = yield* client.mutation(refs.public.notes.insert, {
    text: "Hello from the server",
  });

  const result = yield* client.action(refs.public.random.getNumber);
});
```

Each method returns an `Effect` that can fail with `WebSocketClientError` (wrapping transport-level errors) or `ParseResult.ParseError` (if schema encoding or decoding fails).

## Reactive queries

`reactiveQuery` subscribes to a query over the WebSocket connection and returns a `Stream` that emits a new value whenever the query result changes on the server.

```ts theme={null}
import { WebSocketClient } from "@confect/js";
import * as Console from "effect/Console";
import * as Effect from "effect/Effect";
import * as Stream from "effect/Stream";

import refs from "./confect/_generated/refs";

const program = Effect.gen(function* () {
  const client = yield* WebSocketClient.WebSocketClient;

  yield* client
    .reactiveQuery(refs.public.notes.list)
    .pipe(Stream.runForEach((notes) => Console.log(notes)));
});
```

The underlying WebSocket subscription is cleaned up automatically when the stream's scope ends (for example, when the consuming Effect is interrupted or when an operator like `Stream.take` completes).

## Typed errors

When a ref's spec declares an `error` schema, the decoded error is added to the error channel of `query`, `mutation`, `action`, and `reactiveQuery` alongside `WebSocketClientError` and `ParseError`. See [Error Handling](/server/error-handling) for how to declare error schemas.

```ts theme={null}
import { WebSocketClient } from "@confect/js";
import * as Effect from "effect/Effect";

import refs from "./confect/_generated/refs";

const lookup = Effect.gen(function* () {
  const client = yield* WebSocketClient.WebSocketClient;

  return yield* client.query(refs.public.notes.getOrFail, { noteId });
});
// Effect.Effect<Note, NoteNotFound | WebSocketClientError | ParseError, WebSocketClient.WebSocketClient>
```

For `reactiveQuery`, a typed failure terminates the `Stream` with the decoded value in its error channel—the subscription does not stay open across failures. Recover with `Stream.catchTag` (or any other `Stream` error combinator) to keep the stream alive across typed failures.

```ts theme={null}
import { WebSocketClient } from "@confect/js";
import * as Effect from "effect/Effect";
import * as Stream from "effect/Stream";

import refs from "./confect/_generated/refs";

const lookups = Effect.gen(function* () {
  const client = yield* WebSocketClient.WebSocketClient;

  return client
    .reactiveQuery(refs.public.notes.getOrFail, { noteId })
    .pipe(
      Stream.catchTag("NoteNotFound", (error) =>
        Stream.succeed(`Note ${error.noteId} not found.`),
      ),
    );
});
```

## Authentication

Set the authentication token provider before making authenticated requests. `setAuth` accepts an Effect-returning function that is called whenever a token is needed or expires.

```ts theme={null}
Effect.gen(function* () {
  const client = yield* WebSocketClient.WebSocketClient;

  yield* client.setAuth(
    ({ forceRefreshToken }) =>
      Effect.promise(() => getToken({ forceRefreshToken })),
    (isAuthenticated) => Console.log(`Auth state changed: ${isAuthenticated}`),
  );

  const identity = yield* client.query(refs.public.auth.getIdentity);
});
```

The optional second argument is a function that receives the current `isAuthenticated` status and returns an `Effect` to run whenever the authentication state changes.

## Running programs

Provide the `WebSocketClient` layer when running your program.

```ts theme={null}
import { WebSocketClient } from "@confect/js";
import * as Console from "effect/Console";
import * as Effect from "effect/Effect";
import * as Stream from "effect/Stream";

const WebSocketClientLive = WebSocketClient.layer(
  "https://example-123.convex.cloud",
);

const program = Effect.gen(function* () {
  const client = yield* WebSocketClient.WebSocketClient;

  yield* client.reactiveQuery(refs.public.notes.list).pipe(
    Stream.take(10),
    Stream.runForEach((notes) => Console.log(notes)),
  );
});

Effect.runPromise(program.pipe(Effect.provide(WebSocketClientLive)));
```

## Differences from `ConvexClient`

| `ConvexClient`                                                | `WebSocketClient`                                               |
| ------------------------------------------------------------- | --------------------------------------------------------------- |
| Functions referenced via `api.module.fn`                      | Functions referenced via `refs`                                 |
| Args passed directly to Convex as-is                          | Args are schema-encoded from `Type` to `Encoded` before sending |
| Return values received directly as-is                         | Return values are schema-decoded from `Encoded` to `Type`       |
| `query`/`mutation`/`action` return `Promise`                  | `query`/`mutation`/`action` return `Effect` with typed errors   |
| `onUpdate` uses callbacks and returns an unsubscribe function | `reactiveQuery` returns a `Stream` with automatic cleanup       |
| Must call `close()` manually                                  | Connection closed automatically when the layer's scope ends     |
| `setAuth` takes a `Promise`-returning callback                | `setAuth` takes an `Effect`-returning function                  |
