Streaming
Stream stages (SSE / AsyncIterable)
A stage whose stream function returns an AsyncIterable<T>. The orchestrator collects all emitted chunks into an array (the stage result). onChunk is called for each chunk in real time.
const orchestrator = createPipeline([
{ key: 'auth', request: async () => getToken() },
{
key: 'liveData',
stream: async function* ({ prev }) {
const source = new EventSource(`/api/stream?token=${prev}`)
yield* eventSourceToAsyncIterable(source)
},
onChunk: (chunk, sharedData) => {
sharedData.partial = (sharedData.partial ?? '') + chunk
updateUI(sharedData.partial)
},
},
{
key: 'finalize',
// allResults.liveData.data is the full array of chunks
request: async ({ allResults }) => allResults.liveData.data.join(''),
},
])- Respects
abort()— checks the abort signal between each chunk. - Supports
continueOnError— failed stream stages can be skipped like any other step. - Emits standard step events:
step:start,step:success,step:error.
Pagination
paginate() iterates a paginated API's pages as an AsyncGenerator<T[]>, hiding the difference between cursor-based and offset/limit-based APIs:
import { paginate, paginateAll, flattenPages } from 'rest-pipeline-js'
// Cursor-based (default strategy)
for await (const page of paginate({
fetchPage: (cursor) => client.get('/items', { params: { cursor } }).then((r) => r.data),
})) {
console.log(page.length, 'items')
}
// Offset/limit-based
for await (const page of paginate({
strategy: 'offset',
limit: 50,
fetchPage: (offset, limit) =>
client.get('/items', { params: { offset, limit } }).then((r) => r.data),
})) {
console.log(page.length, 'items')
}fetchPage returns { items, nextCursor } (cursor strategy — stop when nextCursor is null/undefined) or { items, total? } (offset strategy — stops when a page is shorter than limit, or offset reaches total if the API reports one).
paginateAll(options)— collects every page into one flat array; simplest option when the total dataset is small enough to hold in memory at once.flattenPages(pages)— turns a stream of pages into a stream of individual items; useful as aStreamStageConfig.streamsource whenonChunkshould fire per item rather than per page (seeexamples/pagination-stream.ts).- Both
paginate()and itsfetchPagecallback accept an optionalsignalforabort()support.
See examples/pagination-fanout.ts instead if you know the page count upfront and want to fetch them concurrently rather than sequentially.
WebSocket stages
A stage that runs over a persistent WebSocket connection instead of a single request/response — chat/presence feeds, live order books, collaborative editing events, etc. Messages returned by onMessage are collected into the stage's data array (same pattern as stream stages' chunks); onChunk fires per message in real time.
const orchestrator = pipe()
.step({ key: 'auth', request: async () => getToken() })
.websocket({
key: 'chatFeed',
url: ({ prev }) => `wss://chat.example.com/rooms/general?token=${prev}`,
onOpen: () => console.log('connected'),
onMessage: (data) => JSON.parse(data),
onChunk: (message, sharedData) => updateUI(message),
closeOn: (message) => message.text === '__end__',
onClose: ({ wasClean }) => console.log('closed, clean:', wasClean),
onError: (error) => console.error(error),
timeoutMs: 5 * 60_000,
})
.build()url— string or a function of{ prev, allResults, sharedData, signal }, same paramsrequestgets.createWebSocket— factory for the underlying implementation. Defaults toglobalThis.WebSocket(browsers, Deno, Node ≥22). For Node <22, pass one backed by thewspackage:createWebSocket: (url, protocols) => new WS(url, protocols).onMessage(required) — called per message withevent.data; can beasync. A non-undefinedreturn value is collected into the stage's result array and passed toonChunk.- Success/error is decided by the close event, not the error event: most WebSocket implementations fire
errorimmediately beforeclose, soonErroralone doesn't fail the stage — a clean close (wasClean: true) resolves the stage successfully with everything collected so far; an unclean close rejects it, going throughcontinueOnErrorlike any other stage. closeOn(data)— returntrueto close the connection and end the stage successfully once you've seen what you need, instead of waiting for the server to close it.timeoutMs— overall connection timeout (not reset by messages); closes the socket and fails the stage if it fires before a clean close happens on its own.- Respects
abort()— closes the underlying connection and rejects the stage.
See examples/websocket-stage.ts for the full annotated version.