Client library

@thuum/client

The TypeScript client for browsers, Node.js, Deno and Bun. Tokens, domain clients, REST helpers, sockets and typed errors.

@thuum/client is a small, dependency-free TypeScript client for the whole API. It only needs fetch (and WebSocket for sockets), so it runs in browsers, Node.js 22+, Deno and Bun. Every REST route has a typed method; both WebSockets have a socket class with promise-based operations.

Create a client#

TypeScript
import { ThuumClient } from '@thuum/client';

const thuum = new ThuumClient({ baseUrl: 'https://api.thuum.dev' });
OptionMeaning
baseUrlThe API's base URL. A trailing / is ignored.
fetchA custom fetch (e.g. a framework's request-scoped one). Defaults to the global.

Tokens and domain clients#

Exchange a key for a token, then create a domain client bound to that token:

TypeScript
const { token, expires_in } = await thuum.exchangeToken('acme', process.env.THUUM_APP_KEY!);
const acme = thuum.domain('acme', token, {
	onError(error) {
		if (isApiError(error, 401)) {
			// The token expired or the key was deleted: get a new token, sign the user out, …
		}
	}
});

const me = await acme.me();

A domain client is cheap: create one per token. onError sees every error before it is thrown, which is a good place to handle 401s once for the whole app.

In a browser, you usually don't exchange a key at all: your server hands the browser a token, and the browser opens a socket with it. You don't even need a ThuumClient there:

TypeScript
import { EventBrokerSocket } from '@thuum/client';

const { token } = await fetch('/api/thuum-token').then((r) => r.json());
const socket = new EventBrokerSocket({ baseUrl: 'https://api.thuum.dev', domain: 'acme', token });

What a domain client offers#

GroupMethodsNeeds
me()—any token
eventBroker.datalist, publish, purgeEvent Broker data R, P, W
eventBroker.topicslist, get, put, deleteEvent Broker topic flags R / W
eventBroker.connect()returns an EventBrokerSocketan Event Broker claim
stateBroker.datalist, get, set, merge, delete, echoState Broker data R, P
stateBroker.topicslist, get, put, deleteState Broker topic flags R / W
stateBroker.connect()returns a StateBrokerSocketa State Broker claim
domain.keyslist, get, create, deleteDomain Claim domain R / W
domain.claimslist, get, create, update, delete, protocols.put/delete, keys.link/unlinkDomain Claim domain R / W
eventBroker.claims, .keysclaims CRUD, topics.put/delete, keys.link/unlink; key pickerDomain Claim event R / W
stateBroker.claims, .keyssame, for State Broker claimsDomain Claim state R / W

Every method returns the response's data, typed. Full signatures are in the reference.

TypeScript
const event = await acme.eventBroker.data.publish('orders:1042', {
	type: 'order.shipped',
	data: { carrier: 'DHL' }
});
const history = await acme.eventBroker.data.list('orders', {
	since: new Date(Date.now() - 3_600_000)
});

const state = await acme.stateBroker.data.merge('orders:1042', 'status', { stage: 'shipped' });
const states = await acme.stateBroker.data.list('orders', { prefix: 'status', limit: 1000 });

Time filters accept epoch milliseconds or Date objects.

Errors#

Every error extends ThuumError:

ClassWhenUseful fields
ThuumApiErrorThe API answered with a non-2xx status.status, message (the API's detail), errors, fieldErrors
ThuumConnectionErrorNo response at all (DNS, refused, aborted).url, cause
EventBrokerSocketError, StateBrokerSocketErrorA socket op failed, or the socket closed first.code
TypeScript
import { isApiError, ThuumConnectionError } from '@thuum/client';

try {
	await acme.stateBroker.data.get('orders:1042', 'status');
} catch (e) {
	if (isApiError(e, 404)) return null; // no such state
	if (isApiError(e, 422)) console.error(e.fieldErrors); // { topic: ['must be …'] }
	if (e instanceof ThuumConnectionError) retryLater();
	throw e;
}

Node.js#

Node.js 22 and later have a global WebSocket, so sockets work as in the browser. Elsewhere, pass any standard-compatible implementation, such as the ws package:

TypeScript
import WebSocket from 'ws';

const socket = acme.eventBroker.connect({
	WebSocket: WebSocket as unknown as typeof globalThis.WebSocket
});

Keep App Keys in environment variables or a secret manager, exchange them on the server only, and see Backend integration for long-running workers.

Low-level requests#

thuum.request(method, path, { token, body }) calls any route and returns its data, with the same error handling. Useful for routes added after your client version:

TypeScript
const data = await thuum.request('GET', '/api/acme/me', { token });