event-router-client
v1.0.1
Published
Client for Event Router
Readme
event-router-client
TypeScript client for the Event Router.
Supports publish, subscribe (callback + async iterator), auto-reconnect, and clean close using AbortController.
Install
npm i event-router-client
# if local file:
# import { EventRouterClient } from './event-router-client'Node ≥ 18 recommended.
Quick start
import { EventRouterClient } from 'event-router-client';
const bus = new EventRouterClient({ host: '127.0.0.1', port: 4040 });
// Subscribe (callback style)
const orders = bus.subscribe({ source: ['app.orders'], detail: { status: [{ prefix: 'created' }] } });
const off = orders.on(e => console.log('got:', e));
// Subscribe (async iterator style)
(async () => {
let seen = 0;
for await (const e of bus.subscribe({ 'detail-type': ['order'] })) {
console.log('iter:', e);
if (++seen === 2) break;
}
})();
// Publish
await bus.publish({
source: 'app.orders',
'detail-type': 'order',
detail: { status: 'created.v1', id: 'o-1' },
});
// Unsubscribe / close
off(); // remove this callback
orders.close(); // cancel the subscription
await bus.close(); // close socket, reject pending publishesAPI
new EventRouterClient(opts?: { host?: string; port?: number; backoffMaxMs?: number; })
host(default127.0.0.1)port(default4040)backoffMaxMs— reconnection cap (default5000)
Auto-connects on first publish() or subscribe() and reconnects on drop.
publish<T extends object>(event: T): Promise<number>
Sends an event to the bus; resolves with delivered (number of matching subscriptions across all connected clients).
Rejects on ack timeout or if the client is closed.
subscribe<T = any>(pattern: object): Subscription<T>
Creates a server-side subscription (unique subId) and returns a Subscription.
Subscription<T>:
subId: stringpattern: objecton((event: T) => void): () => void— add a callback; returns an unsubscriber.close(): void— cancels server subscription and wakes any pending iterator calls.closed: boolean[Symbol.asyncIterator](): AsyncIterator<T>— iterate events:
for await (const e of sub) {
// ...
}The iterator stops when:
sub.close()is called (internally uses anAbortControllerto wake the waiter), or- the client is closed.
close(): Promise<void>
Closes the TCP connection, rejects all in-flight publish() promises with Error('client closed'), and force-closes all subscriptions.
Behavior details
- Reconnect: exponential backoff (
×1.8, capped atbackoffMaxMs). On reconnect, the client re-subscribes all open subscriptions. - Backpressure: the client writes length-prefixed frames; if the kernel send buffer fills, it waits for
'drain'. - Unsubscribe: best-effort if disconnected. Server clears subs on socket close.
- Delivery semantics: at-most-once to each live subscription. No persistence/replay.
- IDs: the server assigns
event.idif missing; you can set your own for dedupe downstream if you need it.
Pattern cheatsheet
Same as the server’s supported subset:
// matches 'created.*'
{ detail: { status: [{ prefix: 'created' }] } }
// numeric ranges
{ detail: { total: [{ numeric: { '>=': 10, '<': 100 } }] } }
// anything-but (array)
{ region: [{ 'anything-but': ['dev', 'test'] }] }
// cidr on string field
{ clientIp: [{ cidr: '10.0.0.0/8' }] }
// wildcard (picomatch)
{ source: [{ wildcard: 'app.*' }] }
// equals-ignore-case
{ env: [{ 'equals-ignore-case': 'PROD' }] }Troubleshooting
- No events? Ensure your pattern keys match event shape; unknown keys never match.
- Iterator hangs on shutdown? Call
sub.close()orclient.close()— both wake the iterator viaAbortController. - Ack timeout in
publish()? Server not reachable or overloaded; increase server CPU, split topics, or batch.
