К содержанию
ActionPulse

Node.js SDK

Публичный API серверной библиотеки ActionPulse для Node.js — опции конструктора, очередь, методы, классы ошибок и остановка процесса. Пакет не опубликован, рабочий путь сегодня — прямой HTTP.

Кому
Node.js-разработчикам
Проверено
На этой странице

В репозитории продукта есть серверная библиотека для Node.js. Она не опубликована, поэтому добавить её в зависимости проекта нельзя. Эта страница показывает её настоящий публичный API — чтобы вы понимали, что вас ждёт, — и называет рабочий путь для бэкенда сегодня.

Версия в репозитории — 0.1.0, она общая для всех трёх серверных библиотек (Node.js, Go, Python). Пакет только ESM, требует Node.js 18 и выше, внешних зависимостей у него нет.

Чем SDK отличается от прямого HTTP

Прямой HTTP — это один fetch и полный контроль. Библиотека добавляет к нему то, что иначе пришлось бы написать самому:

Что делает библиотека Что это значит на практике
Проверяет контракт до отправки неверное имя события бросает ValidationError, а не приходит в rejected
Держит ограниченную очередь в памяти всплеск событий не превращается в лавину запросов
Сама режет батчи соблюдает 500 элементов и 256 КБ на запрос без вашего участия
Повторяет только то, что стоит повторять ошибка транспорта, 429 и 5xx, с учётом Retry-After
Пересчитывает индексы отказов rejected[i].i указывает на позицию в вашем исходном вызове, а не в очередном HTTP-запросе
Санирует ошибки сообщение не содержит ключ, тело запроса, идентификаторы и свойства

Чего библиотека не делает: она не поднимает таймеров и ничего не отправляет сама. Пока вы не вызвали flush() или close(), события лежат в памяти процесса.

Клиент

import { ActionPulse } from '@actionpulse/server-node';

const serverKey = process.env.ACTIONPULSE_SERVER_KEY;
if (!serverKey) throw new Error('ACTIONPULSE_SERVER_KEY is required');

const pp = new ActionPulse({
  endpoint: 'https://YOUR_ACTIONPULSE_HOST',
  writeKey: serverKey,
  appVersion: 'billing-2026.07.28',
  batchSize: 100,
});

pp.track({
  event: 'order_paid',
  user_id: 'YOUR_USER_ID',
  schema_version: 1,
  props: { order_id: 'A-1024', amount_minor: 99000 },
});

const result = await pp.flush();
for (const item of result.rejected) {
  console.error('event rejected', item.i, item.code);
}

Конструктор проверяет опции и не делает сетевых запросов: неверный адрес или пустой ключ бросает ValidationError сразу, а не при первой отправке.

Обратите внимание на именование полей: опции конструктора — camelCase (writeKey, batchSize), а поля события — те же snake_case, что и в HTTP (user_id, schema_version, event_id). Это не опечатка: событие повторяет контракт приёма буквально.

Опции конструктора

Опция По умолчанию Что важно знать
endpoint обязательно, абсолютный http/https адрес приёма
writeKey обязательно, серверный ключ pp_sk_…, без переводов строки
timeoutMs 5000 таймаут одной попытки, снимается через AbortController
queueCapacity 1000 предел очереди в памяти
batchSize 100 элементов в одном запросе, не больше 500
maxRetries 3 целое от 0 до 10; 0 означает «без повторов»
baseBackoffMs 100 начальная задержка
maxBackoffMs 2000 потолок задержки, не меньше baseBackoffMs
appVersion до 128 печатных ASCII-байт, уходит в заголовок диагностики
release то же, для идентификатора релиза
logger (entry) => void, вызывается на каждой повторной попытке
processor (event) => event или null, последний фильтр перед очередью
fetch globalThis.fetch своя реализация запроса
sleep встроенная своя реализация ожидания для тестов

Опции source и schemaVersion объявлены устаревшими. source игнорируется: источник события выводится из типа ключа, а не из того, что указал вызывающий. schemaVersion работает как значение по умолчанию для schema_version события.

processor вызывается для каждого события перед постановкой в очередь и получает замороженный объект. Верните изменённое событие или null, чтобы его выбросить. event_id и time восстанавливаются после вызова, поэтому обработчик не может нарушить идемпотентность. Исключение внутри обработчика превращается в ValidationError.

Методы

Метод Подпись Поведение
queueSize number (свойство) длина очереди
track (event) => string ставит одно событие в очередь, возвращает его event_id
batch (events) => string[] проверяет все события и ставит их все или ни одного
group (input) => string ставит в очередь обычное событие group_identify
identify (input) => Promise<object> отправляет сразу, без очереди и без повторов
alias (input) => Promise<object> отправляет сразу; поле traits игнорируется
flush () => Promise<TrackResponse> отправляет накопленное
close () => Promise<TrackResponse> отправляет накопленное и закрывает клиент

track и batch синхронные: они только ставят в очередь и бросают исключение, если событие не проходит проверку или очередь заполнена. Никакого await для них не нужно и никакого «незамеченного отказа промиса» они не создают.

identify и alias возвращают разобранное тело ответа. У этих операций нет поля идемпотентности, поэтому они никогда не повторяются автоматически — решение о повторе принимаете вы.

Одновременные вызовы flush() выстраиваются в очередь и не пересекаются, так что вызывать его из нескольких мест безопасно.

Ошибки

Класс Когда Что делать
ValidationError локальная проверка контракта исправить вызов
QueueFullError очередь заполнена увеличить queueCapacity или вызывать flush чаще
TransportError сеть или таймаут, повторы исчерпаны повторить позже
APIError приём ответил не 202 смотреть status и code

Все четыре наследуют ActionPulseError:

import { APIError, ActionPulseError } from '@actionpulse/server-node';

try {
  await pp.flush();
} catch (error) {
  if (error instanceof APIError && error.status === 401) {
    console.error('actionpulse: credential rejected');
  } else if (error instanceof ActionPulseError) {
    console.error('actionpulse: delivery failed');
  } else {
    throw error;
  }
}

APIError содержит только operation, status и канонический code из тела ответа. Тела ответа, ключа и свойств события в сообщении нет намеренно: такую ошибку можно писать в журнал целиком.

logger получает { operation, attempt, status } — и ничего больше. По умолчанию журналирование выключено.

Остановка процесса

Автоматической отправки при выходе нет. Незакрытый клиент теряет всё, что осталось в очереди.

async function shutdown(signal) {
  try {
    await pp.close();
  } catch (error) {
    console.error('actionpulse close failed', error.message);
  }
  process.exit(signal === 'SIGINT' ? 130 : 0);
}

process.once('SIGTERM', () => void shutdown('SIGTERM'));
process.once('SIGINT', () => void shutdown('SIGINT'));

Важные детали:

  • close() сначала отправляет очередь, и только потом перестаёт принимать вызовы.
  • Если отправка не удалась, клиент остаётся открытым: можно починить связь и повторить close().
  • Повторный close() на уже закрытом клиенте возвращает пустой результат без исключения; вызов во время закрытия бросает ошибку.
  • process.on('exit') для этого не подходит: асинхронная отправка в этом обработчике уже не выполнится. Нужен именно сигнальный обработчик.

Рабочий путь сегодня

Пока библиотека не опубликована, отправляйте события прямым HTTP: это тот же контракт, те же лимиты и те же коды отказов. Готовый пример на Node.js есть в отправке событий с бэкенда — там же перечислены поля события, коды отказов и правила повторов.

Если вы захотите позже перейти на библиотеку, полезно заранее сделать две вещи: складывать event_id вместе с бизнес-фактом и держать отправку за одним модулем в своём коде. Тогда замена транспорта не затронет вызовы.

Дальше: лимиты и квоты и доставка, повторы и дубли.