JavaScript SDK

@runnev/client — официальный SDK для JavaScript и TypeScript. Ноль зависимостей во время выполнения, поставляется в ESM и CommonJS, работает в Node 18+ и современных браузерах на встроенных fetch и ReadableStream.

Установка

bash
npm install @runnev/client   # v1.6.1

Типы включены в пакет; отдельный пакет @types добавлять не нужно.

Настройка

javascript
import { Runnev } from "@runnev/client";

const runnev = new Runnev({
  apiKey: process.env.RUNNEV_API_KEY,   // обязательно
  baseUrl: "https://runnev.dev/v1",     // по умолчанию
  timeoutMs: 30000,                     // таймаут на запрос (не на поток подписки)
  maxRetries: 4,                        // повторы при 429 / 5xx, с backoff
});

В браузере создавайте его только с демо-ключом только для чтения; никогда не отправляйте ключ rnv_live_ на клиентское устройство.

Публикация

javascript
// создание (серверный id)
const stream = await runnev.createStream({ name: "orders-eu", retentionSeconds: 86400 });

// публикация под явным номером — идемпотентна, безопасно повторять
const res = await runnev.publish(stream.id, 41823, [
  { type: "order.paid", id: "o_5521", amount: 1999 },
]);
console.log(res.cursor, res.duplicate); // 41823 false

// серверный номер
await runnev.publishAuto(stream.id, [{ type: "tick" }]);

// сырые байты для потока в режиме raw
await runnev.publishRaw(stream.id, 9001, new Uint8Array(protobufBytes));

Подписка

Subscribe возвращает асинхронный итерируемый объект и также принимает колбэки. Он переподключается автоматически, возобновляя с последнего увиденного номера.

javascript
// асинхронная итерация
for await (const batch of runnev.subscribe(stream.id, { cursor: 0 })) {
  console.log(batch.seq, batch.events);
}

// или колбэки, с AbortSignal для остановки
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),
});
// позже: controller.abort();

Возобновление между перезапусками

javascript
let last = loadCursor() ?? -1; // из вашего надёжного хранилища
for await (const batch of runnev.subscribe(stream.id, { cursor: last })) {
  if (batch.seq <= last) continue;   // дедуп «не менее одного раза»
  await handle(batch);
  last = batch.seq;
  saveCursor(last);
}

Обработка ошибок

Сбои бросают подкласс RunnevError, несущий машиночитаемый код, идентификатор запроса, HTTP-статус и ссылку на документацию.

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; // не повторяемо
  } else if (err instanceof RunnevError) {
    console.error(err.code, err.status, err.requestId, err.docUrl);
  }
}

Классы: RunnevError (базовый), AuthenticationError, InvalidRequestError, RateLimitError, ApiError.

Поверхность API

МетодВозвращает
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>

В каталоге examples/ есть запускаемые publish.mjs, subscribe.mjs и raw-protobuf.mjs.