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

bash
npm install @runnev/client   # v1.6.1

Types are bundled; there is no @types package to add.

Configure

javascript
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

javascript
// 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.

javascript
// 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

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

javascript
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

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