FurniOpsDocs
TypeScript SDK

Change stream

changes.stream, a reconnecting async iterable over GET /v1/stream that resumes where it left off.

furniops.changes.stream(query?, options?) opens GET /v1/stream and returns a ChangeStream: an async iterable of events that reconnects on its own and resumes after the last event it delivered.

stream.ts
const controller = new AbortController();
const stream = furniops.changes.stream(
  { types: "offer.changed,offer.created", payload: "thin" },
  { signal: controller.signal, lastEventId: loadLastEventId() },
);
for await (const { id, type, event, apiVersion } of stream) {
  console.log(type, event.id, apiVersion);
  saveLastEventId(id); // a feed cursor: resume after it, on the stream or the feed
}

Each event

FieldMeaning
idThe event's position: a feed cursor. Save it to resume, on the stream or the feed.
typeThe event type, offer.changed.
eventThe envelope, parsed and typed as a WebhookEvent.
dataThe envelope exactly as received, as a string: the bytes to forward or sign.
apiVersionThe FurniOps-Version the stream answered in.

stream.lastEventId is the last position seen, including the server's own position marks.

Query

The first argument is the stream's query, as in the API:

FieldMeaning
typesComma-separated event types to keep. Every type by default.
cataloguesComma-separated cat_ IDs, at most 50: only events of those catalogues. Every catalogue you can see by default.
payloadthin (default) or fat. See payloads.
cursorStart after this feed cursor. Without one (and without lastEventId), the stream starts with events committed from now on.

Options

OptionDefaultMeaning
signalnoneEnds the stream: the loop rejects with the signal's reason.
lastEventIdnoneResume after this event (sent as Last-Event-ID), over the query's cursor.
maxReconnectsunlimitedFailed connection attempts in a row (refused, 429, 5xx) before the last failure is thrown. A successful connection resets the count.
idleTimeout45000Milliseconds without a byte before the connection is treated as dead and replaced. The server sends a comment every 15 s while it is quiet, so a healthy connection never trips it.
reconnectDelay3000The first wait before reconnecting, until the server sends its own retry:.
apiVersion, timeout, headersas the clientAs for any call. timeout covers the wait for the response headers.

Reconnection

  • When a connection ends, the stream reconnects after reconnectDelay (or the server's retry:) and sends Last-Event-ID, so no event between the two connections is lost. Each connection asks your apiKey function again, so a refreshed token is picked up.
  • Failed attempts in a row back off exponentially; a Retry-After up to 60 s is honoured.
  • A 4xx other than 429 (a revoked key, a missing scope) is never retried: it is thrown at once.
  • Breaking out of the for await loop closes the connection.

A stream is not a delivery guarantee

The stream resumes from the last event it delivered while your process runs. If your process stops, events committed meanwhile are delivered only if you resume with a saved lastEventId within the feed's 30 days. For a system that must not miss an event, save the id after handling each one, as in the example, or follow the feed.

When to use it

UseBest fit
A live dashboard, a console, a local toolThe stream
Keeping a database in syncThe feed, with wait
Reacting in a server you already run, without a loopWebhooks

Each connection is in the stream rate class, reconnections included.

On this page