rest-pipeline-js 1.4.0 → 1.4.1
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/LICENSE +21 -0
- package/package.json +103 -74
- package/.claude/settings.local.json +0 -11
- package/CHANGELOG.md +0 -150
- package/demo/App.vue +0 -122
- package/demo/index.html +0 -22
- package/demo/main.js +0 -5
- package/demo/style.css +0 -857
- package/demo/views/CacheDemo.vue +0 -599
- package/demo/views/FlightDemo.vue +0 -481
- package/demo/views/ParallelDemo.vue +0 -546
- package/demo/views/RetryDemo.vue +0 -506
- package/eslint.config.js +0 -40
- package/react-shim.d.ts +0 -1
- package/src/cache.ts +0 -93
- package/src/circuit-breaker.ts +0 -90
- package/src/error-handler.ts +0 -10
- package/src/index.ts +0 -11
- package/src/pipeline-builder.ts +0 -179
- package/src/pipeline-orchestrator.ts +0 -1397
- package/src/pipeline-validator.ts +0 -151
- package/src/progress-tracker.ts +0 -60
- package/src/rate-limiter.ts +0 -76
- package/src/react.ts +0 -12
- package/src/request-executor.ts +0 -168
- package/src/rest-client.ts +0 -570
- package/src/tsconfig.json +0 -10
- package/src/types.ts +0 -720
- package/src/usePipelineProgress-react.ts +0 -21
- package/src/usePipelineProgress-vue.ts +0 -17
- package/src/usePipelineRun-react.ts +0 -52
- package/src/usePipelineRun-vue.ts +0 -63
- package/src/usePipelineStageResult-react.ts +0 -32
- package/src/usePipelineStageResult-vue.ts +0 -34
- package/src/usePipelineStepEvents-react.ts +0 -49
- package/src/usePipelineStepEvents-vue.ts +0 -48
- package/src/useRestClient-react.ts +0 -12
- package/src/useRestClient-vue.ts +0 -12
- package/src/vue-demo/demo.css +0 -768
- package/src/vue-demo/demo.vue +0 -621
- package/src/vue-demo/index.html +0 -21
- package/src/vue-demo/main.js +0 -4
- package/src/vue.ts +0 -12
- package/tests/error-handler.test.ts +0 -10
- package/tests/pipeline-builder.test.ts +0 -112
- package/tests/pipeline-orchestrator.test.ts +0 -1461
- package/tests/progress-tracker.test.ts +0 -13
- package/tests/react-hooks.test.ts +0 -61
- package/tests/request-executor.test.ts +0 -39
- package/tests/rest-client.test.ts +0 -548
- package/tests/types.test.ts +0 -105
- package/tests/vue-hooks.test.ts +0 -57
- package/tsconfig.cjs.json +0 -17
- package/tsconfig.esm.json +0 -16
- package/tsconfig.json +0 -17
- package/vite.config.js +0 -25
- package/vitest.config.ts +0 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Danil Lisin Vladimirovich (macrulez)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,74 +1,103 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "rest-pipeline-js",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"types": "./dist/esm/
|
|
21
|
-
"import": "./dist/esm/
|
|
22
|
-
"require": "./dist/cjs/
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"react": "
|
|
68
|
-
"
|
|
69
|
-
},
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "rest-pipeline-js",
|
|
3
|
+
"version": "1.4.1",
|
|
4
|
+
"description": "Orchestrate REST API calls in sequential and parallel pipelines with retry, caching, rate limiting and SSE streaming. Vanilla JS / Vue 3 / React.",
|
|
5
|
+
"author": "macrulez <macrulezru@gmail.com> (https://macrulez.ru/en)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/macrulezru/pipeline-js#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/macrulezru/pipeline-js.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/macrulezru/pipeline-js/issues"
|
|
14
|
+
},
|
|
15
|
+
"main": "dist/cjs/index.js",
|
|
16
|
+
"module": "dist/esm/index.js",
|
|
17
|
+
"types": "dist/esm/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/esm/index.d.ts",
|
|
21
|
+
"import": "./dist/esm/index.js",
|
|
22
|
+
"require": "./dist/cjs/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./vue": {
|
|
25
|
+
"types": "./dist/esm/vue.d.ts",
|
|
26
|
+
"import": "./dist/esm/vue.js",
|
|
27
|
+
"require": "./dist/cjs/vue.js"
|
|
28
|
+
},
|
|
29
|
+
"./react": {
|
|
30
|
+
"types": "./dist/esm/react.d.ts",
|
|
31
|
+
"import": "./dist/esm/react.js",
|
|
32
|
+
"require": "./dist/cjs/react.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"sideEffects": false,
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"prepublishOnly": "npm run build",
|
|
46
|
+
"build": "tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"demo:vue": "npm run start-demo-vite",
|
|
49
|
+
"start-demo-vite": "vite --config vite.config.js"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"rest",
|
|
53
|
+
"api",
|
|
54
|
+
"pipeline",
|
|
55
|
+
"orchestration",
|
|
56
|
+
"retry",
|
|
57
|
+
"cache",
|
|
58
|
+
"rate-limit",
|
|
59
|
+
"sse",
|
|
60
|
+
"axios",
|
|
61
|
+
"vue",
|
|
62
|
+
"react",
|
|
63
|
+
"typescript"
|
|
64
|
+
],
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"react": ">=18.0.0",
|
|
67
|
+
"react-dom": ">=18.0.0",
|
|
68
|
+
"vue": ">=3.3.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"react": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"react-dom": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"vue": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"axios": "^1.13.2"
|
|
83
|
+
},
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@testing-library/react": "^16.3.1",
|
|
86
|
+
"@types/react": "^19.2.8",
|
|
87
|
+
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
|
88
|
+
"@typescript-eslint/parser": "^8.53.0",
|
|
89
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
90
|
+
"eslint": "^9.39.2",
|
|
91
|
+
"eslint-plugin-vue": "^10.6.2",
|
|
92
|
+
"jsdom": "^27.4.0",
|
|
93
|
+
"prismjs": "^1.30.0",
|
|
94
|
+
"react": "^19.2.3",
|
|
95
|
+
"react-dom": "^19.2.3",
|
|
96
|
+
"scheduler": "^0.27.0",
|
|
97
|
+
"typescript": "^5.9.3",
|
|
98
|
+
"vite": "^7.3.1",
|
|
99
|
+
"vitest": "^4.0.17",
|
|
100
|
+
"vue": "^3.5.26",
|
|
101
|
+
"vue-eslint-parser": "^10.2.0"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(grep -n \"stepIndex,$\\\\|stepIndex: i,\\\\|emitStepStart\\(\\\\|emitStepFinish\\(\\\\|emitStepError\\(\\\\|emitStepSkipped\\(\\\\|stageResults: { \\\\.\\\\.\\\\.this\\\\.stageResults }\" src/pipeline-orchestrator.ts)",
|
|
5
|
-
"Bash(cat > *)"
|
|
6
|
-
],
|
|
7
|
-
"additionalDirectories": [
|
|
8
|
-
"\\tmp"
|
|
9
|
-
]
|
|
10
|
-
}
|
|
11
|
-
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## [Unreleased]
|
|
4
|
-
|
|
5
|
-
### Fixed
|
|
6
|
-
|
|
7
|
-
- **Flaky test in `tests/rest-client.test.ts`** ("при повторном 401 после onUnauthorized — не попадает в бесконечный цикл") — the mock error set `err.isAxiosError = true` *after* `Object.setPrototypeOf(err, axios.AxiosError.prototype)`. `AxiosError.prototype.isAxiosError` is defined as non-writable (`Object.defineProperty(..., { value: true })`), so that assignment threw a `TypeError` in strict mode, which masked the actual 401-retry logic being exercised. Fixed by assigning `isAxiosError` before swapping the prototype, matching the (correct) pattern already used by the other axios-error mocks in the same file. No production code changed.
|
|
8
|
-
|
|
9
|
-
### Added
|
|
10
|
-
|
|
11
|
-
#### Pipeline Orchestrator
|
|
12
|
-
|
|
13
|
-
- **`signal` in stage hooks** — `request`, `condition`, `before`, `after`, `errorHandler`, and `StreamStageConfig.stream` now receive the pipeline's `AbortSignal` in their params object. Pass it down to `fetch`/`axios`/etc. so `abort()` actually cancels custom async work inside stage functions, not just the orchestrator's own bookkeeping.
|
|
14
|
-
- **`recoverStep(data)`** (from `types.ts`, re-exported from the root entry point) — `errorHandler` can return `recoverStep(data)` to recover a failed stage back into a successful one (`status: "success"`, `data`), running the same commit path as a normal success (metrics, `persistAdapter.save()`, `middleware.afterEach`, `step:success` event) instead of stopping/continuing-as-error. Returning anything else keeps the previous behavior (error, transformed via `toApiError`).
|
|
15
|
-
|
|
16
|
-
#### RestClient
|
|
17
|
-
|
|
18
|
-
- **No `axios.create()` when `adapter` is set** — `createRestClient()` no longer constructs the built-in axios instance if a custom `HttpAdapter` is provided, avoiding unnecessary work in edge/serverless environments that only use the adapter.
|
|
19
|
-
|
|
20
|
-
### Changed
|
|
21
|
-
|
|
22
|
-
- Internal: `PipelineOrchestrator.executeStage()` success/error commit logic was extracted into `_commitStepSuccess()` / `_commitStepError()` so the new `errorHandler` recovery path and the normal success path share identical metrics/persist/middleware/event behavior.
|
|
23
|
-
|
|
24
|
-
#### Pipeline Orchestrator
|
|
25
|
-
|
|
26
|
-
- **`ParallelStageGroup.concurrency`** — caps how many stages of a parallel group run at once instead of always starting all of them via `Promise.all`. Useful for fan-out over many items (e.g. paginated fetches) without opening hundreds of requests at the same time. Results are still returned/stored in the same shape and order as an unlimited group. Supported by the `pipe()` builder via `.parallel(stages, { concurrency })`.
|
|
27
|
-
|
|
28
|
-
#### RestClient
|
|
29
|
-
|
|
30
|
-
- **`AuthProvider.tokenTtlMs`** — caches `getToken()`'s result for the given duration instead of calling it before every request. The cache is invalidated automatically on a `401` (before `onUnauthorized` runs), so the retried request always fetches a fresh token. Without `tokenTtlMs`, behavior is unchanged (`getToken()` called every request).
|
|
31
|
-
- **`invalidateCache(matcher)`** — new method on the client returned by `createRestClient()`. Removes only the response-cache entries whose URL matches `matcher` (substring, `RegExp`, or `(info: { method, url }) => boolean`) instead of clearing the whole cache like `clearCache()`. Returns the number of entries removed.
|
|
32
|
-
- `TtlCache` gained `keys()` and `deleteWhere(predicate)` to support the above.
|
|
33
|
-
|
|
34
|
-
### Added (continued)
|
|
35
|
-
|
|
36
|
-
#### RestClient — Circuit breaker
|
|
37
|
-
|
|
38
|
-
- **`HttpConfig.circuitBreaker`** (new `CircuitBreakerConfig`: `{ failureThreshold, openMs, successThreshold?, isFailure? }`) — after `failureThreshold` consecutive failures the client rejects requests immediately with `CircuitOpenError` (`code: "CIRCUIT_OPEN"`) for `openMs`, without making a network call. After `openMs` it probes with real requests in a `half-open` state: success (×`successThreshold`, default 1) closes the circuit, failure re-opens it. `isFailure(error)` can exclude certain errors (e.g. 4xx) from counting as failures. Cancelled/aborted requests never count as failures. New module `src/circuit-breaker.ts` exports `CircuitBreaker`, `CircuitOpenError`, and the `CircuitBreakerState` type.
|
|
39
|
-
- **`client.getCircuitBreakerState()`** — returns `"closed" | "open" | "half-open"`, or `null` if `circuitBreaker` isn't configured.
|
|
40
|
-
- Not set by default — without `circuitBreaker`, behavior is unchanged.
|
|
41
|
-
|
|
42
|
-
#### Pipeline Orchestrator — run correlation
|
|
43
|
-
|
|
44
|
-
- **`runId`** — every `run()` call generates a fresh ID (via `crypto.randomUUID()`, falling back to a timestamp-based string), shared by `PipelineMetrics.onPipelineStart/onPipelineEnd/onStepDuration`, every `PipelineStepEvent` (`.runId`), and every entry returned by `getLogs()`/`exportState()`. All attempts within one `run()` (including `pipelineRetry` retries) share the same `runId`. `rerunStep()` generates its own separate `runId`. New `orchestrator.getRunId()` reads the current/last one. `PipelineMetrics`' three callback `info` objects and `PipelineStepEvent` gained a `runId` field (required on the former, optional on the latter for backward compatibility).
|
|
45
|
-
|
|
46
|
-
#### DX utilities — typed `pipe()` builder
|
|
47
|
-
|
|
48
|
-
- **`PipelineBuilder<TPrev>`** — the fluent builder is now generic: `.step()` infers and threads the previous step's output type into the next step's `prev`, so TypeScript catches type mismatches across a chain and provides autocomplete. The first step's `prev` is typed `undefined`, matching actual runtime behavior. `.parallel()` / `.subPipeline()` / `.stream()` intentionally don't change the threaded type, since the orchestrator's `prev` for the next step always comes from the last regular `.step()`, never from a parallel group/sub-pipeline/stream. Purely a type-level addition — `PipelineBuilder` still mutates the same instance internally, so existing non-chained usage (calling `.step()` without reassigning the result) keeps working unchanged.
|
|
49
|
-
- `ParallelStageGroup.concurrency` is also exposed through `pipe().parallel(stages, { concurrency })`.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## [1.3.7] - 2026-04-04
|
|
54
|
-
|
|
55
|
-
### Added
|
|
56
|
-
|
|
57
|
-
#### Pipeline Orchestrator
|
|
58
|
-
|
|
59
|
-
- **Pipeline metrics** — `PipelineConfig.metrics` with three callbacks:
|
|
60
|
-
- `onPipelineStart({ timestamp })` — fires at the beginning of `run()`
|
|
61
|
-
- `onPipelineEnd({ durationMs, success, stageResults })` — fires when `run()` completes
|
|
62
|
-
- `onStepDuration({ stepKey, durationMs, status })` — fires after every executed step
|
|
63
|
-
- **Plugin system** — `options.plugins` accepts an array of `PipelinePlugin` objects. Each plugin receives the orchestrator instance in `install(orchestrator)` and can subscribe to events, add middleware hooks, etc. Returning a function from `install()` registers it as a cleanup callback.
|
|
64
|
-
- **`destroy()`** — new public method that invokes cleanup functions from all installed plugins.
|
|
65
|
-
- **Persist adapter** — `options.persistAdapter` accepts a `PipelineStateAdapter` object with `save` / `load` methods. When set: state is automatically loaded at the start of `run()` (via `importState`) and saved after each successfully completed step.
|
|
66
|
-
- **Stream stages** — new `StreamStageConfig` element type for `PipelineItem`. The `stream` function returns an `AsyncIterable<T>`; the orchestrator iterates it and collects chunks into an array (the stage result). The optional `onChunk(chunk, sharedData)` callback fires for each chunk in real time. Stream stages honour `abort()`, `continueOnError`, and emit standard step events.
|
|
67
|
-
- **Generic step keys** — `PipelineOrchestrator<TKeys extends string = string>` now accepts a generic type parameter for typed auto-complete in `on()`, `rerunStep()`, and `subscribeStepProgress()`.
|
|
68
|
-
|
|
69
|
-
#### DX utilities
|
|
70
|
-
|
|
71
|
-
- **`createPipeline(stages, options?)`** — factory function that creates a `PipelineOrchestrator` without the nested `{ config: { stages } }` boilerplate.
|
|
72
|
-
- **`pipe()`** — fluent builder API. Methods: `.step()`, `.parallel()`, `.subPipeline()`, `.stream()`, `.build(options?)`, `.toConfig(options?)`.
|
|
73
|
-
- **`validatePipelineConfig(config, context?)`** — validates a `PipelineConfig` before runtime. Checks for duplicate keys, empty keys, empty `stages` array, invalid field types, and recursively validates nested sub-pipelines. Returns `{ valid: boolean; errors: string[] }`.
|
|
74
|
-
- **`getStageResults()`** — synchronous snapshot of all stage results (no subscription needed).
|
|
75
|
-
|
|
76
|
-
#### Vue / React hooks
|
|
77
|
-
|
|
78
|
-
- **`usePipelineStageResultVue(orchestrator, stepKey)`** — reactive `Ref<PipelineStepResult | null>` for a single step.
|
|
79
|
-
- **`usePipelineStageResultReact(orchestrator, stepKey)`** — state hook for a single step, updates on every `stageResults` change.
|
|
80
|
-
|
|
81
|
-
#### RestClient
|
|
82
|
-
|
|
83
|
-
- **`HttpAdapter`** — new `adapter` field in `HttpConfig`. When provided, replaces the built-in axios client with a custom implementation (e.g. native `fetch`). All other features (auth, interceptors, retry, sanitization, metrics) continue to work on top of the adapter.
|
|
84
|
-
|
|
85
|
-
#### Types
|
|
86
|
-
|
|
87
|
-
- `PipelineMetrics` interface
|
|
88
|
-
- `PipelinePlugin` type
|
|
89
|
-
- `PipelineStateAdapter` type
|
|
90
|
-
- `StreamStageConfig<T>` type; updated `PipelineItem` union to include it
|
|
91
|
-
- `HttpAdapter` type
|
|
92
|
-
- `PipelineLogEventType` union — exhaustive list of all log event type strings
|
|
93
|
-
- Extended `PipelineConfig` with `metrics?`
|
|
94
|
-
- Extended `PipelineOptions` with `persistAdapter?` and `plugins?`
|
|
95
|
-
- Extended `HttpConfig` with `adapter?`
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## [1.3.6] - 2026-04-03
|
|
100
|
-
|
|
101
|
-
### Added
|
|
102
|
-
|
|
103
|
-
#### Pipeline Orchestrator
|
|
104
|
-
|
|
105
|
-
- **`continueOnError`** — per-stage and global flag to continue pipeline execution when a step fails. When enabled, failed steps are marked with `status: "error"` but do not stop the pipeline.
|
|
106
|
-
- **`next()` function** — DAG (directed acyclic graph) transitions allowing non-linear pipeline flows. After successful step execution, you can dynamically jump to any stage by its key or continue sequentially by returning `null`. Includes protection against infinite loops (max steps = stages.length × 10).
|
|
107
|
-
- **Sub-pipelines** — embed a complete `PipelineConfig` as a stage using the `subPipeline` field. Sub-pipelines run with their own context but share the parent's `sharedData` and abort signal. Results are stored under the stage key.
|
|
108
|
-
- **`pipelineRetry`** — automatic retry of the entire pipeline on failure. Supports:
|
|
109
|
-
- `attempts` — number of retry attempts
|
|
110
|
-
- `delayMs` — delay between retries
|
|
111
|
-
- `retryFrom` — resume from `"start"` (default, resets all results) or `"failed-step"` (preserves successful stage results)
|
|
112
|
-
- **`pipelineTimeoutMs`** — global timeout for the entire pipeline execution. When exceeded, the pipeline is automatically aborted via `abort()`, cancelling any in-flight HTTP requests.
|
|
113
|
-
|
|
114
|
-
#### RestClient
|
|
115
|
-
|
|
116
|
-
- **Request interceptors** — modify request configuration before sending. Supports single interceptor or array of interceptors applied in sequence.
|
|
117
|
-
- **Response interceptors** — transform response data after receiving. Applied before returning the response to the caller.
|
|
118
|
-
- **Error interceptors** — handle or modify errors before they are thrown.
|
|
119
|
-
- **Global `onError` handler** — simple callback for centralized error handling. Receives the `ApiError` and the original request configuration.
|
|
120
|
-
- **Stale-While-Revalidate cache strategy** — serves stale cached data immediately while fetching fresh data in the background. Configured via:
|
|
121
|
-
- `strategy: "stale-while-revalidate"`
|
|
122
|
-
- `staleMs` — extra time to serve stale data after TTL expires
|
|
123
|
-
- **Request deduplication** — prevents duplicate in-flight GET requests. When enabled (`deduplicateRequests: true`), multiple identical requests share the same pending Promise, reducing network traffic.
|
|
124
|
-
- **`head()` method** — execute HEAD requests to retrieve headers without the response body.
|
|
125
|
-
- **`options()` method** — execute OPTIONS requests to discover allowed HTTP methods and CORS policies.
|
|
126
|
-
|
|
127
|
-
#### Types
|
|
128
|
-
|
|
129
|
-
- Added `RequestInterceptor`, `ResponseInterceptor`, `ErrorInterceptor` types
|
|
130
|
-
- Added `SubPipelineStage` type and updated `PipelineItem` union
|
|
131
|
-
- Extended `CacheConfig` with `strategy` and `staleMs` fields
|
|
132
|
-
- Extended `HttpConfig` with `interceptors`, `onError`, `deduplicateRequests`
|
|
133
|
-
- Extended `PipelineConfig` with `options` object containing `continueOnError`, `pipelineRetry`, `pipelineTimeoutMs`
|
|
134
|
-
- Extended `PipelineStageConfig` with `continueOnError` and `next` fields
|
|
135
|
-
|
|
136
|
-
#### Cache
|
|
137
|
-
|
|
138
|
-
- **`TtlCache.getStale()`** — new method that returns cached values even after TTL expiration, as long as they are within the `staleMs` window. Returns an object with `{ value, isStale }` where `isStale: true` indicates the value is beyond TTL but still usable.
|
|
139
|
-
|
|
140
|
-
### Changed
|
|
141
|
-
|
|
142
|
-
- `PipelineOrchestrator.run()` now supports retry logic via `pipelineRetry` configuration
|
|
143
|
-
- Main `run()` logic extracted to `_runOnce()` private method to enable retry functionality
|
|
144
|
-
- `PipelineOrchestrator` constructor now properly handles `config.options` separately from constructor `options`
|
|
145
|
-
|
|
146
|
-
### Fixed
|
|
147
|
-
|
|
148
|
-
- Backward compatibility with existing `PipelineConfig` objects that do not include the new `options` field
|
|
149
|
-
- `PipelineOrchestrator` constructor no longer conflicts between `config.options` and constructor `params.options`
|
|
150
|
-
- Parallel stage groups now correctly handle `continueOnError` behavior
|
package/demo/App.vue
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { ref, shallowRef, defineAsyncComponent } from "vue";
|
|
3
|
-
|
|
4
|
-
const FlightDemo = defineAsyncComponent(() => import("./views/FlightDemo.vue"));
|
|
5
|
-
const ParallelDemo = defineAsyncComponent(
|
|
6
|
-
() => import("./views/ParallelDemo.vue"),
|
|
7
|
-
);
|
|
8
|
-
const RetryDemo = defineAsyncComponent(() => import("./views/RetryDemo.vue"));
|
|
9
|
-
const CacheDemo = defineAsyncComponent(() => import("./views/CacheDemo.vue"));
|
|
10
|
-
|
|
11
|
-
const demos = [
|
|
12
|
-
{
|
|
13
|
-
id: "flight",
|
|
14
|
-
icon: "✈️",
|
|
15
|
-
title: "Flight Pipeline",
|
|
16
|
-
subtitle: "Sequential stages · sharedData",
|
|
17
|
-
component: FlightDemo,
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
id: "parallel",
|
|
21
|
-
icon: "🔀",
|
|
22
|
-
title: "Parallel Loading",
|
|
23
|
-
subtitle: "pipe() builder · concurrent",
|
|
24
|
-
component: ParallelDemo,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
id: "retry",
|
|
28
|
-
icon: "🛡️",
|
|
29
|
-
title: "Retry & Recovery",
|
|
30
|
-
subtitle: "Backoff · abort · pause/resume",
|
|
31
|
-
component: RetryDemo,
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
id: "cache",
|
|
35
|
-
icon: "⚡",
|
|
36
|
-
title: "Cache & Rate Limit",
|
|
37
|
-
subtitle: "HTTP optimization · metrics",
|
|
38
|
-
component: CacheDemo,
|
|
39
|
-
},
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
const activeId = ref("flight");
|
|
43
|
-
const ActiveComponent = shallowRef(FlightDemo);
|
|
44
|
-
|
|
45
|
-
function navigate(demo: (typeof demos)[number]) {
|
|
46
|
-
activeId.value = demo.id;
|
|
47
|
-
ActiveComponent.value = demo.component;
|
|
48
|
-
}
|
|
49
|
-
</script>
|
|
50
|
-
|
|
51
|
-
<template>
|
|
52
|
-
<div class="app-layout">
|
|
53
|
-
<!-- ── Sidebar ──────────────────────────────────────────── -->
|
|
54
|
-
<aside class="sidebar">
|
|
55
|
-
<div class="sidebar-logo">
|
|
56
|
-
<div class="sidebar-logo__icon">🚀</div>
|
|
57
|
-
<div class="sidebar-logo__text">
|
|
58
|
-
<div class="sidebar-logo__name">rest-pipeline-js</div>
|
|
59
|
-
<div class="sidebar-logo__sub">Interactive Demo</div>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
<nav class="sidebar-nav">
|
|
64
|
-
<button
|
|
65
|
-
v-for="demo in demos"
|
|
66
|
-
:key="demo.id"
|
|
67
|
-
class="nav-item"
|
|
68
|
-
:class="{ 'nav-item--active': activeId === demo.id }"
|
|
69
|
-
@click="navigate(demo)"
|
|
70
|
-
>
|
|
71
|
-
<span class="nav-item__icon">{{ demo.icon }}</span>
|
|
72
|
-
<div class="nav-item__text">
|
|
73
|
-
<div class="nav-item__title">{{ demo.title }}</div>
|
|
74
|
-
<div class="nav-item__sub">{{ demo.subtitle }}</div>
|
|
75
|
-
</div>
|
|
76
|
-
</button>
|
|
77
|
-
</nav>
|
|
78
|
-
|
|
79
|
-
<div class="sidebar-links">
|
|
80
|
-
<a
|
|
81
|
-
href="https://github.com/macrulezru/pipeline-js"
|
|
82
|
-
target="_blank"
|
|
83
|
-
class="sidebar-link"
|
|
84
|
-
title="GitHub"
|
|
85
|
-
>
|
|
86
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor">
|
|
87
|
-
<path
|
|
88
|
-
d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"
|
|
89
|
-
/>
|
|
90
|
-
</svg>
|
|
91
|
-
GitHub
|
|
92
|
-
</a>
|
|
93
|
-
<a
|
|
94
|
-
href="https://www.npmjs.com/package/rest-pipeline-js"
|
|
95
|
-
target="_blank"
|
|
96
|
-
class="sidebar-link"
|
|
97
|
-
title="npm"
|
|
98
|
-
>
|
|
99
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor">
|
|
100
|
-
<path
|
|
101
|
-
d="M0 0v24h24V0H0zm6.672 18.586H3.328V5.414h3.344v13.172zm7.656 0h-3.344v-9.83H7.656V5.414h10.004v13.172h-3.332v-9.83z"
|
|
102
|
-
/>
|
|
103
|
-
</svg>
|
|
104
|
-
npm
|
|
105
|
-
</a>
|
|
106
|
-
</div>
|
|
107
|
-
</aside>
|
|
108
|
-
|
|
109
|
-
<!-- ── Main content ───────────────────────────────────── -->
|
|
110
|
-
<main class="content">
|
|
111
|
-
<Suspense>
|
|
112
|
-
<component :is="ActiveComponent" />
|
|
113
|
-
<template #fallback>
|
|
114
|
-
<div class="loading-placeholder">
|
|
115
|
-
<div class="loading-spinner"></div>
|
|
116
|
-
<span>Loading demo…</span>
|
|
117
|
-
</div>
|
|
118
|
-
</template>
|
|
119
|
-
</Suspense>
|
|
120
|
-
</main>
|
|
121
|
-
</div>
|
|
122
|
-
</template>
|
package/demo/index.html
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>rest-pipeline-js — Interactive Demo</title>
|
|
7
|
-
<link
|
|
8
|
-
rel="icon"
|
|
9
|
-
href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚀</text></svg>"
|
|
10
|
-
/>
|
|
11
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
12
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
13
|
-
<link
|
|
14
|
-
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
15
|
-
rel="stylesheet"
|
|
16
|
-
/>
|
|
17
|
-
</head>
|
|
18
|
-
<body>
|
|
19
|
-
<div id="app"></div>
|
|
20
|
-
<script type="module" src="./main.js"></script>
|
|
21
|
-
</body>
|
|
22
|
-
</html>
|