JavaScript SDK
@runnev/client is the official JavaScript and TypeScript SDK. Zero runtime
dependencies, ships ESM and CommonJS, and runs in Node 18+ and modern browsers on the
built-in fetch and ReadableStream.
Install
npm install @runnev/client # v1.6.1Types are bundled; there is no @types package to add.
Configure
import { Runnev } from "@runnev/client";
const runnev = new Runnev({
apiKey: process.env.RUNNEV_API_KEY, // required
baseUrl: "https://runnev.dev/v1", // default
timeoutMs: 30000, // per-request timeout (not the subscribe stream)
maxRetries: 4, // retries on 429 / 5xx, with backoff
});In the browser, construct it with the read-only demo key only; never ship a
rnv_live_ key to a client device.
Publish
// create (server-assigned id)
const stream = await runnev.createStream({ name: "orders-eu", retentionSeconds: 86400 });
// publish at an explicit sequence - idempotent, safe to retry
const res = await runnev.publish(stream.id, 41823, [
{ type: "order.paid", id: "o_5521", amount: 1999 },
]);
console.log(res.cursor, res.duplicate); // 41823 false
// server-assigned sequence
await runnev.publishAuto(stream.id, [{ type: "tick" }]);
// raw bytes for a raw-mode stream
await runnev.publishRaw(stream.id, 9001, new Uint8Array(protobufBytes));Subscribe
Subscribe returns an async iterable and also accepts callbacks. It reconnects automatically, resuming from the last sequence it saw.
// async iteration
for await (const batch of runnev.subscribe(stream.id, { cursor: 0 })) {
console.log(batch.seq, batch.events);
}
// or callbacks, with an AbortSignal to stop
const controller = new AbortController();
runnev.subscribe(stream.id, {
cursor: "head",
signal: controller.signal,
onOpen: () => console.log("connected"),
onBatch: (batch) => console.log(batch.seq, batch.events),
onError: (err) => console.error(err.code, err.requestId),
});
// later: controller.abort();Resume across restarts
let last = loadCursor() ?? -1; // from your durable store
for await (const batch of runnev.subscribe(stream.id, { cursor: last })) {
if (batch.seq <= last) continue; // at-least-once dedup
await handle(batch);
last = batch.seq;
saveCursor(last);
}Error handling
Failures throw a RunnevError subclass carrying the machine-readable
code, the request id, the HTTP status, and a docs link.
import { RunnevError, RateLimitError, AuthenticationError } from "@runnev/client";
try {
await runnev.publish(id, seq, events);
} catch (err) {
if (err instanceof RateLimitError) {
await sleep(err.retryAfterMs);
} else if (err instanceof AuthenticationError) {
throw err; // not retryable
} else if (err instanceof RunnevError) {
console.error(err.code, err.status, err.requestId, err.docUrl);
}
}Classes: RunnevError (base), AuthenticationError,
InvalidRequestError, RateLimitError,
ApiError.
API surface
| Method | Returns |
|---|---|
createStream({ name, retentionSeconds?, maxBytes?, mode?, id? }) | Stream |
listStreams({ limit?, cursor? }) | { data, hasMore, nextCursor } |
getStream(id) | Stream |
deleteStream(id) | void |
publish(id, seq, events) | PublishResult |
publishRaw(id, seq, bytes) | PublishResult |
publishAuto(id, events) | PublishResult |
getCursor(id) | { cursor, updatedAt } |
getBatch(id, seq) | Batch |
subscribe(id, options?) | AsyncIterable<Batch> |
The examples/ directory has runnable publish.mjs,
subscribe.mjs, and raw-protobuf.mjs.