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