Skip to main content
WebSocketClient is an Effect service that wraps Convex’s ConvexClient. It provides the same query, mutation, and action methods as HttpClient, plus a reactiveQuery method that returns a Stream of live results. It works in any JavaScript environment that supports WebSocket. The WebSocket connection is managed as a scoped resource β€” 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.
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.
import { WebSocketClient } from "@confect/js";
import { Effect } from "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.
import { WebSocketClient } from "@confect/js";
import { Console, Effect, Stream } from "effect";

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

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.
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.
import { WebSocketClient } from "@confect/js";
import { Console, Effect, Stream } from "effect";

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

ConvexClientWebSocketClient
Functions referenced via api.module.fnFunctions referenced via refs
Args passed directly to Convex as-isArgs are schema-encoded from Type to Encoded before sending
Return values received directly as-isReturn values are schema-decoded from Encoded to Type
query / mutation / action return Promisequery / mutation / action return Effect with typed errors
onUpdate uses callbacks and returns an unsubscribe functionreactiveQuery returns a Stream with automatic cleanup
Must call close() manuallyConnection closed automatically when the layer’s scope ends
setAuth takes a Promise-returning callbacksetAuth takes an Effect-returning function