rest-pipeline-js 1.2.6 → 1.3.5
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 +412 -747
- package/dist/cjs/cache.js +48 -0
- package/dist/cjs/pipeline-orchestrator.js +407 -441
- package/dist/cjs/progress-tracker.js +3 -3
- package/dist/cjs/rate-limiter.js +68 -0
- package/dist/cjs/request-executor.js +88 -10
- package/dist/cjs/rest-client.js +84 -71
- package/dist/cjs/usePipelineRun-react.js +12 -5
- package/dist/cjs/usePipelineRun-vue.js +17 -8
- package/dist/esm/cache.d.ts +14 -0
- package/dist/esm/cache.js +44 -0
- package/dist/esm/pipeline-orchestrator.d.ts +51 -94
- package/dist/esm/pipeline-orchestrator.js +411 -445
- package/dist/esm/progress-tracker.d.ts +7 -3
- package/dist/esm/progress-tracker.js +3 -3
- package/dist/esm/rate-limiter.d.ts +19 -0
- package/dist/esm/rate-limiter.js +64 -0
- package/dist/esm/request-executor.d.ts +8 -2
- package/dist/esm/request-executor.js +88 -10
- package/dist/esm/rest-client.d.ts +10 -5
- package/dist/esm/rest-client.js +83 -38
- package/dist/esm/types.d.ts +76 -5
- package/dist/esm/usePipelineRun-react.d.ts +8 -4
- package/dist/esm/usePipelineRun-react.js +13 -6
- package/dist/esm/usePipelineRun-vue.d.ts +8 -5
- package/dist/esm/usePipelineRun-vue.js +18 -9
- package/dist/esm/useRestClient-react.d.ts +7 -5
- package/dist/esm/useRestClient-vue.d.ts +7 -5
- package/package.json +1 -1
- package/src/cache.ts +47 -0
- package/src/index.ts +8 -8
- package/src/pipeline-orchestrator.ts +534 -519
- package/src/progress-tracker.ts +3 -3
- package/src/rate-limiter.ts +76 -0
- package/src/request-executor.ts +144 -16
- package/src/rest-client.ts +184 -40
- package/src/types.ts +132 -5
- package/src/usePipelineRun-react.ts +52 -36
- package/src/usePipelineRun-vue.ts +63 -47
- package/src/vue-demo/demo.vue +3 -3
- package/tests/pipeline-orchestrator.test.ts +717 -126
- package/tests/request-executor.test.ts +39 -9
- package/tests/rest-client.test.ts +259 -9
- package/tests/types.test.ts +105 -20
- package/dist/error-handler.d.ts +0 -7
- package/dist/error-handler.js +0 -6
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -7
- package/dist/pipeline-orchestrator.d.ts +0 -136
- package/dist/pipeline-orchestrator.js +0 -561
- package/dist/progress-tracker.d.ts +0 -22
- package/dist/progress-tracker.js +0 -43
- package/dist/react.d.ts +0 -5
- package/dist/react.js +0 -6
- package/dist/request-executor.d.ts +0 -9
- package/dist/request-executor.js +0 -29
- package/dist/rest-client.d.ts +0 -14
- package/dist/rest-client.js +0 -160
- package/dist/types.d.ts +0 -164
- package/dist/types.js +0 -1
- package/dist/usePipelineProgress-react.d.ts +0 -8
- package/dist/usePipelineProgress-react.js +0 -14
- package/dist/usePipelineProgress-vue.d.ts +0 -16
- package/dist/usePipelineProgress-vue.js +0 -14
- package/dist/usePipelineRun-react.d.ts +0 -12
- package/dist/usePipelineRun-react.js +0 -30
- package/dist/usePipelineRun-vue.d.ts +0 -21
- package/dist/usePipelineRun-vue.js +0 -42
- package/dist/usePipelineStepEvents-react.d.ts +0 -29
- package/dist/usePipelineStepEvents-react.js +0 -41
- package/dist/usePipelineStepEvents-vue.d.ts +0 -39
- package/dist/usePipelineStepEvents-vue.js +0 -40
- package/dist/useRestClient-react.d.ts +0 -15
- package/dist/useRestClient-react.js +0 -10
- package/dist/useRestClient-vue.d.ts +0 -15
- package/dist/useRestClient-vue.js +0 -10
- package/dist/vue.d.ts +0 -5
- package/dist/vue.js +0 -6
package/README.md
CHANGED
|
@@ -22,936 +22,601 @@ import { createRestClient } from "rest-pipeline-js";
|
|
|
22
22
|
const client = createRestClient({
|
|
23
23
|
baseURL: "https://api.example.com",
|
|
24
24
|
timeout: 5000,
|
|
25
|
-
|
|
25
|
+
retry: {
|
|
26
|
+
attempts: 2,
|
|
27
|
+
delayMs: 500,
|
|
28
|
+
backoffMultiplier: 2,
|
|
29
|
+
retriableStatus: [429, 500, 503],
|
|
30
|
+
},
|
|
31
|
+
cache: { enabled: true, ttlMs: 60000 },
|
|
32
|
+
rateLimit: { maxConcurrent: 3, maxRequestsPerInterval: 10, intervalMs: 1000 },
|
|
33
|
+
// Auth Provider — token injected automatically, 401 triggers refresh + one retry
|
|
34
|
+
auth: {
|
|
35
|
+
getToken: async () => localStorage.getItem("token") ?? "",
|
|
36
|
+
onUnauthorized: async () => {
|
|
37
|
+
/* refresh token here */
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
// Mask sensitive headers in metrics (authorization, x-api-key, cookie, …)
|
|
41
|
+
sanitizeHeaders: true,
|
|
26
42
|
});
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
const res = await client.get("/users/1");
|
|
45
|
+
console.log(res.data);
|
|
46
|
+
|
|
47
|
+
// PATCH support
|
|
48
|
+
await client.patch("/users/1", { name: "Alice" });
|
|
49
|
+
|
|
50
|
+
// Cancellable request
|
|
51
|
+
const req = client.cancellableRequest("my-key", "/search", {
|
|
52
|
+
params: { q: "foo" },
|
|
53
|
+
});
|
|
54
|
+
// Cancel it any time:
|
|
55
|
+
client.cancelRequest("my-key");
|
|
36
56
|
```
|
|
37
57
|
|
|
58
|
+
---
|
|
59
|
+
|
|
38
60
|
#### Example: Run a pipeline, handle errors, track progress, use shared data
|
|
39
61
|
|
|
40
62
|
```js
|
|
41
63
|
import { PipelineOrchestrator } from "rest-pipeline-js";
|
|
42
64
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
const orchestrator = new PipelineOrchestrator({
|
|
66
|
+
config: {
|
|
67
|
+
stages: [
|
|
68
|
+
{
|
|
69
|
+
key: "fetchUser",
|
|
70
|
+
request: async ({ sharedData }) => {
|
|
71
|
+
const res = await fetch(`/api/users/${sharedData.userId}`);
|
|
72
|
+
return res.json();
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: "processData",
|
|
77
|
+
condition: ({ prev }) => prev !== null,
|
|
78
|
+
before: ({ prev }) => ({ ...prev, processed: true }),
|
|
79
|
+
request: async ({ prev }) => prev,
|
|
80
|
+
after: ({ result }) => ({ ...result, finishedAt: Date.now() }),
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
middleware: {
|
|
84
|
+
beforeEach: ({ stage }) => console.log("Starting:", stage.key),
|
|
85
|
+
afterEach: ({ stage, result }) =>
|
|
86
|
+
console.log("Done:", stage.key, result.data),
|
|
87
|
+
onError: ({ stage, error }) =>
|
|
88
|
+
console.error("Error in", stage.key, error),
|
|
51
89
|
},
|
|
52
|
-
],
|
|
53
|
-
};
|
|
54
|
-
const httpConfig = {
|
|
55
|
-
baseURL: "https://api.example.com",
|
|
56
|
-
timeout: 7000,
|
|
57
|
-
headers: { Authorization: "Bearer TOKEN" },
|
|
58
|
-
retry: { attempts: 2, delayMs: 1000 },
|
|
59
|
-
cache: { enabled: true, ttlMs: 60000 },
|
|
60
|
-
rateLimit: { maxConcurrent: 2 },
|
|
61
|
-
metrics: {
|
|
62
|
-
onRequestStart: (info) => console.log("Start:", info),
|
|
63
|
-
onRequestEnd: (info) => console.log("End:", info),
|
|
64
90
|
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
sharedData,
|
|
71
|
-
{ autoReset: true },
|
|
72
|
-
);
|
|
91
|
+
httpConfig: {
|
|
92
|
+
baseURL: "https://api.example.com",
|
|
93
|
+
retry: { attempts: 2, delayMs: 1000, backoffMultiplier: 2 },
|
|
94
|
+
cache: { enabled: true, ttlMs: 60000 },
|
|
95
|
+
},
|
|
96
|
+
sharedData: { userId: 42 },
|
|
97
|
+
options: { autoReset: true },
|
|
98
|
+
});
|
|
73
99
|
|
|
74
100
|
orchestrator.subscribeProgress((progress) => {
|
|
75
101
|
console.log(
|
|
76
|
-
"
|
|
102
|
+
"Stage:",
|
|
77
103
|
progress.currentStage,
|
|
78
104
|
"Statuses:",
|
|
79
105
|
progress.stageStatuses,
|
|
80
106
|
);
|
|
81
107
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
orchestrator.on("step:step2:error", (payload) => {
|
|
86
|
-
console.error("Step 2 error:", payload.error);
|
|
87
|
-
});
|
|
88
|
-
orchestrator.on("log", () => {
|
|
89
|
-
console.log("Logs:", orchestrator.getLogs());
|
|
108
|
+
|
|
109
|
+
orchestrator.on("step:fetchUser:success", (payload) => {
|
|
110
|
+
console.log("fetchUser done:", payload.data);
|
|
90
111
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
console.log("Stage results:", result.stageResults);
|
|
96
|
-
})
|
|
97
|
-
.catch((err) => {
|
|
98
|
-
console.error("Pipeline error:", err);
|
|
99
|
-
});
|
|
100
|
-
// orchestrator.rerunStep('step2');
|
|
112
|
+
|
|
113
|
+
const result = await orchestrator.run();
|
|
114
|
+
console.log("Pipeline finished:", result.success);
|
|
115
|
+
console.log("Stage results:", result.stageResults);
|
|
101
116
|
```
|
|
102
117
|
|
|
103
118
|
---
|
|
104
119
|
|
|
105
|
-
The module provides a universal mechanism for building and managing REST API pipelines with progress tracking, error handling, event subscriptions, and extensibility.
|
|
106
|
-
|
|
107
120
|
### Main classes and functions
|
|
108
121
|
|
|
109
122
|
#### createRestClient(config: HttpConfig): RestClient
|
|
110
123
|
|
|
111
|
-
Creates a REST client with advanced HTTP
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
Creates a REST client with advanced HTTP features.
|
|
125
|
+
|
|
126
|
+
**Available methods:**
|
|
127
|
+
|
|
128
|
+
| Method | Description |
|
|
129
|
+
| --------------------------------------- | ---------------------------------- |
|
|
130
|
+
| `get(url, config?)` | GET request |
|
|
131
|
+
| `post(url, data?, config?)` | POST request |
|
|
132
|
+
| `put(url, data?, config?)` | PUT request |
|
|
133
|
+
| `patch(url, data?, config?)` | PATCH request |
|
|
134
|
+
| `delete(url, config?)` | DELETE request |
|
|
135
|
+
| `request(url, config?)` | Generic request |
|
|
136
|
+
| `cancellableRequest(key, url, config?)` | Request cancellable by key |
|
|
137
|
+
| `cancelRequest(key)` | Cancel request by key |
|
|
138
|
+
| `clearCache()` | Clear this client's response cache |
|
|
139
|
+
|
|
140
|
+
**HttpConfig options:**
|
|
141
|
+
|
|
142
|
+
| Option | Description |
|
|
143
|
+
| ---------------------------------- | -------------------------------------------------------------------------------- |
|
|
144
|
+
| `baseURL` | Base URL for all requests |
|
|
145
|
+
| `timeout` | Request timeout in ms |
|
|
146
|
+
| `headers` | Default headers |
|
|
147
|
+
| `withCredentials` | Include cookies |
|
|
148
|
+
| `retry.attempts` | Number of retry attempts |
|
|
149
|
+
| `retry.delayMs` | Base delay between retries in ms |
|
|
150
|
+
| `retry.backoffMultiplier` | Exponential backoff multiplier |
|
|
151
|
+
| `retry.retriableStatus` | HTTP status codes eligible for retry (e.g. `[429, 500, 503]`) |
|
|
152
|
+
| `cache.enabled` | Enable response caching for GET requests |
|
|
153
|
+
| `cache.ttlMs` | Cache TTL in ms |
|
|
154
|
+
| `rateLimit.maxConcurrent` | Max simultaneous requests |
|
|
155
|
+
| `rateLimit.maxRequestsPerInterval` | Max requests per time window |
|
|
156
|
+
| `rateLimit.intervalMs` | Time window size in ms |
|
|
157
|
+
| `metrics.onRequestStart` | Callback on request start |
|
|
158
|
+
| `metrics.onRequestEnd` | Callback on request end (includes duration and bytes) |
|
|
159
|
+
| `auth.getToken` | Async function returning a Bearer token (called before every request) |
|
|
160
|
+
| `auth.onUnauthorized` | Optional async callback on 401 — refresh the token here; request is retried once |
|
|
161
|
+
| `sanitizeHeaders` | Mask sensitive headers in metrics callbacks (default: `false`) |
|
|
162
|
+
| `sensitiveHeaders` | Additional headers to mask (extends `DEFAULT_SENSITIVE_HEADERS`) |
|
|
163
|
+
| `retry.maxRetryAfterMs` | Max wait from `Retry-After` header in ms (default: `60000`) |
|
|
164
|
+
|
|
165
|
+
**Per-request cache override:**
|
|
114
166
|
|
|
115
167
|
```js
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
headers: { Authorization: "Bearer TOKEN" },
|
|
121
|
-
retry: { attempts: 2 },
|
|
122
|
-
cache: { enabled: true, ttlMs: 60000 },
|
|
168
|
+
const res = await client.get("/data", {
|
|
169
|
+
useCache: true,
|
|
170
|
+
cacheTtlMs: 30000,
|
|
171
|
+
cacheKey: "my-custom-key",
|
|
123
172
|
});
|
|
124
|
-
async function getUser(id) {
|
|
125
|
-
const res = await client.request(`/users/${id}`);
|
|
126
|
-
if (res.error) {
|
|
127
|
-
console.error("Error:", res.error);
|
|
128
|
-
} else {
|
|
129
|
-
console.log("User:", res.data);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
173
|
```
|
|
133
174
|
|
|
134
175
|
---
|
|
135
176
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
Wrapper for REST requests with retry and timeout support.
|
|
177
|
+
### Auth Provider
|
|
139
178
|
|
|
140
|
-
|
|
179
|
+
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.
|
|
141
180
|
|
|
142
181
|
```js
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
182
|
+
const client = createRestClient({
|
|
183
|
+
baseURL: "https://api.example.com",
|
|
184
|
+
auth: {
|
|
185
|
+
getToken: async () => {
|
|
186
|
+
return localStorage.getItem("access_token") ?? "";
|
|
187
|
+
},
|
|
188
|
+
onUnauthorized: async () => {
|
|
189
|
+
// refresh token, update storage
|
|
190
|
+
const newToken = await refreshAccessToken();
|
|
191
|
+
localStorage.setItem("access_token", newToken);
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Authorization: Bearer <token> is added automatically to every request
|
|
197
|
+
const res = await client.get("/profile");
|
|
157
198
|
```
|
|
158
199
|
|
|
159
200
|
---
|
|
160
201
|
|
|
161
|
-
|
|
202
|
+
### Log Sanitization
|
|
162
203
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
##### Key methods and parameters
|
|
166
|
-
|
|
167
|
-
- **constructor(pipelineConfig, httpConfig, sharedData?, options?)**
|
|
168
|
-
- `pipelineConfig` — array of stages, their params, conditions, handlers
|
|
169
|
-
- `httpConfig` — HTTP client config
|
|
170
|
-
- `sharedData` — shared data pool between stages
|
|
171
|
-
- `options.autoReset` — whether to reset state after finish
|
|
172
|
-
- **run(onStepPause?, externalSignal?)** — run the pipeline
|
|
173
|
-
- `onStepPause(stepIndex, stepResult, stageResults)` — callback for pause/confirmation/modification between stages
|
|
174
|
-
- `externalSignal` — external AbortSignal
|
|
175
|
-
- Returns: `{ stageResults, success }`
|
|
176
|
-
- **rerunStep(stepKey, options?)** — rerun a single stage
|
|
177
|
-
- **subscribeProgress(listener)** — subscribe to progress updates
|
|
178
|
-
- **subscribeStageResults(listener)** — subscribe to stage results
|
|
179
|
-
- **subscribeStepProgress(stepKey, listener)** — subscribe to a specific stage's progress
|
|
180
|
-
- **on(eventName, handler)** — universal event subscription
|
|
181
|
-
- **onStepStart/Finish/Error(handler)** — subscribe to stage events
|
|
182
|
-
- **getProgress()** — get current progress snapshot
|
|
183
|
-
- **getProgressRef()** — get progress object (for reactivity)
|
|
184
|
-
- **getLogs()** — get pipeline logs
|
|
185
|
-
- **abort()** — abort pipeline
|
|
186
|
-
- **isAborted()** — check if pipeline was aborted
|
|
187
|
-
|
|
188
|
-
##### Stage parameters
|
|
189
|
-
|
|
190
|
-
- `key` — unique stage key
|
|
191
|
-
- `command` — endpoint/command for request
|
|
192
|
-
- `method` — HTTP method
|
|
193
|
-
- `dependsOn` — array of stage keys this depends on
|
|
194
|
-
- `condition({ prev, allResults, sharedData })` — condition function
|
|
195
|
-
- `before({ prev, allResults, sharedData })` — pre-processing hook (called before request; can modify input)
|
|
196
|
-
- `request({ prev, allResults, sharedData })` — custom request function. If before returns a value, it will be passed to request as prev.
|
|
197
|
-
- `after({ result, allResults, sharedData })` — post-processing hook (called after request, before next stage; can modify result)
|
|
198
|
-
- `pauseBefore` — optional pause (in ms) before request execution
|
|
199
|
-
- `pauseAfter` — optional pause (in ms) after request execution
|
|
200
|
-
- `retryCount`, `timeoutMs` — per-stage retry/timeout
|
|
201
|
-
- `errorHandler({ error, key, sharedData })` — custom error handler
|
|
202
|
-
|
|
203
|
-
##### Step execution flow diagram
|
|
204
|
+
Mask sensitive headers in metrics callbacks (`onRequestStart` / `onRequestEnd`) so they never appear in logs.
|
|
204
205
|
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
│ before │
|
|
208
|
-
│ (optional) │
|
|
209
|
-
└─────┬──────┘
|
|
210
|
-
│
|
|
211
|
-
▼
|
|
212
|
-
┌────────────┐
|
|
213
|
-
│ request │
|
|
214
|
-
└─────┬──────┘
|
|
215
|
-
│
|
|
216
|
-
▼
|
|
217
|
-
┌────────────┐
|
|
218
|
-
│ after │
|
|
219
|
-
│ (optional) │
|
|
220
|
-
└─────┬──────┘
|
|
221
|
-
│
|
|
222
|
-
▼
|
|
223
|
-
┌────────────┐
|
|
224
|
-
│ next step │
|
|
225
|
-
└────────────┘
|
|
226
|
-
|
|
227
|
-
If an error occurs at any stage:
|
|
228
|
-
└─► errorHandler (if defined) → error result
|
|
229
|
-
```
|
|
206
|
+
```js
|
|
207
|
+
import { createRestClient, DEFAULT_SENSITIVE_HEADERS } from "rest-pipeline-js";
|
|
230
208
|
|
|
231
|
-
|
|
209
|
+
// DEFAULT_SENSITIVE_HEADERS includes: authorization, x-api-key, x-auth-token,
|
|
210
|
+
// cookie, set-cookie, proxy-authorization
|
|
232
211
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
{
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
method: "POST",
|
|
242
|
-
dependsOn: ["first"],
|
|
212
|
+
const client = createRestClient({
|
|
213
|
+
baseURL: "https://api.example.com",
|
|
214
|
+
sanitizeHeaders: true, // opt-in — disabled by default
|
|
215
|
+
sensitiveHeaders: ["x-internal-secret"], // extend the default list
|
|
216
|
+
metrics: {
|
|
217
|
+
onRequestStart: (info) => {
|
|
218
|
+
// info.requestHeaders — sensitive values replaced with "REDACTED"
|
|
219
|
+
console.log(info.requestHeaders);
|
|
243
220
|
},
|
|
244
|
-
|
|
245
|
-
};
|
|
246
|
-
const httpConfig = { baseURL: "https://api.example.com" };
|
|
247
|
-
const sharedData = { sessionId: "abc" };
|
|
248
|
-
const orchestrator = new PipelineOrchestrator(
|
|
249
|
-
pipelineConfig,
|
|
250
|
-
httpConfig,
|
|
251
|
-
sharedData,
|
|
252
|
-
);
|
|
253
|
-
orchestrator.subscribeProgress((progress) => {
|
|
254
|
-
console.log("Progress:", progress);
|
|
255
|
-
});
|
|
256
|
-
orchestrator.on("step:first:success", (payload) => {
|
|
257
|
-
console.log("First stage done:", payload.data);
|
|
221
|
+
},
|
|
258
222
|
});
|
|
259
|
-
orchestrator
|
|
260
|
-
.run(async (i, result) => {
|
|
261
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
262
|
-
return result;
|
|
263
|
-
})
|
|
264
|
-
.then((result) => console.log("Pipeline finished:", result))
|
|
265
|
-
.catch((err) => console.error("Pipeline error:", err));
|
|
266
223
|
```
|
|
267
224
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
#### ProgressTracker
|
|
271
|
-
|
|
272
|
-
Internal class for tracking pipeline progress.
|
|
273
|
-
|
|
274
|
-
#### Example
|
|
225
|
+
You can also use `sanitizeHeadersMap` directly:
|
|
275
226
|
|
|
276
227
|
```js
|
|
277
|
-
import {
|
|
278
|
-
const tracker = new ProgressTracker(3);
|
|
279
|
-
tracker.subscribe((progress) => {
|
|
280
|
-
console.log("Current progress:", progress);
|
|
281
|
-
});
|
|
282
|
-
tracker.updateStage(1, "success");
|
|
283
|
-
console.log(tracker.getProgress());
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
---
|
|
287
|
-
|
|
288
|
-
#### ErrorHandler
|
|
289
|
-
|
|
290
|
-
Class for handling pipeline stage errors.
|
|
291
|
-
|
|
292
|
-
#### Example
|
|
228
|
+
import { sanitizeHeadersMap } from "rest-pipeline-js";
|
|
293
229
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
230
|
+
const safe = sanitizeHeadersMap(
|
|
231
|
+
{ authorization: "Bearer abc", "content-type": "application/json" },
|
|
232
|
+
["x-custom-secret"],
|
|
233
|
+
);
|
|
234
|
+
// { authorization: "REDACTED", "content-type": "application/json" }
|
|
299
235
|
```
|
|
300
236
|
|
|
301
|
-
#### Types and interfaces
|
|
302
|
-
|
|
303
|
-
- **HttpConfig** — REST client config (baseURL, timeout, headers, retry, cache, rateLimit, metrics)
|
|
304
|
-
- **ApiError** — API error description
|
|
305
|
-
- **ApiResponse<T>** — API response (data, error, status)
|
|
306
|
-
- **PipelineConfig, PipelineResult, PipelineStepEvent, PipelineStepStatus** — pipeline and stage types
|
|
307
|
-
|
|
308
237
|
---
|
|
309
238
|
|
|
310
|
-
|
|
239
|
+
#### RequestExecutor
|
|
311
240
|
|
|
312
|
-
|
|
241
|
+
Wrapper for REST requests with retry, timeout (via AbortController), Retry-After header support, and backoff.
|
|
313
242
|
|
|
314
243
|
```js
|
|
315
|
-
|
|
316
|
-
import { ref } from 'vue';
|
|
317
|
-
import { PipelineOrchestrator, usePipelineProgressVue, usePipelineRunVue } from 'rest-pipeline-js/vue';
|
|
318
|
-
const pipelineConfig = { stages: [/* ... */] };
|
|
319
|
-
const httpConfig = { baseURL: 'https://api.example.com' };
|
|
320
|
-
const orchestrator = new PipelineOrchestrator(pipelineConfig, httpConfig);
|
|
321
|
-
const progress = usePipelineProgressVue(orchestrator);
|
|
322
|
-
const { run, running, result, error } = usePipelineRunVue(orchestrator);
|
|
323
|
-
</script>
|
|
324
|
-
<template>
|
|
325
|
-
<div>
|
|
326
|
-
<div>Current stage: {{ progress.value.currentStage }}</div>
|
|
327
|
-
<button @click="run()" :disabled="running">Start</button>
|
|
328
|
-
<div v-if="result">Done: {{ result }}</div>
|
|
329
|
-
<div v-if="error">Error: {{ error.message }}</div>
|
|
330
|
-
</div>
|
|
331
|
-
</template>
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
---
|
|
335
|
-
|
|
336
|
-
Composition functions for Vue 3 (import from `rest-pipeline-js/vue`):
|
|
337
|
-
|
|
338
|
-
- **usePipelineProgressVue(orchestrator)** — reactive pipeline progress (Ref<PipelineProgress>)
|
|
339
|
-
- **usePipelineRunVue(orchestrator)** — run pipeline and get reactive status (run, running, result, error)
|
|
340
|
-
- **usePipelineStepEventVue(orchestrator, stepKey, eventType)** — subscribe to stage events (success, error, progress)
|
|
341
|
-
- **usePipelineLogsVue(orchestrator)** — reactive pipeline logs
|
|
342
|
-
- **useRerunPipelineStepVue(orchestrator)** — rerun a stage
|
|
343
|
-
- **useRestClientVue(config)** — reactive REST client (computed)
|
|
344
|
-
|
|
345
|
-
---
|
|
346
|
-
|
|
347
|
-
### React integration
|
|
244
|
+
import { RequestExecutor } from "rest-pipeline-js";
|
|
348
245
|
|
|
349
|
-
|
|
246
|
+
const executor = new RequestExecutor({
|
|
247
|
+
baseURL: "https://api.example.com",
|
|
248
|
+
retry: {
|
|
249
|
+
attempts: 3,
|
|
250
|
+
delayMs: 500,
|
|
251
|
+
backoffMultiplier: 2,
|
|
252
|
+
retriableStatus: [429, 500, 502, 503],
|
|
253
|
+
maxRetryAfterMs: 30000, // cap Retry-After at 30 s
|
|
254
|
+
},
|
|
255
|
+
});
|
|
350
256
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
import {
|
|
354
|
-
PipelineOrchestrator,
|
|
355
|
-
usePipelineProgressReact,
|
|
356
|
-
usePipelineRunReact,
|
|
357
|
-
} from "rest-pipeline-js/react";
|
|
358
|
-
const pipelineConfig = {
|
|
359
|
-
stages: [
|
|
360
|
-
/* ... */
|
|
361
|
-
],
|
|
362
|
-
};
|
|
363
|
-
const httpConfig = { baseURL: "https://api.example.com" };
|
|
364
|
-
const orchestrator = new PipelineOrchestrator(pipelineConfig, httpConfig);
|
|
365
|
-
export function PipelineComponent() {
|
|
366
|
-
const progress = usePipelineProgressReact(orchestrator);
|
|
367
|
-
const [run, { running, result, error }] = usePipelineRunReact(orchestrator);
|
|
368
|
-
return (
|
|
369
|
-
<div>
|
|
370
|
-
<div>Current stage: {progress.currentStage}</div>
|
|
371
|
-
<button onClick={() => run()} disabled={running}>
|
|
372
|
-
Start
|
|
373
|
-
</button>
|
|
374
|
-
{result && <div>Done: {JSON.stringify(result)}</div>}
|
|
375
|
-
{error && <div>Error: {error.message}</div>}
|
|
376
|
-
</div>
|
|
377
|
-
);
|
|
378
|
-
}
|
|
257
|
+
// 5th arg: external AbortSignal (e.g. from orchestrator.abort())
|
|
258
|
+
const res = await executor.execute("/data", undefined, 3, 5000, signal);
|
|
379
259
|
```
|
|
380
260
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
Hooks for React (import from `rest-pipeline-js/react`):
|
|
384
|
-
|
|
385
|
-
- **usePipelineProgressReact(orchestrator)** — subscribe to pipeline progress (PipelineProgress)
|
|
386
|
-
- **usePipelineRunReact(orchestrator)** — run pipeline and get status ([run, { running, result, error }])
|
|
387
|
-
- **usePipelineStepEventReact(orchestrator, stepKey, eventType)** — subscribe to stage events (success/error/progress)
|
|
388
|
-
- **usePipelineLogsReact(orchestrator)** — subscribe to pipeline logs
|
|
389
|
-
- **useRerunPipelineStepReact(orchestrator)** — rerun a stage
|
|
390
|
-
- **useRestClientReact(config)** — memoized REST client
|
|
391
|
-
|
|
392
|
-
---
|
|
393
|
-
|
|
394
|
-
## Requirements
|
|
395
|
-
|
|
396
|
-
- Node.js >= 14.0.0
|
|
397
|
-
- Modern browser with ES2020 support
|
|
261
|
+
When the server returns a `Retry-After` header (numeric seconds or HTTP-date), that delay takes priority over the backoff formula. Values exceeding `maxRetryAfterMs` are clamped to the cap. Timeout is enforced via `AbortController` — the actual HTTP request is cancelled, not just the promise.
|
|
398
262
|
|
|
399
263
|
---
|
|
400
264
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
The package has three entry points so that bundlers do not pull in React when you only use core or Vue.
|
|
404
|
-
|
|
405
|
-
| Entry point | Use for | Contents |
|
|
406
|
-
|-------------|---------|----------|
|
|
407
|
-
| `rest-pipeline-js` | Core only | `PipelineOrchestrator`, `createRestClient`, types, `rest-client`, `request-executor`, `error-handler`, `progress-tracker`. No Vue/React. |
|
|
408
|
-
| `rest-pipeline-js/vue` | Vue projects | Everything from core + Vue hooks: `usePipelineProgressVue`, `usePipelineRunVue`, `useRestClientVue`, `usePipelineStepEventVue`, `usePipelineLogsVue`, `useRerunPipelineStepVue`. |
|
|
409
|
-
| `rest-pipeline-js/react` | React projects | Everything from core + React hooks: `usePipelineProgressReact`, `usePipelineRunReact`, `useRestClientReact`, `usePipelineStepEventReact`, `usePipelineLogsReact`, `useRerunPipelineStepReact`. |
|
|
410
|
-
|
|
411
|
-
**Recommended imports:**
|
|
412
|
-
|
|
413
|
-
- **Core only** (no framework):
|
|
414
|
-
|
|
415
|
-
```js
|
|
416
|
-
import { createRestClient, PipelineOrchestrator } from "rest-pipeline-js";
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
- **Vue** (core + Vue hooks):
|
|
420
|
-
|
|
421
|
-
```js
|
|
422
|
-
import { PipelineOrchestrator, usePipelineRunVue } from "rest-pipeline-js/vue";
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
- **React** (core + React hooks):
|
|
426
|
-
|
|
427
|
-
```js
|
|
428
|
-
import { PipelineOrchestrator, usePipelineRunReact } from "rest-pipeline-js/react";
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
If you use only `rest-pipeline-js` or `rest-pipeline-js/vue`, the bundler will not resolve or include `react`. The package sets `sideEffects: false` and declares `react`/`react-dom` as `peerDependencies` for the React entry point.
|
|
265
|
+
#### PipelineOrchestrator
|
|
432
266
|
|
|
433
|
-
|
|
267
|
+
Main class for building and managing a pipeline of sequential (and parallel) stages.
|
|
434
268
|
|
|
435
|
-
|
|
269
|
+
##### Constructor
|
|
436
270
|
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
271
|
+
```js
|
|
272
|
+
new PipelineOrchestrator({
|
|
273
|
+
config, // PipelineConfig — stages and optional middleware
|
|
274
|
+
httpConfig?, // HttpConfig — HTTP client settings
|
|
275
|
+
sharedData?, // Record<string, any> — shared pool across all stages
|
|
276
|
+
options?, // { autoReset?: boolean }
|
|
277
|
+
})
|
|
444
278
|
```
|
|
445
279
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
280
|
+
##### Key methods
|
|
281
|
+
|
|
282
|
+
| Method | Description |
|
|
283
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------- |
|
|
284
|
+
| `run(onStepPause?, externalSignal?)` | Execute all stages. Returns `{ stageResults, success }` |
|
|
285
|
+
| `rerunStep(stepKey, options?)` | Re-execute a single stage (respects condition, before, after, middleware) |
|
|
286
|
+
| `abort()` | Abort pipeline execution (cancels the current HTTP request via AbortSignal) |
|
|
287
|
+
| `isAborted()` | Check if pipeline was aborted |
|
|
288
|
+
| `pause()` | Pause after the current stage completes |
|
|
289
|
+
| `resume()` | Resume a paused pipeline |
|
|
290
|
+
| `isPaused()` | Check if pipeline is paused |
|
|
291
|
+
| `exportState()` | Serialize stageResults and logs to a plain object |
|
|
292
|
+
| `importState(state)` | Restore stageResults and logs from a snapshot |
|
|
293
|
+
| `subscribeProgress(listener)` | Subscribe to progress updates |
|
|
294
|
+
| `subscribeStageResults(listener)` | Subscribe to stageResults changes |
|
|
295
|
+
| `subscribeStepProgress(stepKey, listener)` | Subscribe to a specific stage's progress |
|
|
296
|
+
| `on(eventName, handler)` | Subscribe to any event (`step:<key>:start\|success\|error\|skipped\|progress`, `log`) |
|
|
297
|
+
| `onStepStart/Finish/Error(handler)` | Subscribe to stage lifecycle events |
|
|
298
|
+
| `getProgress()` | Get current progress snapshot |
|
|
299
|
+
| `getLogs()` | Get all pipeline logs |
|
|
300
|
+
| `clearStageResults()` | Reset results and progress |
|
|
301
|
+
|
|
302
|
+
##### Stage parameters (PipelineStageConfig)
|
|
303
|
+
|
|
304
|
+
| Parameter | Description |
|
|
305
|
+
| --------------------------------------------- | ------------------------------------------------------------------------ |
|
|
306
|
+
| `key` | Unique stage identifier |
|
|
307
|
+
| `request({ prev, allResults, sharedData })` | Main stage function — return value becomes the stage result |
|
|
308
|
+
| `condition({ prev, allResults, sharedData })` | If returns `false`, stage is skipped with status `"skipped"` |
|
|
309
|
+
| `before({ prev, allResults, sharedData })` | Pre-processing hook — returned value replaces `prev` passed to `request` |
|
|
310
|
+
| `after({ result, allResults, sharedData })` | Post-processing hook — returned value replaces the stage result |
|
|
311
|
+
| `errorHandler({ error, key, sharedData })` | Per-stage error handler |
|
|
312
|
+
| `retryCount` | Override retry count for this stage |
|
|
313
|
+
| `timeoutMs` | Override timeout for this stage |
|
|
314
|
+
| `pauseBefore` | Delay in ms before executing `request` |
|
|
315
|
+
| `pauseAfter` | Delay in ms after executing `request` |
|
|
316
|
+
|
|
317
|
+
##### Stage execution flow
|
|
449
318
|
|
|
450
|
-
MIT
|
|
451
|
-
|
|
452
|
-
---
|
|
453
|
-
|
|
454
|
-
## Author
|
|
455
|
-
|
|
456
|
-
Danil Lisin Vladimirovich aka Macrulez
|
|
457
|
-
|
|
458
|
-
GitHub: [macrulezru](https://github.com/macrulezru)
|
|
459
|
-
|
|
460
|
-
Website: [macrulez.ru](https://macrulez.ru/)
|
|
461
|
-
|
|
462
|
-
---
|
|
463
|
-
|
|
464
|
-
## Support
|
|
465
|
-
|
|
466
|
-
Questions and bugs — via [issue](https://github.com/macrulezru/pipeline-js/issues)
|
|
467
|
-
|
|
468
|
-
## Установка
|
|
469
|
-
|
|
470
|
-
```sh
|
|
471
|
-
npm i rest-pipeline-js
|
|
472
319
|
```
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
} else {
|
|
494
|
-
console.log(res.data);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
320
|
+
condition? → false → [status: skipped] → next stage
|
|
321
|
+
↓ true
|
|
322
|
+
middleware.beforeEach
|
|
323
|
+
↓
|
|
324
|
+
pauseBefore
|
|
325
|
+
↓
|
|
326
|
+
before() hook
|
|
327
|
+
↓
|
|
328
|
+
request()
|
|
329
|
+
↓
|
|
330
|
+
after() hook
|
|
331
|
+
↓
|
|
332
|
+
pauseAfter
|
|
333
|
+
↓
|
|
334
|
+
middleware.afterEach
|
|
335
|
+
↓
|
|
336
|
+
[status: success] → next stage
|
|
337
|
+
|
|
338
|
+
On error at any point:
|
|
339
|
+
└─► stage.errorHandler (if set) → middleware.onError → [status: error] → stop
|
|
497
340
|
```
|
|
498
341
|
|
|
499
|
-
|
|
342
|
+
---
|
|
500
343
|
|
|
501
|
-
|
|
502
|
-
import { PipelineOrchestrator } from "rest-pipeline-js";
|
|
344
|
+
### Parallel stages
|
|
503
345
|
|
|
504
|
-
|
|
505
|
-
stages: [
|
|
506
|
-
{
|
|
507
|
-
key: "step1",
|
|
508
|
-
command: "/api/step1",
|
|
509
|
-
method: "POST",
|
|
510
|
-
// Можно добавить кастомные параметры шага
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
key: "step2",
|
|
514
|
-
command: "/api/step2",
|
|
515
|
-
method: "POST",
|
|
516
|
-
dependsOn: ["step1"], // step2 выполнится только после step1
|
|
517
|
-
},
|
|
518
|
-
],
|
|
519
|
-
};
|
|
346
|
+
Group stages for concurrent execution using `parallel`:
|
|
520
347
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
348
|
+
```js
|
|
349
|
+
const orchestrator = new PipelineOrchestrator({
|
|
350
|
+
config: {
|
|
351
|
+
stages: [
|
|
352
|
+
// Sequential stage
|
|
353
|
+
{ key: "auth", request: async () => getToken() },
|
|
354
|
+
|
|
355
|
+
// Parallel group — all run concurrently
|
|
356
|
+
{
|
|
357
|
+
key: "load-data",
|
|
358
|
+
parallel: [
|
|
359
|
+
{ key: "loadUsers", request: async () => fetchUsers() },
|
|
360
|
+
{ key: "loadProducts", request: async () => fetchProducts() },
|
|
361
|
+
{ key: "loadSettings", request: async () => fetchSettings() },
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
// Sequential stage after the group
|
|
366
|
+
{ key: "render", request: async ({ allResults }) => render(allResults) },
|
|
367
|
+
],
|
|
531
368
|
},
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
// Общий пул данных между шагами
|
|
535
|
-
const sharedData = { sessionId: "abc123" };
|
|
536
|
-
|
|
537
|
-
const orchestrator = new PipelineOrchestrator(
|
|
538
|
-
pipelineConfig,
|
|
539
|
-
httpConfig,
|
|
540
|
-
sharedData,
|
|
541
|
-
{ autoReset: true },
|
|
542
|
-
);
|
|
543
|
-
|
|
544
|
-
// Отслеживание прогресса
|
|
545
|
-
orchestrator.subscribeProgress((progress) => {
|
|
546
|
-
console.log(
|
|
547
|
-
"Текущий шаг:",
|
|
548
|
-
progress.currentStage,
|
|
549
|
-
"Статусы:",
|
|
550
|
-
progress.stageStatuses,
|
|
551
|
-
);
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
// Подписка на события успеха/ошибки шага
|
|
555
|
-
orchestrator.on("step:step1:success", (payload) => {
|
|
556
|
-
console.log("Step 1 завершён успешно:", payload.data);
|
|
557
|
-
});
|
|
558
|
-
orchestrator.on("step:step2:error", (payload) => {
|
|
559
|
-
console.error("Ошибка на step2:", payload.error);
|
|
560
369
|
});
|
|
561
|
-
|
|
562
|
-
// Подписка на все логи pipeline
|
|
563
|
-
orchestrator.on("log", () => {
|
|
564
|
-
console.log("Логи:", orchestrator.getLogs());
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
// Запуск pipeline с передачей параметров
|
|
568
|
-
orchestrator
|
|
569
|
-
.run({ foo: "bar" })
|
|
570
|
-
.then((result) => {
|
|
571
|
-
console.log("Pipeline завершён. Итог:", result);
|
|
572
|
-
// Доступ к результатам всех шагов:
|
|
573
|
-
console.log("Результаты шагов:", result.stageResults);
|
|
574
|
-
})
|
|
575
|
-
.catch((err) => {
|
|
576
|
-
// Глобальная обработка ошибок pipeline
|
|
577
|
-
console.error("Pipeline error:", err);
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
// Повторный запуск шага (например, после ошибки)
|
|
581
|
-
// orchestrator.rerunStep('step2');
|
|
582
370
|
```
|
|
583
371
|
|
|
584
|
-
|
|
372
|
+
- All stages in a `parallel` group run simultaneously via `Promise.all`.
|
|
373
|
+
- If **any** stage in the group fails, the pipeline stops and marks `success: false`.
|
|
374
|
+
- Each parallel stage has its own key and result in `stageResults`.
|
|
375
|
+
- `rerunStep(key)` works for stages inside parallel groups too.
|
|
585
376
|
|
|
586
377
|
---
|
|
587
378
|
|
|
588
|
-
|
|
379
|
+
### Global middleware
|
|
589
380
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
---
|
|
593
|
-
|
|
594
|
-
### createRestClient(config: HttpConfig): RestClient
|
|
595
|
-
|
|
596
|
-
Создаёт REST-клиент с поддержкой расширенных возможностей для работы с HTTP API.
|
|
597
|
-
|
|
598
|
-
#### Пример
|
|
381
|
+
Apply hooks to every stage without modifying individual stage configs:
|
|
599
382
|
|
|
600
383
|
```js
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
384
|
+
const orchestrator = new PipelineOrchestrator({
|
|
385
|
+
config: {
|
|
386
|
+
stages: [
|
|
387
|
+
/* ... */
|
|
388
|
+
],
|
|
389
|
+
middleware: {
|
|
390
|
+
beforeEach: async ({ stage, index, sharedData }) => {
|
|
391
|
+
console.log(`[${index}] Starting: ${stage.key}`);
|
|
392
|
+
sharedData.startedAt = Date.now();
|
|
393
|
+
},
|
|
394
|
+
afterEach: async ({ stage, index, result, sharedData }) => {
|
|
395
|
+
const ms = Date.now() - sharedData.startedAt;
|
|
396
|
+
console.log(`[${index}] Done: ${stage.key} in ${ms}ms`, result.data);
|
|
397
|
+
},
|
|
398
|
+
onError: async ({ stage, error, sharedData }) => {
|
|
399
|
+
await reportError({ stage: stage.key, error, context: sharedData });
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
},
|
|
609
403
|
});
|
|
610
|
-
|
|
611
|
-
async function getUser(id) {
|
|
612
|
-
const res = await client.request(`/users/${id}`);
|
|
613
|
-
if (res.error) {
|
|
614
|
-
console.error("Ошибка:", res.error);
|
|
615
|
-
} else {
|
|
616
|
-
console.log("Пользователь:", res.data);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
404
|
```
|
|
620
405
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
### RequestExecutor
|
|
624
|
-
|
|
625
|
-
Обёртка для выполнения REST-запросов с поддержкой автоматического retry и таймаута.
|
|
626
|
-
|
|
627
|
-
#### Пример
|
|
628
|
-
|
|
629
|
-
```js
|
|
630
|
-
import { RequestExecutor } from "rest-pipeline-js";
|
|
631
|
-
|
|
632
|
-
const executor = new RequestExecutor({ baseURL: "https://api.example.com" });
|
|
633
|
-
|
|
634
|
-
async function fetchData() {
|
|
635
|
-
try {
|
|
636
|
-
const res = await executor.execute("/data", { method: "GET" }, 3, 3000);
|
|
637
|
-
if (res.error) {
|
|
638
|
-
console.error("Ошибка:", res.error);
|
|
639
|
-
} else {
|
|
640
|
-
console.log("Данные:", res.data);
|
|
641
|
-
}
|
|
642
|
-
} catch (err) {
|
|
643
|
-
console.error("Критическая ошибка:", err);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
```
|
|
406
|
+
Middleware runs in addition to (not instead of) per-stage `errorHandler`.
|
|
647
407
|
|
|
648
408
|
---
|
|
649
409
|
|
|
650
|
-
###
|
|
651
|
-
|
|
652
|
-
Основной класс для построения и управления конвейером (pipeline) из последовательных шагов.
|
|
653
|
-
|
|
654
|
-
#### Основные методы и параметры
|
|
655
|
-
|
|
656
|
-
- **constructor(pipelineConfig, httpConfig, sharedData?, options?)** — создание экземпляра:
|
|
657
|
-
- `pipelineConfig` — массив шагов (stages), их параметры, условия, обработчики
|
|
658
|
-
- `httpConfig` — настройки HTTP клиента
|
|
659
|
-
- `sharedData` — общий пул данных между шагами
|
|
660
|
-
- `options.autoReset` — сбрасывать ли состояние после завершения
|
|
661
|
-
|
|
662
|
-
- **run(onStepPause?, externalSignal?)** — запуск конвейера
|
|
663
|
-
- `onStepPause(stepIndex, stepResult, stageResults)` — callback для паузы/подтверждения/модификации результата между шагами (можно реализовать задержку, диалог, логику)
|
|
664
|
-
- `externalSignal` — внешний AbortSignal для отмены
|
|
665
|
-
- Возвращает: `{ stageResults, success }`
|
|
666
|
-
|
|
667
|
-
- **rerunStep(stepKey, options?)** — повторно выполнить один шаг
|
|
668
|
-
- `onStepPause` и `externalSignal` аналогично run
|
|
669
|
-
- Возвращает результат шага
|
|
670
|
-
|
|
671
|
-
- **subscribeProgress(listener)** — подписка на прогресс выполнения (listener получает PipelineProgress)
|
|
672
|
-
- **subscribeStageResults(listener)** — подписка на изменения результатов всех шагов
|
|
673
|
-
- **subscribeStepProgress(stepKey, listener)** — подписка на прогресс конкретного шага
|
|
674
|
-
- **on(eventName, handler)** — универсальная подписка на события:
|
|
675
|
-
- `step:<stepKey>:start|success|error|progress` — события по шагам
|
|
676
|
-
- `log` — новые логи
|
|
677
|
-
- любые кастомные события
|
|
678
|
-
- **onStepStart/Finish/Error(handler)** — подписка на начало/успех/ошибку шага (PipelineStepEvent)
|
|
679
|
-
- **getProgress()** — получить текущий прогресс (snapshot)
|
|
680
|
-
- **getProgressRef()** — получить ссылку на объект прогресса (для реактивности)
|
|
681
|
-
- **getLogs()** — получить массив логов pipeline
|
|
682
|
-
- **abort()** — отменить выполнение пайплайна
|
|
683
|
-
- **isAborted()** — проверить, был ли пайплайн отменён
|
|
684
|
-
|
|
685
|
-
#### Важные параметры шага (stage):
|
|
686
|
-
|
|
687
|
-
- `key` — уникальный ключ шага
|
|
688
|
-
- `command` — команда/endpoint для запроса
|
|
689
|
-
- `method` — HTTP-метод
|
|
690
|
-
- `dependsOn` — массив ключей шагов, от которых зависит этот шаг
|
|
691
|
-
- `condition({ prev, allResults, sharedData })` — функция-условие для выполнения шага
|
|
692
|
-
- `before({ prev, allResults, sharedData })` — before-хук (вызывается перед запросом; может изменить входные данные)
|
|
693
|
-
- `request({ prev, allResults, sharedData })` — кастомная функция запроса (альтернатива command). Если before возвращает значение, оно будет передано в request как prev.
|
|
694
|
-
- `after({ result, allResults, sharedData })` — post-processing хук (вызывается после запроса, до перехода к следующему этапу; может модифицировать результат)
|
|
695
|
-
- `pauseBefore` — опциональная пауза (в миллисекундах) перед выполнением запроса
|
|
696
|
-
- `pauseAfter` — опциональная пауза (в миллисекундах) после выполнения запроса
|
|
697
|
-
- `retryCount`, `timeoutMs` — индивидуальные настройки повтора и таймаута
|
|
698
|
-
- `errorHandler({ error, key, sharedData })` — обработчик ошибок шага
|
|
699
|
-
|
|
700
|
-
##### Диаграмма выполнения шага
|
|
701
|
-
|
|
702
|
-
```
|
|
703
|
-
┌───────────────┐
|
|
704
|
-
│ before │
|
|
705
|
-
│ (опционально) │
|
|
706
|
-
└─────┬─────────┘
|
|
707
|
-
│
|
|
708
|
-
▼
|
|
709
|
-
┌────────────┐
|
|
710
|
-
│ request │
|
|
711
|
-
└─────┬──────┘
|
|
712
|
-
│
|
|
713
|
-
▼
|
|
714
|
-
┌───────────────┐
|
|
715
|
-
│ after │
|
|
716
|
-
│ (опционально) │
|
|
717
|
-
└─────┬─────────┘
|
|
718
|
-
│
|
|
719
|
-
▼
|
|
720
|
-
┌────────────┐
|
|
721
|
-
│ следующий │
|
|
722
|
-
│ шаг │
|
|
723
|
-
└────────────┘
|
|
724
|
-
|
|
725
|
-
Если возникает ошибка на любом этапе:
|
|
726
|
-
└─► errorHandler (если определён) → результат с ошибкой
|
|
727
|
-
```
|
|
410
|
+
### Pause / Resume
|
|
728
411
|
|
|
729
|
-
|
|
412
|
+
Pause the pipeline after a stage and resume later:
|
|
730
413
|
|
|
731
414
|
```js
|
|
732
|
-
|
|
415
|
+
const orchestrator = new PipelineOrchestrator({ config });
|
|
733
416
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
{ key: "first", command: "/api/first", method: "POST" },
|
|
737
|
-
{
|
|
738
|
-
key: "second",
|
|
739
|
-
command: "/api/second",
|
|
740
|
-
method: "POST",
|
|
741
|
-
dependsOn: ["first"],
|
|
742
|
-
},
|
|
743
|
-
],
|
|
744
|
-
};
|
|
745
|
-
const httpConfig = { baseURL: "https://api.example.com" };
|
|
746
|
-
const sharedData = { sessionId: "abc" };
|
|
747
|
-
const orchestrator = new PipelineOrchestrator(
|
|
748
|
-
pipelineConfig,
|
|
749
|
-
httpConfig,
|
|
750
|
-
sharedData,
|
|
751
|
-
);
|
|
417
|
+
// Pause after step1 completes
|
|
418
|
+
orchestrator.on("step:step1:success", () => orchestrator.pause());
|
|
752
419
|
|
|
753
|
-
orchestrator.
|
|
754
|
-
console.log("Прогресс:", progress);
|
|
755
|
-
});
|
|
420
|
+
const runPromise = orchestrator.run();
|
|
756
421
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
422
|
+
// At some point later (e.g. after user confirmation):
|
|
423
|
+
await showConfirmDialog();
|
|
424
|
+
orchestrator.resume();
|
|
760
425
|
|
|
761
|
-
|
|
762
|
-
orchestrator
|
|
763
|
-
.run(async (i, result) => {
|
|
764
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
765
|
-
return result;
|
|
766
|
-
})
|
|
767
|
-
.then((result) => console.log("Pipeline завершён:", result))
|
|
768
|
-
.catch((err) => console.error("Ошибка pipeline:", err));
|
|
426
|
+
await runPromise;
|
|
769
427
|
```
|
|
770
428
|
|
|
771
|
-
|
|
429
|
+
- `pause()` — pipeline waits after the current stage finishes (including events).
|
|
430
|
+
- `resume()` — continues from the next stage.
|
|
431
|
+
- `abort()` while paused unblocks the pipeline and terminates it.
|
|
772
432
|
|
|
773
|
-
|
|
433
|
+
---
|
|
774
434
|
|
|
775
|
-
|
|
435
|
+
### Export / Import state
|
|
776
436
|
|
|
777
|
-
|
|
437
|
+
Save and restore the pipeline state across page reloads or sessions:
|
|
778
438
|
|
|
779
439
|
```js
|
|
780
|
-
|
|
440
|
+
const orchestrator = new PipelineOrchestrator({ config });
|
|
441
|
+
await orchestrator.run();
|
|
781
442
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
});
|
|
786
|
-
tracker.updateStage(1, "success");
|
|
787
|
-
console.log(tracker.getProgress());
|
|
788
|
-
```
|
|
789
|
-
|
|
790
|
-
---
|
|
443
|
+
// Save state
|
|
444
|
+
const snapshot = orchestrator.exportState();
|
|
445
|
+
localStorage.setItem("pipelineState", JSON.stringify(snapshot));
|
|
791
446
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
#### Пример
|
|
797
|
-
|
|
798
|
-
```js
|
|
799
|
-
import { ErrorHandler } from "rest-pipeline-js";
|
|
447
|
+
// Later — restore and inspect without re-running
|
|
448
|
+
const saved = JSON.parse(localStorage.getItem("pipelineState"));
|
|
449
|
+
const orchestrator2 = new PipelineOrchestrator({ config });
|
|
450
|
+
orchestrator2.importState(saved);
|
|
800
451
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
console.log(error); // { type: 'unknown', error: [Error], stageKey: 'step1' }
|
|
452
|
+
console.log(orchestrator2.getProgress()); // restored progress
|
|
453
|
+
console.log(orchestrator2.getLogs()); // restored logs (timestamps as Date objects)
|
|
804
454
|
```
|
|
805
455
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
- **HttpConfig** — конфигурация REST клиента (baseURL, timeout, headers, retry, cache, rateLimit, metrics)
|
|
809
|
-
- **ApiError** — описание ошибки API
|
|
810
|
-
- **ApiResponse<T>** — ответ API (данные, ошибка, статус)
|
|
811
|
-
- **PipelineConfig, PipelineResult, PipelineStepEvent, PipelineStepStatus** — описание pipeline и стадий
|
|
456
|
+
`exportState()` returns `{ stageResults, logs }` — a plain JSON-serializable object. Timestamps in logs are stored as ISO strings and restored as `Date` objects on `importState`.
|
|
812
457
|
|
|
813
458
|
---
|
|
814
459
|
|
|
815
|
-
###
|
|
460
|
+
### Vue integration
|
|
816
461
|
|
|
817
|
-
####
|
|
462
|
+
#### Example: use in Vue component
|
|
818
463
|
|
|
819
|
-
```
|
|
464
|
+
```vue
|
|
820
465
|
<script setup>
|
|
821
|
-
import {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
466
|
+
import {
|
|
467
|
+
PipelineOrchestrator,
|
|
468
|
+
usePipelineProgressVue,
|
|
469
|
+
usePipelineRunVue,
|
|
470
|
+
} from "rest-pipeline-js/vue";
|
|
471
|
+
|
|
472
|
+
const orchestrator = new PipelineOrchestrator({
|
|
473
|
+
config: {
|
|
474
|
+
stages: [
|
|
475
|
+
/* ... */
|
|
476
|
+
],
|
|
477
|
+
},
|
|
478
|
+
});
|
|
828
479
|
const progress = usePipelineProgressVue(orchestrator);
|
|
829
|
-
const { run, running, result, error } =
|
|
480
|
+
const { run, running, result, error, abort, pause, resume, rerunStep } =
|
|
481
|
+
usePipelineRunVue(orchestrator);
|
|
830
482
|
</script>
|
|
831
483
|
|
|
832
484
|
<template>
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
485
|
+
<div>
|
|
486
|
+
<div>Current stage: {{ progress.currentStage }}</div>
|
|
487
|
+
<button @click="run()" :disabled="running">Start</button>
|
|
488
|
+
<button @click="abort()" :disabled="!running">Abort</button>
|
|
489
|
+
<button @click="pause()">Pause</button>
|
|
490
|
+
<button @click="resume()">Resume</button>
|
|
491
|
+
<div v-if="result">Done: {{ result }}</div>
|
|
492
|
+
<div v-if="error">Error: {{ error.message }}</div>
|
|
493
|
+
</div>
|
|
839
494
|
</template>
|
|
840
495
|
```
|
|
841
496
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
Экспортируются composition-функции для интеграции rest-pipeline-js с Vue 3 (импортировать из `rest-pipeline-js/vue`):
|
|
497
|
+
Composition functions (import from `rest-pipeline-js/vue`):
|
|
845
498
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
499
|
+
| Function | Returns | Description |
|
|
500
|
+
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------- |
|
|
501
|
+
| `usePipelineProgressVue(orchestrator)` | `Ref<PipelineProgress>` | Reactive progress |
|
|
502
|
+
| `usePipelineRunVue(orchestrator)` | `{ run, running, result, error, stageResults, abort, pause, resume, rerunStep, clearStageResults }` | Run pipeline and get reactive state |
|
|
503
|
+
| `usePipelineStepEventVue(orchestrator, stepKey, eventType)` | `Ref<any>` | Last payload for a specific step event |
|
|
504
|
+
| `usePipelineLogsVue(orchestrator)` | `Ref<log[]>` | Reactive logs |
|
|
505
|
+
| `useRerunPipelineStepVue(orchestrator)` | `function` | Bound `rerunStep` |
|
|
506
|
+
| `useRestClientVue(config)` | `ComputedRef<RestClient>` | Reactive REST client |
|
|
852
507
|
|
|
853
508
|
---
|
|
854
509
|
|
|
855
|
-
###
|
|
510
|
+
### React integration
|
|
856
511
|
|
|
857
|
-
####
|
|
512
|
+
#### Example: use in React component
|
|
858
513
|
|
|
859
514
|
```jsx
|
|
860
|
-
import
|
|
515
|
+
import { useRef } from "react";
|
|
861
516
|
import {
|
|
862
517
|
PipelineOrchestrator,
|
|
863
518
|
usePipelineProgressReact,
|
|
864
519
|
usePipelineRunReact,
|
|
865
520
|
} from "rest-pipeline-js/react";
|
|
866
521
|
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
522
|
+
const orchestrator = new PipelineOrchestrator({
|
|
523
|
+
config: {
|
|
524
|
+
stages: [
|
|
525
|
+
/* ... */
|
|
526
|
+
],
|
|
527
|
+
},
|
|
528
|
+
});
|
|
874
529
|
|
|
875
530
|
export function PipelineComponent() {
|
|
876
531
|
const progress = usePipelineProgressReact(orchestrator);
|
|
877
|
-
const [run, { running, result, error }] =
|
|
532
|
+
const [run, { running, result, error, abort, pause, resume, rerunStep }] =
|
|
533
|
+
usePipelineRunReact(orchestrator);
|
|
878
534
|
|
|
879
535
|
return (
|
|
880
536
|
<div>
|
|
881
|
-
<div
|
|
537
|
+
<div>Current stage: {progress.currentStage}</div>
|
|
882
538
|
<button onClick={() => run()} disabled={running}>
|
|
883
|
-
|
|
539
|
+
Start
|
|
884
540
|
</button>
|
|
885
|
-
|
|
886
|
-
|
|
541
|
+
<button onClick={() => abort()} disabled={!running}>
|
|
542
|
+
Abort
|
|
543
|
+
</button>
|
|
544
|
+
<button onClick={() => pause()}>Pause</button>
|
|
545
|
+
<button onClick={() => resume()}>Resume</button>
|
|
546
|
+
{result && <div>Done: {JSON.stringify(result)}</div>}
|
|
547
|
+
{error && <div>Error: {error.message}</div>}
|
|
887
548
|
</div>
|
|
888
549
|
);
|
|
889
550
|
}
|
|
890
551
|
```
|
|
891
552
|
|
|
892
|
-
|
|
553
|
+
Hooks (import from `rest-pipeline-js/react`):
|
|
893
554
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
555
|
+
| Hook | Returns | Description |
|
|
556
|
+
| ------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------- |
|
|
557
|
+
| `usePipelineProgressReact(orchestrator)` | `PipelineProgress` | Reactive progress |
|
|
558
|
+
| `usePipelineRunReact(orchestrator)` | `[run, { running, result, error, stageResults, abort, pause, resume, rerunStep }]` | Run pipeline and get state |
|
|
559
|
+
| `usePipelineStepEventReact(orchestrator, stepKey, eventType)` | `any` | Last payload for a specific step event |
|
|
560
|
+
| `usePipelineLogsReact(orchestrator)` | `log[]` | Reactive logs |
|
|
561
|
+
| `useRerunPipelineStepReact(orchestrator)` | `function` | Bound `rerunStep` |
|
|
562
|
+
| `useRestClientReact(config)` | `RestClient` | Memoized REST client |
|
|
902
563
|
|
|
903
564
|
---
|
|
904
565
|
|
|
905
|
-
##
|
|
566
|
+
## Entry points
|
|
567
|
+
|
|
568
|
+
| Entry point | Use for | Contents |
|
|
569
|
+
| ------------------------ | -------------- | --------------------------------------------------------------------------- |
|
|
570
|
+
| `rest-pipeline-js` | Core only | `PipelineOrchestrator`, `createRestClient`, types, utilities. No Vue/React. |
|
|
571
|
+
| `rest-pipeline-js/vue` | Vue projects | Core + Vue composition functions |
|
|
572
|
+
| `rest-pipeline-js/react` | React projects | Core + React hooks |
|
|
906
573
|
|
|
907
|
-
|
|
574
|
+
```js
|
|
575
|
+
// Core only
|
|
576
|
+
import { createRestClient, PipelineOrchestrator } from "rest-pipeline-js";
|
|
908
577
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
578
|
+
// Vue
|
|
579
|
+
import { PipelineOrchestrator, usePipelineRunVue } from "rest-pipeline-js/vue";
|
|
580
|
+
|
|
581
|
+
// React
|
|
582
|
+
import {
|
|
583
|
+
PipelineOrchestrator,
|
|
584
|
+
usePipelineRunReact,
|
|
585
|
+
} from "rest-pipeline-js/react";
|
|
586
|
+
```
|
|
914
587
|
|
|
915
|
-
|
|
588
|
+
`sideEffects: false` — unused entry points are tree-shaken. `react`/`react-dom` are `peerDependencies`.
|
|
916
589
|
|
|
917
590
|
---
|
|
918
591
|
|
|
919
|
-
##
|
|
592
|
+
## Requirements
|
|
920
593
|
|
|
921
594
|
- Node.js >= 14.0.0
|
|
922
|
-
-
|
|
595
|
+
- Modern browser with ES2019+ support
|
|
923
596
|
|
|
924
597
|
---
|
|
925
598
|
|
|
926
|
-
##
|
|
599
|
+
## Development
|
|
927
600
|
|
|
928
601
|
```bash
|
|
929
|
-
# Клонировать репозиторий
|
|
930
602
|
git clone https://github.com/macrulezru/pipeline-js.git
|
|
931
603
|
cd pipeline-js
|
|
932
604
|
npm install
|
|
933
605
|
npm test
|
|
934
|
-
npm run lint
|
|
935
606
|
```
|
|
936
607
|
|
|
937
608
|
---
|
|
938
609
|
|
|
939
|
-
##
|
|
610
|
+
## License
|
|
940
611
|
|
|
941
612
|
MIT
|
|
942
613
|
|
|
943
614
|
---
|
|
944
615
|
|
|
945
|
-
##
|
|
946
|
-
|
|
947
|
-
Данил Лисин Владимирович aka Macrulez
|
|
948
|
-
|
|
949
|
-
GitHub: [macrulezru](https://github.com/macrulezru)
|
|
950
|
-
|
|
951
|
-
Сайт: [macrulez.ru](https://macrulez.ru/)
|
|
616
|
+
## Author
|
|
952
617
|
|
|
953
|
-
|
|
618
|
+
Danil Lisin Vladimirovich aka Macrulez
|
|
954
619
|
|
|
955
|
-
|
|
620
|
+
GitHub: [macrulezru](https://github.com/macrulezru) · Website: [macrulez.ru](https://macrulez.ru/)
|
|
956
621
|
|
|
957
|
-
|
|
622
|
+
Questions and bugs — [issues](https://github.com/macrulezru/pipeline-js/issues)
|