rest-pipeline-js 1.3.10 → 1.3.12

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