rest-pipeline-js 1.3.10 → 1.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +402 -328
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -4,175 +4,183 @@
4
4
  </h1>
5
5
  <img
6
6
  src="https://s3.twcstorage.ru/c9a2cc89-780f97fd-311d-4a1a-b86f-c25665c9dc46/images/npm/rest-pipeline-js.webp"
7
- alt="vue-virtual-scroller-kit"
7
+ alt="rest-pipeline-js"
8
8
  style="max-width:100%;width:auto;height:300px;border-radius:12px"
9
9
  />
10
10
  </div>
11
11
 
12
- **Flexible, modular pipeline orchestrator for REST APIs.**
12
+ 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).
13
+
14
+ ---
15
+
16
+ ## Contents
17
+
18
+ - [Features](#features)
19
+ - [Installation](#installation)
20
+ - [Demo](#demo)
21
+ - [Quick start](#quick-start)
22
+ - [createRestClient](#createrestclient)
23
+ - [Auth Provider](#auth-provider)
24
+ - [Log Sanitization](#log-sanitization)
25
+ - [RequestExecutor](#requestexecutor)
26
+ - [PipelineOrchestrator](#pipelineorchestrator)
27
+ - [Parallel stages](#parallel-stages)
28
+ - [Global middleware](#global-middleware)
29
+ - [Pause / Resume](#pause--resume)
30
+ - [Export / Import state](#export--import-state)
31
+ - [Pipeline metrics](#pipeline-metrics)
32
+ - [createPipeline() + pipe() builder](#createpipeline--pipe-builder)
33
+ - [validatePipelineConfig()](#validatepipelineconfig)
34
+ - [Plugin system](#plugin-system)
35
+ - [Persist adapter](#persist-adapter)
36
+ - [Stream stages](#stream-stages-sse--asynciterable)
37
+ - [HTTP Adapter](#http-adapter-custom-fetch--edge-environments)
38
+ - [Vue integration](#vue-integration)
39
+ - [React integration](#react-integration)
40
+ - [Entry points](#entry-points)
41
+ - [Architecture](#architecture)
42
+ - [Bundle size & peer dependencies](#bundle-size--peer-dependencies)
43
+
44
+ ---
45
+
46
+ ## Features
47
+
48
+ - **`createRestClient()`** — full-featured HTTP client built on top of axios: retry with exponential backoff and `Retry-After` support, response caching for GET requests, rate limiting (concurrency + req/interval), auth provider with automatic 401 refresh, request cancellation by key, custom HTTP adapters
49
+ - **`PipelineOrchestrator`** — sequential and parallel stage execution; each stage has `condition`, `before`, `request`, `after`, `errorHandler` hooks; `sharedData` pool shared across all stages
50
+ - **Global middleware** — `beforeEach` / `afterEach` / `onError` hooks that apply to every stage without modifying individual configs
51
+ - **Parallel groups** — multiple stages run concurrently via `Promise.all`; single failure stops the group
52
+ - **Pause / Resume / Abort** — `pause()` waits after the current stage; `resume()` continues; `abort()` cancels the current HTTP request via `AbortController`
53
+ - **Export / Import state** — serialize `stageResults` + logs to a plain object; restore on the next page load
54
+ - **Stream stages** — `stream: async function*` for SSE / any `AsyncIterable`; `onChunk` callback in real time; abort-aware
55
+ - **Pipeline metrics** — `onPipelineStart`, `onPipelineEnd`, `onStepDuration` callbacks without touching stage logic
56
+ - **`createPipeline()` / `pipe()` builder** — short factory and fluent builder API for common patterns
57
+ - **`validatePipelineConfig()`** — catch duplicate keys, empty keys, type errors before runtime
58
+ - **Plugin system** — install reusable behavior (logging, analytics, etc.); cleanup via `destroy()`
59
+ - **Persist adapter** — pluggable save/load interface; auto-save after each stage
60
+ - **Log sanitization** — mask sensitive headers (`authorization`, `x-api-key`, `cookie`, …) in metrics callbacks
61
+ - **Vue integration** — `usePipelineRunVue`, `usePipelineProgressVue`, and more (import from `rest-pipeline-js/vue`)
62
+ - **React integration** — `usePipelineRunReact`, `usePipelineProgressReact`, and more (import from `rest-pipeline-js/react`)
63
+ - **Tree-shakeable** — `sideEffects: false`; Vue and React entry points are code-split
13
64
 
14
65
  ---
15
66
 
16
67
  ## Installation
17
68
 
18
- ```sh
19
- npm i rest-pipeline-js
69
+ ```bash
70
+ npm install rest-pipeline-js
71
+ ```
72
+
73
+ Peer dependencies for framework integrations:
74
+
75
+ ```bash
76
+ # Vue
77
+ npm install vue@>=3.3
78
+
79
+ # React
80
+ npm install react@>=19 react-dom@>=19
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Demo
86
+
87
+ A live interactive demo of the pipeline running against a real flight-search API — 4 sequential stages: airport lookup, availability, ancillary services, and seat map.
88
+
89
+ ```bash
90
+ git clone https://github.com/macrulezru/pipeline-js.git
91
+ cd pipeline-js
92
+ npm install
93
+ npm run demo:vue
20
94
  ```
21
95
 
22
- ## Features & API
96
+ Opens at `http://localhost:3000` (or the next available port). Click **Run Pipeline** to execute all stages and watch results appear in real time. A boarding pass is rendered when all stages succeed.
23
97
 
24
- ### Core module (rest-pipeline-js)
98
+ ---
25
99
 
26
- #### Example: Create REST client and make a request
100
+ ## Quick start
27
101
 
28
102
  ```js
29
- import { createRestClient } from "rest-pipeline-js";
103
+ import { createRestClient, PipelineOrchestrator } from "rest-pipeline-js";
30
104
 
105
+ // 1. Create a REST client
31
106
  const client = createRestClient({
32
107
  baseURL: "https://api.example.com",
33
- timeout: 5000,
34
- retry: {
35
- attempts: 2,
36
- delayMs: 500,
37
- backoffMultiplier: 2,
38
- retriableStatus: [429, 500, 503],
39
- },
108
+ retry: { attempts: 2, delayMs: 500, backoffMultiplier: 2 },
40
109
  cache: { enabled: true, ttlMs: 60000 },
41
- rateLimit: { maxConcurrent: 3, maxRequestsPerInterval: 10, intervalMs: 1000 },
42
- // Auth Provider — token injected automatically, 401 triggers refresh + one retry
43
110
  auth: {
44
111
  getToken: async () => localStorage.getItem("token") ?? "",
45
- onUnauthorized: async () => {
46
- /* refresh token here */
47
- },
112
+ onUnauthorized: async () => { /* refresh token */ },
48
113
  },
49
- // Mask sensitive headers in metrics (authorization, x-api-key, cookie, …)
50
- sanitizeHeaders: true,
51
114
  });
52
115
 
53
116
  const res = await client.get("/users/1");
54
- console.log(res.data);
55
-
56
- // PATCH support
57
- await client.patch("/users/1", { name: "Alice" });
58
-
59
- // Cancellable request
60
- const req = client.cancellableRequest("my-key", "/search", {
61
- params: { q: "foo" },
62
- });
63
- // Cancel it any time:
64
- client.cancelRequest("my-key");
65
- ```
66
-
67
- ---
68
-
69
- #### Example: Run a pipeline, handle errors, track progress, use shared data
70
-
71
- ```js
72
- import { PipelineOrchestrator } from "rest-pipeline-js";
73
117
 
118
+ // 2. Run a pipeline
74
119
  const orchestrator = new PipelineOrchestrator({
75
120
  config: {
76
121
  stages: [
77
- {
78
- key: "fetchUser",
79
- request: async ({ sharedData }) => {
80
- const res = await fetch(`/api/users/${sharedData.userId}`);
81
- return res.json();
82
- },
83
- },
84
- {
85
- key: "processData",
86
- condition: ({ prev }) => prev !== null,
87
- before: ({ prev }) => ({ ...prev, processed: true }),
88
- request: async ({ prev }) => prev,
89
- after: ({ result }) => ({ ...result, finishedAt: Date.now() }),
90
- },
122
+ { key: "fetchUser", request: async ({ sharedData }) => client.get(`/users/${sharedData.userId}`) },
123
+ { key: "processData", request: async ({ prev }) => ({ ...prev.data, processed: true }) },
91
124
  ],
92
- middleware: {
93
- beforeEach: ({ stage }) => console.log("Starting:", stage.key),
94
- afterEach: ({ stage, result }) =>
95
- console.log("Done:", stage.key, result.data),
96
- onError: ({ stage, error }) =>
97
- console.error("Error in", stage.key, error),
98
- },
99
- },
100
- httpConfig: {
101
- baseURL: "https://api.example.com",
102
- retry: { attempts: 2, delayMs: 1000, backoffMultiplier: 2 },
103
- cache: { enabled: true, ttlMs: 60000 },
104
125
  },
105
126
  sharedData: { userId: 42 },
106
- options: { autoReset: true },
107
- });
108
-
109
- orchestrator.subscribeProgress((progress) => {
110
- console.log(
111
- "Stage:",
112
- progress.currentStage,
113
- "Statuses:",
114
- progress.stageStatuses,
115
- );
116
- });
117
-
118
- orchestrator.on("step:fetchUser:success", (payload) => {
119
- console.log("fetchUser done:", payload.data);
120
127
  });
121
128
 
122
129
  const result = await orchestrator.run();
123
- console.log("Pipeline finished:", result.success);
124
- console.log("Stage results:", result.stageResults);
130
+ console.log(result.success, result.stageResults);
125
131
  ```
126
132
 
127
133
  ---
128
134
 
129
- ### Main classes and functions
135
+ ## createRestClient
130
136
 
131
- #### createRestClient(config: HttpConfig): RestClient
137
+ ```ts
138
+ createRestClient(config: HttpConfig): RestClient
139
+ ```
132
140
 
133
141
  Creates a REST client with advanced HTTP features.
134
142
 
135
- **Available methods:**
136
-
137
- | Method | Description |
138
- | --------------------------------------- | ---------------------------------- |
139
- | `get(url, config?)` | GET request |
140
- | `post(url, data?, config?)` | POST request |
141
- | `put(url, data?, config?)` | PUT request |
142
- | `patch(url, data?, config?)` | PATCH request |
143
- | `delete(url, config?)` | DELETE request |
144
- | `request(url, config?)` | Generic request |
145
- | `cancellableRequest(key, url, config?)` | Request cancellable by key |
146
- | `cancelRequest(key)` | Cancel request by key |
147
- | `clearCache()` | Clear this client's response cache |
148
-
149
- **HttpConfig options:**
150
-
151
- | Option | Description |
152
- | ---------------------------------- | -------------------------------------------------------------------------------- |
153
- | `baseURL` | Base URL for all requests |
154
- | `timeout` | Request timeout in ms |
155
- | `headers` | Default headers |
156
- | `withCredentials` | Include cookies |
157
- | `retry.attempts` | Number of retry attempts |
158
- | `retry.delayMs` | Base delay between retries in ms |
159
- | `retry.backoffMultiplier` | Exponential backoff multiplier |
160
- | `retry.retriableStatus` | HTTP status codes eligible for retry (e.g. `[429, 500, 503]`) |
161
- | `cache.enabled` | Enable response caching for GET requests |
162
- | `cache.ttlMs` | Cache TTL in ms |
163
- | `rateLimit.maxConcurrent` | Max simultaneous requests |
164
- | `rateLimit.maxRequestsPerInterval` | Max requests per time window |
165
- | `rateLimit.intervalMs` | Time window size in ms |
166
- | `metrics.onRequestStart` | Callback on request start |
167
- | `metrics.onRequestEnd` | Callback on request end (includes duration and bytes) |
168
- | `auth.getToken` | Async function returning a Bearer token (called before every request) |
169
- | `auth.onUnauthorized` | Optional async callback on 401 refresh the token here; request is retried once |
170
- | `sanitizeHeaders` | Mask sensitive headers in metrics callbacks (default: `false`) |
171
- | `sensitiveHeaders` | Additional headers to mask (extends `DEFAULT_SENSITIVE_HEADERS`) |
172
- | `retry.maxRetryAfterMs` | Max wait from `Retry-After` header in ms (default: `60000`) |
173
- | `adapter` | Custom HTTP adapter (e.g. native `fetch`) — replaces built-in axios |
174
-
175
- **Per-request cache override:**
143
+ ### Methods
144
+
145
+ | Method | Description |
146
+ |---|---|
147
+ | `get(url, config?)` | GET request |
148
+ | `post(url, data?, config?)` | POST request |
149
+ | `put(url, data?, config?)` | PUT request |
150
+ | `patch(url, data?, config?)` | PATCH request |
151
+ | `delete(url, config?)` | DELETE request |
152
+ | `request(url, config?)` | Generic request |
153
+ | `cancellableRequest(key, url, config?)` | Request cancellable by key |
154
+ | `cancelRequest(key)` | Cancel request by key |
155
+ | `clearCache()` | Clear this client's response cache |
156
+
157
+ ### HttpConfig options
158
+
159
+ | Option | Description |
160
+ |---|---|
161
+ | `baseURL` | Base URL for all requests |
162
+ | `timeout` | Request timeout in ms |
163
+ | `headers` | Default headers |
164
+ | `withCredentials` | Include cookies |
165
+ | `retry.attempts` | Number of retry attempts |
166
+ | `retry.delayMs` | Base delay between retries in ms |
167
+ | `retry.backoffMultiplier` | Exponential backoff multiplier |
168
+ | `retry.retriableStatus` | HTTP status codes eligible for retry (e.g. `[429, 500, 503]`) |
169
+ | `retry.maxRetryAfterMs` | Max wait from `Retry-After` header in ms (default: `60000`) |
170
+ | `cache.enabled` | Enable response caching for GET requests |
171
+ | `cache.ttlMs` | Cache TTL in ms |
172
+ | `rateLimit.maxConcurrent` | Max simultaneous requests |
173
+ | `rateLimit.maxRequestsPerInterval` | Max requests per time window |
174
+ | `rateLimit.intervalMs` | Time window size in ms |
175
+ | `metrics.onRequestStart` | Callback on request start |
176
+ | `metrics.onRequestEnd` | Callback on request end (includes duration and bytes) |
177
+ | `auth.getToken` | Async function returning a Bearer token (called before every request) |
178
+ | `auth.onUnauthorized` | Optional async callback on 401 refresh the token here; request is retried once |
179
+ | `sanitizeHeaders` | Mask sensitive headers in metrics callbacks (default: `false`) |
180
+ | `sensitiveHeaders` | Additional headers to mask (extends `DEFAULT_SENSITIVE_HEADERS`) |
181
+ | `adapter` | Custom HTTP adapter (e.g. native `fetch`) — replaces built-in axios |
182
+
183
+ ### Per-request cache override
176
184
 
177
185
  ```js
178
186
  const res = await client.get("/data", {
@@ -182,9 +190,44 @@ const res = await client.get("/data", {
182
190
  });
183
191
  ```
184
192
 
193
+ ### Full example
194
+
195
+ ```js
196
+ import { createRestClient } from "rest-pipeline-js";
197
+
198
+ const client = createRestClient({
199
+ baseURL: "https://api.example.com",
200
+ timeout: 5000,
201
+ retry: {
202
+ attempts: 2,
203
+ delayMs: 500,
204
+ backoffMultiplier: 2,
205
+ retriableStatus: [429, 500, 503],
206
+ },
207
+ cache: { enabled: true, ttlMs: 60000 },
208
+ rateLimit: { maxConcurrent: 3, maxRequestsPerInterval: 10, intervalMs: 1000 },
209
+ auth: {
210
+ getToken: async () => localStorage.getItem("token") ?? "",
211
+ onUnauthorized: async () => { /* refresh token here */ },
212
+ },
213
+ sanitizeHeaders: true,
214
+ });
215
+
216
+ const res = await client.get("/users/1");
217
+ console.log(res.data);
218
+
219
+ // PATCH support
220
+ await client.patch("/users/1", { name: "Alice" });
221
+
222
+ // Cancellable request
223
+ const req = client.cancellableRequest("my-key", "/search", { params: { q: "foo" } });
224
+ // Cancel it any time:
225
+ client.cancelRequest("my-key");
226
+ ```
227
+
185
228
  ---
186
229
 
187
- ### Auth Provider
230
+ ## Auth Provider
188
231
 
189
232
  Automatically inject an `Authorization: Bearer <token>` header before every request. On a `401` response, `onUnauthorized` is called (e.g. to refresh the token) and the request is retried **once** — preventing infinite loops.
190
233
 
@@ -196,7 +239,6 @@ const client = createRestClient({
196
239
  return localStorage.getItem("access_token") ?? "";
197
240
  },
198
241
  onUnauthorized: async () => {
199
- // refresh token, update storage
200
242
  const newToken = await refreshAccessToken();
201
243
  localStorage.setItem("access_token", newToken);
202
244
  },
@@ -209,7 +251,7 @@ const res = await client.get("/profile");
209
251
 
210
252
  ---
211
253
 
212
- ### Log Sanitization
254
+ ## Log Sanitization
213
255
 
214
256
  Mask sensitive headers in metrics callbacks (`onRequestStart` / `onRequestEnd`) so they never appear in logs.
215
257
 
@@ -221,8 +263,8 @@ import { createRestClient, DEFAULT_SENSITIVE_HEADERS } from "rest-pipeline-js";
221
263
 
222
264
  const client = createRestClient({
223
265
  baseURL: "https://api.example.com",
224
- sanitizeHeaders: true, // opt-in — disabled by default
225
- sensitiveHeaders: ["x-internal-secret"], // extend the default list
266
+ sanitizeHeaders: true, // opt-in — disabled by default
267
+ sensitiveHeaders: ["x-internal-secret"], // extend the default list
226
268
  metrics: {
227
269
  onRequestStart: (info) => {
228
270
  // info.requestHeaders — sensitive values replaced with "REDACTED"
@@ -232,7 +274,7 @@ const client = createRestClient({
232
274
  });
233
275
  ```
234
276
 
235
- You can also use `sanitizeHeadersMap` directly:
277
+ Use `sanitizeHeadersMap` directly:
236
278
 
237
279
  ```js
238
280
  import { sanitizeHeadersMap } from "rest-pipeline-js";
@@ -246,9 +288,9 @@ const safe = sanitizeHeadersMap(
246
288
 
247
289
  ---
248
290
 
249
- #### RequestExecutor
291
+ ## RequestExecutor
250
292
 
251
- Wrapper for REST requests with retry, timeout (via AbortController), Retry-After header support, and backoff.
293
+ Wrapper for REST requests with retry, timeout (via `AbortController`), `Retry-After` header support, and backoff.
252
294
 
253
295
  ```js
254
296
  import { RequestExecutor } from "rest-pipeline-js";
@@ -260,7 +302,7 @@ const executor = new RequestExecutor({
260
302
  delayMs: 500,
261
303
  backoffMultiplier: 2,
262
304
  retriableStatus: [429, 500, 502, 503],
263
- maxRetryAfterMs: 30000, // cap Retry-After at 30 s
305
+ maxRetryAfterMs: 30000, // cap Retry-After at 30 s
264
306
  },
265
307
  });
266
308
 
@@ -272,11 +314,11 @@ When the server returns a `Retry-After` header (numeric seconds or HTTP-date), t
272
314
 
273
315
  ---
274
316
 
275
- #### PipelineOrchestrator
317
+ ## PipelineOrchestrator
276
318
 
277
319
  Main class for building and managing a pipeline of sequential (and parallel) stages.
278
320
 
279
- ##### Constructor
321
+ ### Constructor
280
322
 
281
323
  ```js
282
324
  new PipelineOrchestrator({
@@ -287,46 +329,46 @@ new PipelineOrchestrator({
287
329
  })
288
330
  ```
289
331
 
290
- ##### Key methods
291
-
292
- | Method | Description |
293
- | ------------------------------------------ | ------------------------------------------------------------------------------------- |
294
- | `run(onStepPause?, externalSignal?)` | Execute all stages. Returns `{ stageResults, success }` |
295
- | `rerunStep(stepKey, options?)` | Re-execute a single stage (respects condition, before, after, middleware) |
296
- | `abort()` | Abort pipeline execution (cancels the current HTTP request via AbortSignal) |
297
- | `isAborted()` | Check if pipeline was aborted |
298
- | `pause()` | Pause after the current stage completes |
299
- | `resume()` | Resume a paused pipeline |
300
- | `isPaused()` | Check if pipeline is paused |
301
- | `exportState()` | Serialize stageResults and logs to a plain object |
302
- | `importState(state)` | Restore stageResults and logs from a snapshot |
303
- | `getStageResults()` | Synchronous snapshot of all stage results (no subscription needed) |
304
- | `destroy()` | Run cleanup callbacks from all installed plugins |
305
- | `subscribeProgress(listener)` | Subscribe to progress updates |
306
- | `subscribeStageResults(listener)` | Subscribe to stageResults changes |
307
- | `subscribeStepProgress(stepKey, listener)` | Subscribe to a specific stage's progress |
308
- | `on(eventName, handler)` | Subscribe to any event (`step:<key>:start\|success\|error\|skipped\|progress`, `log`) |
309
- | `onStepStart/Finish/Error(handler)` | Subscribe to stage lifecycle events |
310
- | `getProgress()` | Get current progress snapshot |
311
- | `getLogs()` | Get all pipeline logs |
312
- | `clearStageResults()` | Reset results and progress |
313
-
314
- ##### Stage parameters (PipelineStageConfig)
315
-
316
- | Parameter | Description |
317
- | --------------------------------------------- | ------------------------------------------------------------------------ |
318
- | `key` | Unique stage identifier |
319
- | `request({ prev, allResults, sharedData })` | Main stage function — return value becomes the stage result |
320
- | `condition({ prev, allResults, sharedData })` | If returns `false`, stage is skipped with status `"skipped"` |
321
- | `before({ prev, allResults, sharedData })` | Pre-processing hook — returned value replaces `prev` passed to `request` |
322
- | `after({ result, allResults, sharedData })` | Post-processing hook — returned value replaces the stage result |
323
- | `errorHandler({ error, key, sharedData })` | Per-stage error handler |
324
- | `retryCount` | Override retry count for this stage |
325
- | `timeoutMs` | Override timeout for this stage |
326
- | `pauseBefore` | Delay in ms before executing `request` |
327
- | `pauseAfter` | Delay in ms after executing `request` |
328
-
329
- ##### Stage execution flow
332
+ ### Methods
333
+
334
+ | Method | Description |
335
+ |---|---|
336
+ | `run(onStepPause?, externalSignal?)` | Execute all stages. Returns `{ stageResults, success }` |
337
+ | `rerunStep(stepKey, options?)` | Re-execute a single stage (respects condition, before, after, middleware) |
338
+ | `abort()` | Abort pipeline execution (cancels the current HTTP request via AbortSignal) |
339
+ | `isAborted()` | Check if pipeline was aborted |
340
+ | `pause()` | Pause after the current stage completes |
341
+ | `resume()` | Resume a paused pipeline |
342
+ | `isPaused()` | Check if pipeline is paused |
343
+ | `exportState()` | Serialize stageResults and logs to a plain object |
344
+ | `importState(state)` | Restore stageResults and logs from a snapshot |
345
+ | `getStageResults()` | Synchronous snapshot of all stage results |
346
+ | `destroy()` | Run cleanup callbacks from all installed plugins |
347
+ | `subscribeProgress(listener)` | Subscribe to progress updates |
348
+ | `subscribeStageResults(listener)` | Subscribe to stageResults changes |
349
+ | `subscribeStepProgress(stepKey, listener)` | Subscribe to a specific stage's progress |
350
+ | `on(eventName, handler)` | Subscribe to any event (`step:<key>:start\|success\|error\|skipped\|progress`, `log`) |
351
+ | `onStepStart/Finish/Error(handler)` | Subscribe to stage lifecycle events |
352
+ | `getProgress()` | Get current progress snapshot |
353
+ | `getLogs()` | Get all pipeline logs |
354
+ | `clearStageResults()` | Reset results and progress |
355
+
356
+ ### Stage parameters (PipelineStageConfig)
357
+
358
+ | Parameter | Description |
359
+ |---|---|
360
+ | `key` | Unique stage identifier |
361
+ | `request({ prev, allResults, sharedData })` | Main stage function — return value becomes the stage result |
362
+ | `condition({ prev, allResults, sharedData })` | If returns `false`, stage is skipped with status `"skipped"` |
363
+ | `before({ prev, allResults, sharedData })` | Pre-processing hook — returned value replaces `prev` passed to `request` |
364
+ | `after({ result, allResults, sharedData })` | Post-processing hook — returned value replaces the stage result |
365
+ | `errorHandler({ error, key, sharedData })` | Per-stage error handler |
366
+ | `retryCount` | Override retry count for this stage |
367
+ | `timeoutMs` | Override timeout for this stage |
368
+ | `pauseBefore` | Delay in ms before executing `request` |
369
+ | `pauseAfter` | Delay in ms after executing `request` |
370
+
371
+ ### Stage execution flow
330
372
 
331
373
  ```
332
374
  condition? → false → [status: skipped] → next stage
@@ -351,9 +393,60 @@ On error at any point:
351
393
  └─► stage.errorHandler (if set) → middleware.onError → [status: error] → stop
352
394
  ```
353
395
 
396
+ ### Full example
397
+
398
+ ```js
399
+ import { PipelineOrchestrator } from "rest-pipeline-js";
400
+
401
+ const orchestrator = new PipelineOrchestrator({
402
+ config: {
403
+ stages: [
404
+ {
405
+ key: "fetchUser",
406
+ request: async ({ sharedData }) => {
407
+ const res = await fetch(`/api/users/${sharedData.userId}`);
408
+ return res.json();
409
+ },
410
+ },
411
+ {
412
+ key: "processData",
413
+ condition: ({ prev }) => prev !== null,
414
+ before: ({ prev }) => ({ ...prev, processed: true }),
415
+ request: async ({ prev }) => prev,
416
+ after: ({ result }) => ({ ...result, finishedAt: Date.now() }),
417
+ },
418
+ ],
419
+ middleware: {
420
+ beforeEach: ({ stage }) => console.log("Starting:", stage.key),
421
+ afterEach: ({ stage, result }) => console.log("Done:", stage.key, result.data),
422
+ onError: ({ stage, error }) => console.error("Error in", stage.key, error),
423
+ },
424
+ },
425
+ httpConfig: {
426
+ baseURL: "https://api.example.com",
427
+ retry: { attempts: 2, delayMs: 1000, backoffMultiplier: 2 },
428
+ cache: { enabled: true, ttlMs: 60000 },
429
+ },
430
+ sharedData: { userId: 42 },
431
+ options: { autoReset: true },
432
+ });
433
+
434
+ orchestrator.subscribeProgress((progress) => {
435
+ console.log("Stage:", progress.currentStage, "Statuses:", progress.stageStatuses);
436
+ });
437
+
438
+ orchestrator.on("step:fetchUser:success", (payload) => {
439
+ console.log("fetchUser done:", payload.data);
440
+ });
441
+
442
+ const result = await orchestrator.run();
443
+ console.log("Pipeline finished:", result.success);
444
+ console.log("Stage results:", result.stageResults);
445
+ ```
446
+
354
447
  ---
355
448
 
356
- ### Parallel stages
449
+ ## Parallel stages
357
450
 
358
451
  Group stages for concurrent execution using `parallel`:
359
452
 
@@ -368,7 +461,7 @@ const orchestrator = new PipelineOrchestrator({
368
461
  {
369
462
  key: "load-data",
370
463
  parallel: [
371
- { key: "loadUsers", request: async () => fetchUsers() },
464
+ { key: "loadUsers", request: async () => fetchUsers() },
372
465
  { key: "loadProducts", request: async () => fetchProducts() },
373
466
  { key: "loadSettings", request: async () => fetchSettings() },
374
467
  ],
@@ -388,16 +481,14 @@ const orchestrator = new PipelineOrchestrator({
388
481
 
389
482
  ---
390
483
 
391
- ### Global middleware
484
+ ## Global middleware
392
485
 
393
486
  Apply hooks to every stage without modifying individual stage configs:
394
487
 
395
488
  ```js
396
489
  const orchestrator = new PipelineOrchestrator({
397
490
  config: {
398
- stages: [
399
- /* ... */
400
- ],
491
+ stages: [ /* ... */ ],
401
492
  middleware: {
402
493
  beforeEach: async ({ stage, index, sharedData }) => {
403
494
  console.log(`[${index}] Starting: ${stage.key}`);
@@ -419,7 +510,7 @@ Middleware runs in addition to (not instead of) per-stage `errorHandler`.
419
510
 
420
511
  ---
421
512
 
422
- ### Pause / Resume
513
+ ## Pause / Resume
423
514
 
424
515
  Pause the pipeline after a stage and resume later:
425
516
 
@@ -444,7 +535,7 @@ await runPromise;
444
535
 
445
536
  ---
446
537
 
447
- ### Export / Import state
538
+ ## Export / Import state
448
539
 
449
540
  Save and restore the pipeline state across page reloads or sessions:
450
541
 
@@ -462,23 +553,21 @@ const orchestrator2 = new PipelineOrchestrator({ config });
462
553
  orchestrator2.importState(saved);
463
554
 
464
555
  console.log(orchestrator2.getProgress()); // restored progress
465
- console.log(orchestrator2.getLogs()); // restored logs (timestamps as Date objects)
556
+ console.log(orchestrator2.getLogs()); // restored logs (timestamps as Date objects)
466
557
  ```
467
558
 
468
559
  `exportState()` returns `{ stageResults, logs }` — a plain JSON-serializable object. Timestamps in logs are stored as ISO strings and restored as `Date` objects on `importState`.
469
560
 
470
561
  ---
471
562
 
472
- ### Pipeline metrics
563
+ ## Pipeline metrics
473
564
 
474
565
  Observe pipeline execution without modifying stage logic:
475
566
 
476
567
  ```js
477
568
  const orchestrator = new PipelineOrchestrator({
478
569
  config: {
479
- stages: [
480
- /* ... */
481
- ],
570
+ stages: [ /* ... */ ],
482
571
  metrics: {
483
572
  onPipelineStart: ({ timestamp }) => {
484
573
  console.log("Pipeline started at", new Date(timestamp).toISOString());
@@ -494,17 +583,17 @@ const orchestrator = new PipelineOrchestrator({
494
583
  });
495
584
  ```
496
585
 
497
- | Callback | Receives | Description |
498
- | ----------------- | --------------------------------------- | --------------------------------- |
499
- | `onPipelineStart` | `{ timestamp }` | Fires at the beginning of `run()` |
500
- | `onPipelineEnd` | `{ durationMs, success, stageResults }` | Fires when `run()` completes |
501
- | `onStepDuration` | `{ stepKey, durationMs, status }` | Fires after every executed step |
586
+ | Callback | Receives | Description |
587
+ |---|---|---|
588
+ | `onPipelineStart` | `{ timestamp }` | Fires at the beginning of `run()` |
589
+ | `onPipelineEnd` | `{ durationMs, success, stageResults }` | Fires when `run()` completes |
590
+ | `onStepDuration` | `{ stepKey, durationMs, status }` | Fires after every executed step |
502
591
 
503
592
  ---
504
593
 
505
- ### createPipeline() + pipe() builder
594
+ ## createPipeline() + pipe() builder
506
595
 
507
- #### createPipeline() — short factory
596
+ ### createPipeline() — short factory
508
597
 
509
598
  ```js
510
599
  import { createPipeline } from "rest-pipeline-js";
@@ -512,54 +601,51 @@ import { createPipeline } from "rest-pipeline-js";
512
601
  const orchestrator = createPipeline(
513
602
  [
514
603
  { key: "fetchUser", request: async () => fetchUser() },
515
- { key: "process", request: async ({ prev }) => process(prev) },
604
+ { key: "process", request: async ({ prev }) => process(prev) },
516
605
  ],
517
606
  {
518
- httpConfig: { baseURL: "https://api.example.com" },
519
- sharedData: { userId: 42 },
607
+ httpConfig: { baseURL: "https://api.example.com" },
608
+ sharedData: { userId: 42 },
520
609
  pipelineOptions: { continueOnError: false },
521
610
  metrics: {
522
- onStepDuration: ({ stepKey, durationMs }) =>
523
- console.log(stepKey, durationMs),
611
+ onStepDuration: ({ stepKey, durationMs }) => console.log(stepKey, durationMs),
524
612
  },
525
613
  },
526
614
  );
527
615
  ```
528
616
 
529
- #### pipe() — fluent builder
617
+ ### pipe() — fluent builder
530
618
 
531
619
  ```js
532
620
  import { pipe } from "rest-pipeline-js";
533
621
 
534
622
  const orchestrator = pipe()
535
- .step({ key: "auth", request: async () => getToken() })
623
+ .step({ key: "auth", request: async () => getToken() })
536
624
  .step({ key: "fetchUser", request: async ({ prev }) => fetchUser(prev) })
537
625
  .parallel([
538
- { key: "loadPosts", request: async () => fetchPosts() },
626
+ { key: "loadPosts", request: async () => fetchPosts() },
539
627
  { key: "loadNotifs", request: async () => fetchNotifications() },
540
628
  ])
541
629
  .stream({
542
630
  key: "liveUpdates",
543
- stream: async function* () {
544
- yield* subscribe("/events");
545
- },
631
+ stream: async function* () { yield* subscribe("/events"); },
546
632
  onChunk: (chunk) => updateUI(chunk),
547
633
  })
548
634
  .build({ httpConfig: { baseURL: "https://api.example.com" } });
549
635
  ```
550
636
 
551
- | Builder method | Description |
552
- | ----------------------------- | -------------------------------------------------------- |
553
- | `.step(stage)` | Add a sequential stage |
554
- | `.parallel(stages, options?)` | Add a parallel group (`key` auto-generated if omitted) |
555
- | `.subPipeline(item)` | Embed a sub-pipeline as a stage |
556
- | `.stream(stage)` | Add a stream stage (AsyncIterable) |
557
- | `.build(options?)` | Create and return a `PipelineOrchestrator` |
558
- | `.toConfig(options?)` | Return `PipelineConfig` without creating an orchestrator |
637
+ | Builder method | Description |
638
+ |---|---|
639
+ | `.step(stage)` | Add a sequential stage |
640
+ | `.parallel(stages, options?)` | Add a parallel group (`key` auto-generated if omitted) |
641
+ | `.subPipeline(item)` | Embed a sub-pipeline as a stage |
642
+ | `.stream(stage)` | Add a stream stage (AsyncIterable) |
643
+ | `.build(options?)` | Create and return a `PipelineOrchestrator` |
644
+ | `.toConfig(options?)` | Return `PipelineConfig` without creating an orchestrator |
559
645
 
560
646
  ---
561
647
 
562
- ### validatePipelineConfig()
648
+ ## validatePipelineConfig()
563
649
 
564
650
  Catch configuration errors before runtime:
565
651
 
@@ -569,8 +655,8 @@ import { validatePipelineConfig } from "rest-pipeline-js";
569
655
  const { valid, errors } = validatePipelineConfig({
570
656
  stages: [
571
657
  { key: "step1", request: async () => data },
572
- { key: "step1", request: async () => other }, // duplicate!
573
- { key: "", request: async () => other }, // empty key!
658
+ { key: "step1", request: async () => other }, // duplicate!
659
+ { key: "", request: async () => other }, // empty key!
574
660
  ],
575
661
  });
576
662
 
@@ -582,7 +668,7 @@ Validates: duplicate keys, empty/invalid keys, empty `stages` array, invalid fie
582
668
 
583
669
  ---
584
670
 
585
- ### Plugin system
671
+ ## Plugin system
586
672
 
587
673
  Package reusable orchestrator behavior into plugins:
588
674
 
@@ -592,21 +678,16 @@ const loggingPlugin = {
592
678
  install(orchestrator) {
593
679
  const off = orchestrator.on("log", (event) => {
594
680
  if (event.type === "step:success") console.log("✓", event.stepKey);
595
- if (event.type === "step:error")
596
- console.error("✗", event.stepKey, event.error);
681
+ if (event.type === "step:error") console.error("✗", event.stepKey, event.error);
597
682
  });
598
- return () => off(); // cleanup on orchestrator.destroy()
683
+ return () => off(); // cleanup on orchestrator.destroy()
599
684
  },
600
685
  };
601
686
 
602
687
  const orchestrator = new PipelineOrchestrator({
603
688
  config: {
604
- stages: [
605
- /* ... */
606
- ],
607
- options: {
608
- plugins: [loggingPlugin, analyticsPlugin],
609
- },
689
+ stages: [ /* ... */ ],
690
+ options: { plugins: [loggingPlugin, analyticsPlugin] },
610
691
  },
611
692
  });
612
693
 
@@ -619,7 +700,7 @@ orchestrator.destroy();
619
700
 
620
701
  ---
621
702
 
622
- ### Persist adapter
703
+ ## Persist adapter
623
704
 
624
705
  Automatically save and restore pipeline state across page reloads:
625
706
 
@@ -634,9 +715,7 @@ const localStorageAdapter = {
634
715
 
635
716
  const orchestrator = new PipelineOrchestrator({
636
717
  config: {
637
- stages: [
638
- /* ... */
639
- ],
718
+ stages: [ /* ... */ ],
640
719
  options: { persistAdapter: localStorageAdapter },
641
720
  },
642
721
  });
@@ -658,7 +737,7 @@ Both methods may be async (useful for IndexedDB or remote storage).
658
737
 
659
738
  ---
660
739
 
661
- ### Stream stages (SSE / AsyncIterable)
740
+ ## Stream stages (SSE / AsyncIterable)
662
741
 
663
742
  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.
664
743
 
@@ -668,7 +747,6 @@ const orchestrator = createPipeline([
668
747
  {
669
748
  key: "liveData",
670
749
  stream: async function* ({ prev }) {
671
- // prev is the result of "auth"
672
750
  const source = new EventSource(`/api/stream?token=${prev}`);
673
751
  yield* eventSourceToAsyncIterable(source);
674
752
  },
@@ -691,27 +769,23 @@ const orchestrator = createPipeline([
691
769
 
692
770
  ---
693
771
 
694
- ### HTTP Adapter (custom fetch / edge environments)
772
+ ## HTTP Adapter (custom fetch / edge environments)
695
773
 
696
774
  Replace the built-in axios client with any HTTP implementation:
697
775
 
698
776
  ```js
699
777
  const fetchAdapter = {
700
778
  async request(config) {
701
- const url = `${config.baseURL ?? ""}${config.url ?? ""}`;
702
- const res = await fetch(url, {
703
- method: config.method ?? "GET",
704
- body: config.data ? JSON.stringify(config.data) : undefined,
779
+ const url = `${config.baseURL ?? ""}${config.url ?? ""}`;
780
+ const res = await fetch(url, {
781
+ method: config.method ?? "GET",
782
+ body: config.data ? JSON.stringify(config.data) : undefined,
705
783
  headers: { "Content-Type": "application/json", ...config.headers },
706
- signal: config.signal,
784
+ signal: config.signal,
707
785
  });
708
786
  const data = await res.json();
709
- return {
710
- data,
711
- status: res.status,
712
- statusText: res.statusText,
713
- headers: Object.fromEntries(res.headers.entries()),
714
- };
787
+ return { data, status: res.status, statusText: res.statusText,
788
+ headers: Object.fromEntries(res.headers.entries()) };
715
789
  },
716
790
  };
717
791
 
@@ -720,7 +794,6 @@ const client = createRestClient({
720
794
  adapter: fetchAdapter,
721
795
  // Auth, interceptors, sanitizeHeaders, metrics still work on top of the adapter
722
796
  auth: { getToken: async () => token },
723
- interceptors: { request: [addCorrelationId] },
724
797
  });
725
798
  ```
726
799
 
@@ -734,9 +807,7 @@ type HttpAdapter = {
734
807
 
735
808
  ---
736
809
 
737
- ### Vue integration
738
-
739
- #### Example: use in Vue component
810
+ ## Vue integration
740
811
 
741
812
  ```vue
742
813
  <script setup>
@@ -746,13 +817,7 @@ import {
746
817
  usePipelineRunVue,
747
818
  } from "rest-pipeline-js/vue";
748
819
 
749
- const orchestrator = new PipelineOrchestrator({
750
- config: {
751
- stages: [
752
- /* ... */
753
- ],
754
- },
755
- });
820
+ const orchestrator = new PipelineOrchestrator({ config: { stages: [ /* ... */ ] } });
756
821
  const progress = usePipelineProgressVue(orchestrator);
757
822
  const { run, running, result, error, abort, pause, resume, rerunStep } =
758
823
  usePipelineRunVue(orchestrator);
@@ -771,23 +836,21 @@ const { run, running, result, error, abort, pause, resume, rerunStep } =
771
836
  </template>
772
837
  ```
773
838
 
774
- Composition functions (import from `rest-pipeline-js/vue`):
839
+ Composables (import from `rest-pipeline-js/vue`):
775
840
 
776
- | Function | Returns | Description |
777
- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------- |
778
- | `usePipelineProgressVue(orchestrator)` | `Ref<PipelineProgress>` | Reactive progress |
779
- | `usePipelineRunVue(orchestrator)` | `{ run, running, result, error, stageResults, abort, pause, resume, rerunStep, clearStageResults }` | Run pipeline and get reactive state |
780
- | `usePipelineStepEventVue(orchestrator, stepKey, eventType)` | `Ref<any>` | Last payload for a specific step event |
781
- | `usePipelineLogsVue(orchestrator)` | `Ref<log[]>` | Reactive logs |
782
- | `useRerunPipelineStepVue(orchestrator)` | `function` | Bound `rerunStep` |
783
- | `useRestClientVue(config)` | `ComputedRef<RestClient>` | Reactive REST client |
784
- | `usePipelineStageResultVue(orchestrator, stepKey)` | `Ref<PipelineStepResult \| null>` | Reactive result of a single stage |
841
+ | Composable | Returns | Description |
842
+ |---|---|---|
843
+ | `usePipelineProgressVue(orchestrator)` | `Ref<PipelineProgress>` | Reactive progress |
844
+ | `usePipelineRunVue(orchestrator)` | `{ run, running, result, error, stageResults, abort, pause, resume, rerunStep, clearStageResults }` | Run pipeline and get reactive state |
845
+ | `usePipelineStepEventVue(orchestrator, stepKey, eventType)` | `Ref<any>` | Last payload for a specific step event |
846
+ | `usePipelineLogsVue(orchestrator)` | `Ref<log[]>` | Reactive logs |
847
+ | `useRerunPipelineStepVue(orchestrator)` | `function` | Bound `rerunStep` |
848
+ | `useRestClientVue(config)` | `ComputedRef<RestClient>` | Reactive REST client |
849
+ | `usePipelineStageResultVue(orchestrator, stepKey)` | `Ref<PipelineStepResult \| null>` | Reactive result of a single stage |
785
850
 
786
851
  ---
787
852
 
788
- ### React integration
789
-
790
- #### Example: use in React component
853
+ ## React integration
791
854
 
792
855
  ```jsx
793
856
  import { useRef } from "react";
@@ -797,13 +860,7 @@ import {
797
860
  usePipelineRunReact,
798
861
  } from "rest-pipeline-js/react";
799
862
 
800
- const orchestrator = new PipelineOrchestrator({
801
- config: {
802
- stages: [
803
- /* ... */
804
- ],
805
- },
806
- });
863
+ const orchestrator = new PipelineOrchestrator({ config: { stages: [ /* ... */ ] } });
807
864
 
808
865
  export function PipelineComponent() {
809
866
  const progress = usePipelineProgressReact(orchestrator);
@@ -813,16 +870,12 @@ export function PipelineComponent() {
813
870
  return (
814
871
  <div>
815
872
  <div>Current stage: {progress.currentStage}</div>
816
- <button onClick={() => run()} disabled={running}>
817
- Start
818
- </button>
819
- <button onClick={() => abort()} disabled={!running}>
820
- Abort
821
- </button>
873
+ <button onClick={() => run()} disabled={running}>Start</button>
874
+ <button onClick={() => abort()} disabled={!running}>Abort</button>
822
875
  <button onClick={() => pause()}>Pause</button>
823
876
  <button onClick={() => resume()}>Resume</button>
824
877
  {result && <div>Done: {JSON.stringify(result)}</div>}
825
- {error && <div>Error: {error.message}</div>}
878
+ {error && <div>Error: {error.message}</div>}
826
879
  </div>
827
880
  );
828
881
  }
@@ -830,25 +883,25 @@ export function PipelineComponent() {
830
883
 
831
884
  Hooks (import from `rest-pipeline-js/react`):
832
885
 
833
- | Hook | Returns | Description |
834
- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------- |
835
- | `usePipelineProgressReact(orchestrator)` | `PipelineProgress` | Reactive progress |
836
- | `usePipelineRunReact(orchestrator)` | `[run, { running, result, error, stageResults, abort, pause, resume, rerunStep }]` | Run pipeline and get state |
837
- | `usePipelineStepEventReact(orchestrator, stepKey, eventType)` | `any` | Last payload for a specific step event |
838
- | `usePipelineLogsReact(orchestrator)` | `log[]` | Reactive logs |
839
- | `useRerunPipelineStepReact(orchestrator)` | `function` | Bound `rerunStep` |
840
- | `useRestClientReact(config)` | `RestClient` | Memoized REST client |
841
- | `usePipelineStageResultReact(orchestrator, stepKey)` | `PipelineStepResult \| null` | Result of a single stage |
886
+ | Hook | Returns | Description |
887
+ |---|---|---|
888
+ | `usePipelineProgressReact(orchestrator)` | `PipelineProgress` | Reactive progress |
889
+ | `usePipelineRunReact(orchestrator)` | `[run, { running, result, error, stageResults, abort, pause, resume, rerunStep }]` | Run pipeline and get state |
890
+ | `usePipelineStepEventReact(orchestrator, stepKey, eventType)` | `any` | Last payload for a specific step event |
891
+ | `usePipelineLogsReact(orchestrator)` | `log[]` | Reactive logs |
892
+ | `useRerunPipelineStepReact(orchestrator)` | `function` | Bound `rerunStep` |
893
+ | `useRestClientReact(config)` | `RestClient` | Memoized REST client |
894
+ | `usePipelineStageResultReact(orchestrator, stepKey)` | `PipelineStepResult \| null` | Result of a single stage |
842
895
 
843
896
  ---
844
897
 
845
898
  ## Entry points
846
899
 
847
- | Entry point | Use for | Contents |
848
- | ------------------------ | -------------- | --------------------------------------------------------------------------- |
849
- | `rest-pipeline-js` | Core only | `PipelineOrchestrator`, `createRestClient`, types, utilities. No Vue/React. |
850
- | `rest-pipeline-js/vue` | Vue projects | Core + Vue composition functions |
851
- | `rest-pipeline-js/react` | React projects | Core + React hooks |
900
+ | Entry point | Use for | Contents |
901
+ |---|---|---|
902
+ | `rest-pipeline-js` | Core only | `PipelineOrchestrator`, `createRestClient`, types, utilities. No Vue/React. |
903
+ | `rest-pipeline-js/vue` | Vue projects | Core + Vue composables |
904
+ | `rest-pipeline-js/react` | React projects | Core + React hooks |
852
905
 
853
906
  ```js
854
907
  // Core only
@@ -858,46 +911,67 @@ import { createRestClient, PipelineOrchestrator } from "rest-pipeline-js";
858
911
  import { PipelineOrchestrator, usePipelineRunVue } from "rest-pipeline-js/vue";
859
912
 
860
913
  // React
861
- import {
862
- PipelineOrchestrator,
863
- usePipelineRunReact,
864
- } from "rest-pipeline-js/react";
914
+ import { PipelineOrchestrator, usePipelineRunReact } from "rest-pipeline-js/react";
865
915
  ```
866
916
 
867
- `sideEffects: false` — unused entry points are tree-shaken. `react`/`react-dom` are `peerDependencies`.
917
+ `sideEffects: false` — unused entry points are tree-shaken. `react` / `react-dom` are `peerDependencies`.
868
918
 
869
919
  ---
870
920
 
871
- ## Requirements
921
+ ## Architecture
872
922
 
873
- - Node.js >= 14.0.0
874
- - Modern browser with ES2019+ support
875
-
876
- ---
877
-
878
- ## Vue Demo
879
-
880
- A live interactive demo of the pipeline running against a real flight-search API — 4 sequential stages: airport lookup, availability, ancillary services, and seat map.
881
-
882
- ```bash
883
- git clone https://github.com/macrulezru/pipeline-js.git
884
- cd pipeline-js
885
- npm install
886
- npm run demo:vue
887
923
  ```
888
-
889
- Opens at `http://localhost:3000` (or the next available port). Click **Run Pipeline** to execute all stages and watch results appear in real time. A boarding pass is rendered when all stages succeed.
924
+ rest-pipeline-js
925
+
926
+ ├── createRestClient (HttpConfig) → RestClient
927
+ │ ├── RequestExecutor — retry + backoff + Retry-After + AbortController timeout
928
+ │ ├── CacheManager — in-memory TTL cache for GET responses
929
+ │ ├── RateLimiter — concurrency + req/interval sliding window
930
+ │ ├── AuthProvider — Bearer injection; 401 refresh + one retry
931
+ │ ├── MetricsCollector — onRequestStart / onRequestEnd callbacks
932
+ │ ├── HeaderSanitizer — masks sensitive headers before metrics callbacks
933
+ │ └── HttpAdapter — pluggable transport (default: axios; swap for fetch / edge)
934
+
935
+ ├── PipelineOrchestrator (config, httpConfig?, sharedData?, options?)
936
+ │ ├── StageRunner — sequential execution loop; parallel via Promise.all
937
+ │ │ condition → pauseBefore → before → request → after → pauseAfter
938
+ │ ├── MiddlewareRunner — beforeEach / afterEach / onError across all stages
939
+ │ ├── EventBus — on() / emit(); step:start|success|error|skipped|progress, log
940
+ │ ├── ProgressTracker — subscribeProgress / subscribeStageResults / getProgress
941
+ │ ├── AbortController — abort() cancels current HTTP request via AbortSignal
942
+ │ ├── PauseController — pause() / resume() inter-stage checkpoints
943
+ │ ├── MetricsHooks — onPipelineStart / onPipelineEnd / onStepDuration
944
+ │ ├── StateSerializer — exportState() / importState() (stageResults + logs)
945
+ │ ├── PersistAdapter — pluggable save/load; auto-save after each stage
946
+ │ └── PluginManager — install() + destroy() lifecycle
947
+
948
+ ├── PipelineBuilder (pipe())
949
+ │ .step() / .parallel() / .subPipeline() / .stream() → .build() / .toConfig()
950
+
951
+ ├── createPipeline() — short factory wrapping new PipelineOrchestrator()
952
+
953
+ ├── validatePipelineConfig() — duplicate keys, empty keys, type checks, recursive
954
+
955
+ ├── /vue (separate entry point)
956
+ │ usePipelineRunVue / usePipelineProgressVue / usePipelineLogsVue
957
+ │ usePipelineStepEventVue / useRestClientVue / usePipelineStageResultVue
958
+
959
+ └── /react (separate entry point)
960
+ usePipelineRunReact / usePipelineProgressReact / usePipelineLogsReact
961
+ usePipelineStepEventReact / useRestClientReact / usePipelineStageResultReact
962
+ ```
890
963
 
891
964
  ---
892
965
 
893
- ## Development
966
+ ## Bundle size & peer dependencies
894
967
 
895
- ```bash
896
- git clone https://github.com/macrulezru/pipeline-js.git
897
- cd pipeline-js
898
- npm install
899
- npm test
900
- ```
968
+ | Entry point | Peer deps | Notes |
969
+ |---|---|---|
970
+ | `rest-pipeline-js` | — | Core — orchestrator, HTTP client, utilities. Depends on `axios`. |
971
+ | `rest-pipeline-js/vue` | `vue ^3.3` | Core + Vue composables |
972
+ | `rest-pipeline-js/react` | `react ^19`, `react-dom ^19` | Core + React hooks |
973
+
974
+ The package ships as tree-shakeable ESM (`dist/esm/`) and CommonJS (`dist/cjs/`). The `/vue` and `/react` entry points are code-split — importing one does not bundle the other.
901
975
 
902
976
  ---
903
977
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rest-pipeline-js",
3
- "version": "1.3.10",
3
+ "version": "1.3.11",
4
4
  "description": "Pipeline Orchestration Utilities for JavaScript REST API Clients",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",