Skip to content

rest-pipeline-js

Flexible, modular pipeline orchestrator for REST APIs — sequential and parallel stages, retry with backoff, response caching, rate limiting, auth provider, stream stages (SSE / AsyncIterable), plugin system, and Vue / React integrations — all with a single dependency (axios).

Features

  • createRestClient() — full-featured HTTP client built on top of axios: retry with exponential backoff and Retry-After support, response caching with a pluggable CacheStore backend (incl. targeted invalidateCache()), rate limiting (concurrency + req/interval) with a pluggable distributed RateLimiterStore, circuit breaker with a pluggable distributed CircuitBreakerStore, auth provider with automatic 401 refresh and optional token caching, request cancellation by key, custom HTTP adapters
  • Request tracing — W3C traceparent header generation plus a TracingProvider hook (duck-typed against OpenTelemetry's Span API) for wiring in a real tracing backend
  • Idempotency keysIdempotency-Key header on mutating requests, manual or auto-generated per logical request across retry attempts
  • Offline queue — queue mutating requests made while offline and replay them (same idempotency key on every attempt) once back online; pluggable persistence, isOnline/onOnlineChange for non-browser environments
  • PipelineOrchestrator — sequential and parallel stage execution; each stage has condition, before, request, after, validateInput, validateOutput, errorHandler hooks (all receive the pipeline's AbortSignal); sharedData pool shared across all stages
  • Error recoveryerrorHandler can return recoverStep(data) to turn a failed stage back into a successful one and keep the pipeline going, instead of only transforming the error
  • Global middlewarebeforeEach / afterEach / onError hooks that apply to every stage without modifying individual configs
  • Parallel groups — multiple stages run concurrently via Promise.all, or through a bounded pool via concurrency; single failure stops the group
  • Pause / Resume / Abortpause() waits after the current stage; resume() continues; abort() cancels the current HTTP request and propagates its AbortSignal into every stage hook so custom request/before/after functions can cancel their own work too
  • Export / Import state — serialize stageResults + logs to a plain object; restore on the next page load
  • Stream stagesstream: async function* for SSE / any AsyncIterable; onChunk callback in real time; abort-aware
  • WebSocket stages — a stage over a persistent connection (onOpen/onMessage/onClose/onError, closeOn); pluggable createWebSocket (defaults to globalThis.WebSocket) for Node <22/edge runtimes
  • Pipeline metrics & run correlationonPipelineStart, onPipelineEnd, onStepDuration callbacks, plus a runId (also on getRunId(), log entries, and step events) shared by every callback/event from the same run
  • createPipeline() / pipe() builder — short factory and fluent builder API for common patterns; in TypeScript, pipe().step() chains infer prev's type from the previous step automatically
  • validatePipelineConfig() — catch duplicate keys, empty keys, type errors before runtime
  • Plugin system — install reusable behavior (logging, analytics, etc.); cleanup via destroy()
  • Persist adapter — pluggable save/load interface; auto-save after each stage
  • Log sanitization — mask sensitive headers (authorization, x-api-key, cookie, …) in metrics callbacks, on by default
  • Vue integrationusePipelineRunVue, usePipelineProgressVue, and more (import from rest-pipeline-js/vue)
  • React integrationusePipelineRunReact, usePipelineProgressReact, and more (import from rest-pipeline-js/react)
  • paginate() / paginateAll() / flattenPages() — iterate a paginated API (cursor- or offset/limit-based) as an AsyncGenerator<T[]>, standalone or as a StreamStageConfig source
  • createMockAdapter() (separate rest-pipeline-js/testing entry point) — route-based HttpAdapter for testing code that uses this package without a real backend, with call history and sequenced responses for exercising retry
  • Tree-shakeablesideEffects: false; Vue and React entry points are code-split

Installation

bash
npm install rest-pipeline-js

Peer dependencies for framework integrations:

bash
# Vue
npm install vue@>=3.3

# React
npm install react@>=19 react-dom@>=19

CDN usage

No bundler, no Node — a single <script> tag pulls in the core module (PipelineOrchestrator / createRestClient / everything under the core entry point, no Vue/React) with axios bundled in, so nothing else needs to be loaded separately. Built as a self-contained IIFE that exposes a window.RestPipeline global — a plain <script> tag, no CommonJS/AMD loader support:

html
<script src="https://unpkg.com/rest-pipeline-js/dist/umd/rest-pipeline.umd.min.js"></script>
<script>
  const { createRestClient, PipelineOrchestrator } = RestPipeline

  const client = createRestClient({ baseURL: 'https://api.example.com' })

  const pipeline = new PipelineOrchestrator({
    config: {
      stages: [{ key: 'user', request: () => client.get('/me') }],
    },
  })

  pipeline.run().then((result) => console.log(result))
</script>

Pin a version for production use (rest-pipeline-js@2.1.0/dist/umd/...) — the unpinned URL above always resolves to the latest release. jsDelivr works the same way: https://cdn.jsdelivr.net/npm/rest-pipeline-js/dist/umd/rest-pipeline.umd.min.js.

An unminified build with a source map (rest-pipeline.umd.js) is also published, for debugging.

Quick start

js
import { createRestClient, PipelineOrchestrator } from 'rest-pipeline-js'

// 1. Create a REST client
const client = createRestClient({
  baseURL: 'https://api.example.com',
  retry: { attempts: 2, delayMs: 500, backoffMultiplier: 2 },
  cache: { enabled: true, ttlMs: 60000 },
  auth: {
    getToken: async () => localStorage.getItem('token') ?? '',
    onUnauthorized: async () => {
      /* refresh token */
    },
  },
})

const res = await client.get('/users/1')

// 2. Run a pipeline
const orchestrator = new PipelineOrchestrator({
  config: {
    stages: [
      {
        key: 'fetchUser',
        request: async ({ sharedData }) => client.get(`/users/${sharedData.userId}`),
      },
      {
        key: 'processData',
        request: async ({ prev }) => ({ ...prev.data, processed: true }),
      },
    ],
  },
  sharedData: { userId: 42 },
})

const result = await orchestrator.run()
console.log(result.success, result.stageResults)