Skip to content

Events

The @arrow-labs/auth-sdk/events subpath lets a first-party backend consume platform events from its dedicated queue. It has one runtime dependency, rabbitmq-client (auto-reconnecting), and is Node-only.

The consumer attaches to your queue arrowlabs.events.{your_client_id} — it never declares or binds topology (the platform owns that). See Platform events for the model.

import { createEventConsumer } from '@arrow-labs/auth-sdk/events';
const consumer = createEventConsumer({
url: process.env.AMQP_URL!, // amqps://user:pass@host:5671
clientId: 'your-client-id', // → queue arrowlabs.events.your-client-id
concurrency: 10, // handlers in flight; default 10
onError: (err, envelope) => logger.error({ err, eventId: envelope?.event_id }, 'handler failed'),
})
.on('user.registered', async (payload, envelope) => {
// payload is fully typed (UserRegisteredPayload): user_id, email, display_name, roles, …
await provisionAccount(payload.user_id, payload.email);
})
.on('user.suspended', async (payload) => {
await disableAccount(payload.user_id); // payload: { user_id, reason }
})
.onUnhandled((envelope) => {
logger.debug({ type: envelope.event_type }, 'no handler');
});
await consumer.start();
// on shutdown:
await consumer.stop();
  • Ack on success. When a handler resolves, the message is acknowledged.
  • Failure → dead-letter, no requeue. If a handler throws (or the body can’t be decoded), the message is nacked without requeue and routed to the dead-letter queue — no poison-message loops. Failed events are inspectable from the admin portal’s DLQ view.

Events are at-least-once. Make handlers idempotent; wire event_id to your own store via the optional deduplicator hook:

createEventConsumer({
url, clientId,
deduplicator: {
isDuplicate: (id) => redis.sismember('processed_events', id), // checked before the handler
markProcessed: (id) => redis.sadd('processed_events', id), // after the handler succeeds
},
});

isDuplicate short-circuits to an ack (handler skipped); markProcessed runs after the handler resolves, before the ack. The SDK ships no storage — the seam is yours.

The full catalogue is exported as PlatformEventType, with EventEnvelope, EventPayloadMap, and each *Payload interface.