rest-pipeline-js 1.0.5 → 1.0.6
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 +4 -1
- package/dist/pipeline-orchestrator.d.ts +106 -10
- package/dist/pipeline-orchestrator.js +285 -20
- package/dist/progress-tracker.d.ts +6 -1
- package/dist/progress-tracker.js +7 -0
- package/dist/types.d.ts +47 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,31 +1,127 @@
|
|
|
1
|
-
import type { PipelineConfig
|
|
1
|
+
import type { PipelineConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Событие шага pipeline (для хуков)
|
|
4
|
+
*/
|
|
5
|
+
export type PipelineStepEvent = {
|
|
6
|
+
/** Индекс шага */
|
|
7
|
+
stepIndex: number;
|
|
8
|
+
/** Ключ шага */
|
|
9
|
+
stepKey: string;
|
|
10
|
+
/** Статус шага */
|
|
11
|
+
status: import('./types').PipelineStepStatus;
|
|
12
|
+
/** Данные результата (если успех) */
|
|
13
|
+
data?: any;
|
|
14
|
+
/** Ошибка (если error) */
|
|
15
|
+
error?: import('./types').ApiError;
|
|
16
|
+
/** Снимок всех результатов на момент события */
|
|
17
|
+
stageResults: Record<string, import('./types').PipelineStepResult>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Callback для подписки на события этапов pipeline
|
|
21
|
+
*/
|
|
22
|
+
export type PipelineStepEventHandler = (event: PipelineStepEvent) => void | Promise<void>;
|
|
2
23
|
export declare class PipelineOrchestrator {
|
|
3
|
-
private config;
|
|
4
24
|
private progress;
|
|
5
25
|
private errorHandler;
|
|
6
26
|
private executor;
|
|
7
27
|
private sharedData;
|
|
8
|
-
|
|
28
|
+
private onStepStartHandlers;
|
|
29
|
+
private onStepFinishHandlers;
|
|
30
|
+
private onStepErrorHandlers;
|
|
31
|
+
/** Универсальные подписчики событий: ключ — имя события */
|
|
32
|
+
private eventHandlers;
|
|
33
|
+
/** Встроенные логи */
|
|
34
|
+
private logs;
|
|
35
|
+
private stageResults;
|
|
36
|
+
private stageResultsListeners;
|
|
37
|
+
private autoReset;
|
|
38
|
+
/** AbortController для отмены пайплайна */
|
|
39
|
+
private abortController;
|
|
40
|
+
private config;
|
|
41
|
+
constructor(config: PipelineConfig, httpConfig: import('./types').HttpConfig, sharedData?: Record<string, unknown>, options?: {
|
|
42
|
+
autoReset?: boolean;
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Подписка на изменения stageResults (реактивно)
|
|
46
|
+
*/
|
|
47
|
+
subscribeStageResults(listener: (results: Record<string, import('./types').PipelineStepResult>) => void): () => void;
|
|
48
|
+
/**
|
|
49
|
+
* Универсальная подписка на события: step:<key>, progress, log и др.
|
|
50
|
+
*/
|
|
51
|
+
on(event: string, handler: (...args: any[]) => void | Promise<void>): () => void;
|
|
52
|
+
/**
|
|
53
|
+
* Вызов всех обработчиков события
|
|
54
|
+
*/
|
|
55
|
+
private emit;
|
|
56
|
+
/**
|
|
57
|
+
* Получить логи пайплайна
|
|
58
|
+
*/
|
|
59
|
+
getLogs(): {
|
|
60
|
+
type: string;
|
|
61
|
+
message: string;
|
|
62
|
+
data?: any;
|
|
63
|
+
timestamp: Date;
|
|
64
|
+
}[];
|
|
65
|
+
private notifyStageResults;
|
|
66
|
+
/**
|
|
67
|
+
* Повторно выполнить только один шаг пайплайна (без полного рестарта)
|
|
68
|
+
* @param stepKey ключ шага
|
|
69
|
+
* @param options дополнительные опции (например, onStepPause, externalSignal)
|
|
70
|
+
*/
|
|
71
|
+
rerunStep(stepKey: string, options?: {
|
|
72
|
+
onStepPause?: (stepIndex: number, stepResult: unknown, stageResults: Record<string, import('./types').PipelineStepResult>) => Promise<unknown> | unknown;
|
|
73
|
+
externalSignal?: AbortSignal;
|
|
74
|
+
}): Promise<import('./types').PipelineStepResult | undefined>;
|
|
75
|
+
/**
|
|
76
|
+
* Отменить выполнение пайплайна (вызывает ошибку AbortError)
|
|
77
|
+
*/
|
|
78
|
+
abort(): void;
|
|
79
|
+
/**
|
|
80
|
+
* Проверить, был ли пайплайн отменён
|
|
81
|
+
*/
|
|
82
|
+
isAborted(): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Подписка на событие начала шага
|
|
85
|
+
*/
|
|
86
|
+
onStepStart(handler: PipelineStepEventHandler): () => void;
|
|
87
|
+
/**
|
|
88
|
+
* Подписка на событие успешного завершения шага
|
|
89
|
+
*/
|
|
90
|
+
onStepFinish(handler: PipelineStepEventHandler): () => void;
|
|
91
|
+
/**
|
|
92
|
+
* Подписка на событие ошибки шага
|
|
93
|
+
*/
|
|
94
|
+
onStepError(handler: PipelineStepEventHandler): () => void;
|
|
95
|
+
private emitStepStart;
|
|
96
|
+
private emitStepFinish;
|
|
97
|
+
private emitStepError;
|
|
9
98
|
/**
|
|
10
99
|
* Подписаться на изменения прогресса выполнения pipeline
|
|
11
100
|
* @param listener функция-обработчик изменений
|
|
12
101
|
* @returns функция для отписки
|
|
13
102
|
*/
|
|
14
103
|
subscribeProgress(listener: (progress: import('./types').PipelineProgress) => void): () => void;
|
|
104
|
+
/**
|
|
105
|
+
* Подписка на прогресс с фильтрацией по этапу (stepKey) или общий
|
|
106
|
+
*/
|
|
107
|
+
subscribeStepProgress(stepKey: string, listener: (status: import('./types').PipelineStepStatus) => void): () => void;
|
|
15
108
|
/**
|
|
16
109
|
* Получить текущий прогресс выполнения pipeline (snapshot, не реактивный)
|
|
17
110
|
*/
|
|
18
111
|
getProgress(): {
|
|
19
112
|
currentStage: number;
|
|
20
113
|
totalStages: number;
|
|
21
|
-
stageStatuses: Array<"
|
|
114
|
+
stageStatuses: Array<import("./types").PipelineStepStatus>;
|
|
22
115
|
};
|
|
23
116
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
117
|
+
* Возвращает текущий снимок состояния прогресса (не реактивный).
|
|
118
|
+
* Для отслеживания изменений используйте subscribeProgress.
|
|
119
|
+
*/
|
|
120
|
+
getProgressRef(): import("./types").PipelineProgress;
|
|
121
|
+
/**
|
|
122
|
+
* Запустить выполнение пайплайна
|
|
123
|
+
* @param onStepPause callback для пользовательской паузы между шагами
|
|
124
|
+
* @param externalSignal внешний AbortSignal (опционально)
|
|
29
125
|
*/
|
|
30
|
-
run(onStepPause?: (stepIndex: number, stepResult: unknown,
|
|
126
|
+
run(onStepPause?: (stepIndex: number, stepResult: unknown, stageResults: Record<string, import('./types').PipelineStepResult>) => Promise<unknown> | unknown, externalSignal?: AbortSignal): Promise<import('./types').PipelineResult>;
|
|
31
127
|
}
|
|
@@ -4,14 +4,204 @@ exports.PipelineOrchestrator = void 0;
|
|
|
4
4
|
const error_handler_1 = require("./error-handler");
|
|
5
5
|
const progress_tracker_1 = require("./progress-tracker");
|
|
6
6
|
const request_executor_1 = require("./request-executor");
|
|
7
|
-
// import { metricsBus } from '@/core/metrics/metrics-bus';
|
|
8
7
|
class PipelineOrchestrator {
|
|
9
|
-
constructor(config, httpConfig, sharedData = {}) {
|
|
8
|
+
constructor(config, httpConfig, sharedData = {}, options = {}) {
|
|
9
|
+
var _a;
|
|
10
|
+
this.onStepStartHandlers = [];
|
|
11
|
+
this.onStepFinishHandlers = [];
|
|
12
|
+
this.onStepErrorHandlers = [];
|
|
13
|
+
/** Универсальные подписчики событий: ключ — имя события */
|
|
14
|
+
this.eventHandlers = {};
|
|
15
|
+
/** Встроенные логи */
|
|
16
|
+
this.logs = [];
|
|
17
|
+
this.stageResults = {};
|
|
18
|
+
this.stageResultsListeners = [];
|
|
19
|
+
/** AbortController для отмены пайплайна */
|
|
20
|
+
this.abortController = null;
|
|
10
21
|
this.config = config;
|
|
11
22
|
this.progress = new progress_tracker_1.ProgressTracker(config.stages.length);
|
|
12
23
|
this.errorHandler = new error_handler_1.ErrorHandler();
|
|
13
24
|
this.executor = new request_executor_1.RequestExecutor(httpConfig);
|
|
14
25
|
this.sharedData = sharedData;
|
|
26
|
+
this.autoReset = (_a = options.autoReset) !== null && _a !== void 0 ? _a : false;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Подписка на изменения stageResults (реактивно)
|
|
30
|
+
*/
|
|
31
|
+
subscribeStageResults(listener) {
|
|
32
|
+
this.stageResultsListeners.push(listener);
|
|
33
|
+
// Немедленно уведомляем нового подписчика о текущем состоянии
|
|
34
|
+
listener({ ...this.stageResults });
|
|
35
|
+
return () => {
|
|
36
|
+
this.stageResultsListeners = this.stageResultsListeners.filter(l => l !== listener);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Универсальная подписка на события: step:<key>, progress, log и др.
|
|
41
|
+
*/
|
|
42
|
+
on(event, handler) {
|
|
43
|
+
if (!this.eventHandlers[event])
|
|
44
|
+
this.eventHandlers[event] = [];
|
|
45
|
+
this.eventHandlers[event].push(handler);
|
|
46
|
+
return () => {
|
|
47
|
+
this.eventHandlers[event] = this.eventHandlers[event].filter(h => h !== handler);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Вызов всех обработчиков события
|
|
52
|
+
*/
|
|
53
|
+
async emit(event, ...args) {
|
|
54
|
+
if (this.eventHandlers[event]) {
|
|
55
|
+
for (const handler of this.eventHandlers[event]) {
|
|
56
|
+
await handler(...args);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Получить логи пайплайна
|
|
62
|
+
*/
|
|
63
|
+
getLogs() {
|
|
64
|
+
return [...this.logs];
|
|
65
|
+
}
|
|
66
|
+
notifyStageResults() {
|
|
67
|
+
for (const listener of this.stageResultsListeners) {
|
|
68
|
+
listener({ ...this.stageResults });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Повторно выполнить только один шаг пайплайна (без полного рестарта)
|
|
73
|
+
* @param stepKey ключ шага
|
|
74
|
+
* @param options дополнительные опции (например, onStepPause, externalSignal)
|
|
75
|
+
*/
|
|
76
|
+
async rerunStep(stepKey, options) {
|
|
77
|
+
var _a;
|
|
78
|
+
const i = this.config.stages.findIndex(s => s.key === stepKey);
|
|
79
|
+
if (i === -1)
|
|
80
|
+
return undefined;
|
|
81
|
+
const stage = this.config.stages[i];
|
|
82
|
+
const key = stage.key;
|
|
83
|
+
const signal = options === null || options === void 0 ? void 0 : options.externalSignal;
|
|
84
|
+
this.logs.push({ type: 'log', message: `rerunStep:${key}:start`, timestamp: new Date(), data: { stepIndex: i } });
|
|
85
|
+
await this.emit('log', { type: 'rerunStep:start', stepKey: key, stepIndex: i });
|
|
86
|
+
this.stageResults[key] = { status: 'pending' };
|
|
87
|
+
this.notifyStageResults();
|
|
88
|
+
this.progress.updateStage(i, 'loading');
|
|
89
|
+
await this.emitStepStart({ stepIndex: i, stepKey: key, status: 'loading', stageResults: { ...this.stageResults } });
|
|
90
|
+
await this.emit(`step:${key}:start`, { stepIndex: i, stepKey: key, status: 'loading', stageResults: { ...this.stageResults } });
|
|
91
|
+
try {
|
|
92
|
+
let stepResult;
|
|
93
|
+
if (typeof stage.request === 'function') {
|
|
94
|
+
stepResult = await stage.request(i > 0 ? (_a = this.stageResults[this.config.stages[i - 1].key]) === null || _a === void 0 ? void 0 : _a.data : undefined, this.stageResults);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const res = await this.executor.execute(stage.key, undefined, stage.retryCount, stage.timeoutMs);
|
|
98
|
+
stepResult = res.data;
|
|
99
|
+
}
|
|
100
|
+
if (options === null || options === void 0 ? void 0 : options.onStepPause) {
|
|
101
|
+
stepResult = await options.onStepPause(i, stepResult, this.stageResults);
|
|
102
|
+
}
|
|
103
|
+
this.stageResults[key] = { status: 'success', data: stepResult };
|
|
104
|
+
this.notifyStageResults();
|
|
105
|
+
this.progress.updateStage(i, 'success');
|
|
106
|
+
await this.emitStepFinish({ stepIndex: i, stepKey: key, status: 'success', data: stepResult, stageResults: { ...this.stageResults } });
|
|
107
|
+
await this.emit(`step:${key}:success`, { stepIndex: i, stepKey: key, status: 'success', data: stepResult, stageResults: { ...this.stageResults } });
|
|
108
|
+
this.logs.push({ type: 'log', message: `rerunStep:${key}:success`, timestamp: new Date(), data: { stepIndex: i, data: stepResult } });
|
|
109
|
+
await this.emit('log', { type: 'rerunStep:success', stepKey: key, stepIndex: i, data: stepResult });
|
|
110
|
+
return this.stageResults[key];
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
let handled;
|
|
114
|
+
if (stage && typeof stage.errorHandler === 'function') {
|
|
115
|
+
handled = stage.errorHandler(err, stage.key, this.sharedData);
|
|
116
|
+
}
|
|
117
|
+
else if (stage) {
|
|
118
|
+
handled = this.errorHandler.handle(err, stage.key);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
handled = this.errorHandler.handle(err, 'unknown');
|
|
122
|
+
}
|
|
123
|
+
if (!handled && stage) {
|
|
124
|
+
handled = this.errorHandler.handle(err, stage.key);
|
|
125
|
+
}
|
|
126
|
+
const { toApiError } = await import('./rest-client.js');
|
|
127
|
+
const apiError = toApiError(handled !== null && handled !== void 0 ? handled : err);
|
|
128
|
+
this.stageResults[key] = { status: 'error', error: apiError };
|
|
129
|
+
this.notifyStageResults();
|
|
130
|
+
this.progress.updateStage(i, 'error');
|
|
131
|
+
await this.emitStepError({ stepIndex: i, stepKey: key, status: 'error', error: apiError, stageResults: { ...this.stageResults } });
|
|
132
|
+
await this.emit(`step:${key}:error`, { stepIndex: i, stepKey: key, status: 'error', error: apiError, stageResults: { ...this.stageResults } });
|
|
133
|
+
this.logs.push({ type: 'error', message: `rerunStep:${key}:error`, timestamp: new Date(), data: { stepIndex: i, error: apiError } });
|
|
134
|
+
await this.emit('log', { type: 'rerunStep:error', stepKey: key, stepIndex: i, error: apiError });
|
|
135
|
+
return this.stageResults[key];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Отменить выполнение пайплайна (вызывает ошибку AbortError)
|
|
140
|
+
*/
|
|
141
|
+
abort() {
|
|
142
|
+
if (this.abortController) {
|
|
143
|
+
this.abortController.abort();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Проверить, был ли пайплайн отменён
|
|
148
|
+
*/
|
|
149
|
+
isAborted() {
|
|
150
|
+
var _a, _b;
|
|
151
|
+
return (_b = (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.signal.aborted) !== null && _b !== void 0 ? _b : false;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Подписка на событие начала шага
|
|
155
|
+
*/
|
|
156
|
+
onStepStart(handler) {
|
|
157
|
+
this.onStepStartHandlers.push(handler);
|
|
158
|
+
return () => {
|
|
159
|
+
this.onStepStartHandlers = this.onStepStartHandlers.filter(h => h !== handler);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Подписка на событие успешного завершения шага
|
|
164
|
+
*/
|
|
165
|
+
onStepFinish(handler) {
|
|
166
|
+
this.onStepFinishHandlers.push(handler);
|
|
167
|
+
return () => {
|
|
168
|
+
this.onStepFinishHandlers = this.onStepFinishHandlers.filter(h => h !== handler);
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Подписка на событие ошибки шага
|
|
173
|
+
*/
|
|
174
|
+
onStepError(handler) {
|
|
175
|
+
this.onStepErrorHandlers.push(handler);
|
|
176
|
+
return () => {
|
|
177
|
+
this.onStepErrorHandlers = this.onStepErrorHandlers.filter(h => h !== handler);
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async emitStepStart(event) {
|
|
181
|
+
for (const handler of this.onStepStartHandlers) {
|
|
182
|
+
await handler(event);
|
|
183
|
+
}
|
|
184
|
+
// Гибкая подписка на step:<key>:start
|
|
185
|
+
await this.emit(`step:${event.stepKey}:start`, event);
|
|
186
|
+
// Логирование
|
|
187
|
+
this.logs.push({ type: 'log', message: `step:${event.stepKey}:start`, timestamp: new Date(), data: event });
|
|
188
|
+
await this.emit('log', { type: 'step:start', ...event });
|
|
189
|
+
}
|
|
190
|
+
async emitStepFinish(event) {
|
|
191
|
+
for (const handler of this.onStepFinishHandlers) {
|
|
192
|
+
await handler(event);
|
|
193
|
+
}
|
|
194
|
+
await this.emit(`step:${event.stepKey}:success`, event);
|
|
195
|
+
this.logs.push({ type: 'log', message: `step:${event.stepKey}:success`, timestamp: new Date(), data: event });
|
|
196
|
+
await this.emit('log', { type: 'step:success', ...event });
|
|
197
|
+
}
|
|
198
|
+
async emitStepError(event) {
|
|
199
|
+
for (const handler of this.onStepErrorHandlers) {
|
|
200
|
+
await handler(event);
|
|
201
|
+
}
|
|
202
|
+
await this.emit(`step:${event.stepKey}:error`, event);
|
|
203
|
+
this.logs.push({ type: 'error', message: `step:${event.stepKey}:error`, timestamp: new Date(), data: event });
|
|
204
|
+
await this.emit('log', { type: 'step:error', ...event });
|
|
15
205
|
}
|
|
16
206
|
/**
|
|
17
207
|
* Подписаться на изменения прогресса выполнения pipeline
|
|
@@ -21,6 +211,12 @@ class PipelineOrchestrator {
|
|
|
21
211
|
subscribeProgress(listener) {
|
|
22
212
|
return this.progress.subscribe(listener);
|
|
23
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Подписка на прогресс с фильтрацией по этапу (stepKey) или общий
|
|
216
|
+
*/
|
|
217
|
+
subscribeStepProgress(stepKey, listener) {
|
|
218
|
+
return this.on(`step:${stepKey}:progress`, listener);
|
|
219
|
+
}
|
|
24
220
|
/**
|
|
25
221
|
* Получить текущий прогресс выполнения pipeline (snapshot, не реактивный)
|
|
26
222
|
*/
|
|
@@ -28,35 +224,82 @@ class PipelineOrchestrator {
|
|
|
28
224
|
return this.progress.getProgress();
|
|
29
225
|
}
|
|
30
226
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* Позволяет приостановить выполнение, запросить подтверждение пользователя или изменить результат шага.
|
|
34
|
-
* Должен вернуть (optionally изменённый) результат шага или промис с ним.
|
|
35
|
-
* Если не передан — пайплайн работает как раньше.
|
|
227
|
+
* Возвращает текущий снимок состояния прогресса (не реактивный).
|
|
228
|
+
* Для отслеживания изменений используйте subscribeProgress.
|
|
36
229
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
230
|
+
getProgressRef() {
|
|
231
|
+
return this.progress.getProgressRef();
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Запустить выполнение пайплайна
|
|
235
|
+
* @param onStepPause callback для пользовательской паузы между шагами
|
|
236
|
+
* @param externalSignal внешний AbortSignal (опционально)
|
|
237
|
+
*/
|
|
238
|
+
async run(onStepPause, externalSignal) {
|
|
239
|
+
var _a, _b, _c;
|
|
240
|
+
if (this.autoReset) {
|
|
241
|
+
this.stageResults = {};
|
|
242
|
+
this.notifyStageResults();
|
|
243
|
+
}
|
|
40
244
|
let success = true;
|
|
245
|
+
// Создаём новый AbortController для этого запуска
|
|
246
|
+
this.abortController = new AbortController();
|
|
247
|
+
const signal = externalSignal !== null && externalSignal !== void 0 ? externalSignal : this.abortController.signal;
|
|
41
248
|
for (let i = 0; i < this.config.stages.length; i++) {
|
|
249
|
+
if (signal.aborted) {
|
|
250
|
+
// Прерываем выполнение, если был вызван abort
|
|
251
|
+
const { toApiError } = await import('./rest-client.js');
|
|
252
|
+
const apiError = toApiError({ message: 'Pipeline aborted', code: 'ABORTED' });
|
|
253
|
+
const key = ((_a = this.config.stages[i]) === null || _a === void 0 ? void 0 : _a.key) || `stage${i}`;
|
|
254
|
+
this.stageResults[key] = { status: 'error', error: apiError };
|
|
255
|
+
this.notifyStageResults();
|
|
256
|
+
this.progress.updateStage(i, 'error');
|
|
257
|
+
this.logs.push({ type: 'error', message: `abort:${key}`, timestamp: new Date(), data: { stepIndex: i, error: apiError } });
|
|
258
|
+
await this.emit('log', { type: 'abort', stepKey: key, stepIndex: i, error: apiError });
|
|
259
|
+
await this.emitStepError({
|
|
260
|
+
stepIndex: i,
|
|
261
|
+
stepKey: key,
|
|
262
|
+
status: 'error',
|
|
263
|
+
error: apiError,
|
|
264
|
+
stageResults: { ...this.stageResults },
|
|
265
|
+
});
|
|
266
|
+
success = false;
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
42
269
|
const stage = this.config.stages[i];
|
|
43
|
-
|
|
270
|
+
const key = (stage === null || stage === void 0 ? void 0 : stage.key) || `stage${i}`;
|
|
271
|
+
this.stageResults[key] = { status: 'pending' };
|
|
272
|
+
this.notifyStageResults();
|
|
273
|
+
this.progress.updateStage(i, 'loading');
|
|
274
|
+
// Гибкая подписка на прогресс шага
|
|
275
|
+
await this.emit(`step:${key}:progress`, 'loading');
|
|
276
|
+
// emit step start
|
|
277
|
+
await this.emitStepStart({
|
|
278
|
+
stepIndex: i,
|
|
279
|
+
stepKey: key,
|
|
280
|
+
status: 'loading',
|
|
281
|
+
stageResults: { ...this.stageResults },
|
|
282
|
+
});
|
|
44
283
|
if (!stage) {
|
|
45
284
|
this.progress.updateStage(i, 'skipped');
|
|
46
|
-
|
|
285
|
+
this.stageResults[key] = { status: 'skipped' };
|
|
286
|
+
this.notifyStageResults();
|
|
287
|
+
await this.emit(`step:${key}:progress`, 'skipped');
|
|
47
288
|
continue;
|
|
48
289
|
}
|
|
49
290
|
// Проверка условия выполнения этапа
|
|
50
|
-
if (stage.condition && !stage.condition(
|
|
291
|
+
if (stage.condition && !stage.condition(i > 0 ? (_b = this.stageResults[this.config.stages[i - 1].key]) === null || _b === void 0 ? void 0 : _b.data : undefined, this.stageResults, this.sharedData)) {
|
|
51
292
|
this.progress.updateStage(i, 'skipped');
|
|
52
|
-
|
|
293
|
+
this.stageResults[key] = { status: 'skipped' };
|
|
294
|
+
this.notifyStageResults();
|
|
295
|
+
await this.emit(`step:${key}:progress`, 'skipped');
|
|
53
296
|
continue;
|
|
54
297
|
}
|
|
55
298
|
try {
|
|
56
299
|
let stepResult;
|
|
57
300
|
// Всегда передаём (prev, allResults) в request — best practice для pipeline
|
|
58
301
|
if (typeof stage.request === 'function') {
|
|
59
|
-
stepResult = await stage.request(
|
|
302
|
+
stepResult = await stage.request(i > 0 ? (_c = this.stageResults[this.config.stages[i - 1].key]) === null || _c === void 0 ? void 0 : _c.data : undefined, this.stageResults);
|
|
60
303
|
}
|
|
61
304
|
else if (stage.key) {
|
|
62
305
|
const res = await this.executor.execute(stage.key, undefined, stage.retryCount, stage.timeoutMs);
|
|
@@ -67,11 +310,20 @@ class PipelineOrchestrator {
|
|
|
67
310
|
}
|
|
68
311
|
// --- Пользовательская пауза/подтверждение/изменение результата ---
|
|
69
312
|
if (onStepPause) {
|
|
70
|
-
stepResult = await onStepPause(i, stepResult,
|
|
313
|
+
stepResult = await onStepPause(i, stepResult, this.stageResults);
|
|
71
314
|
}
|
|
72
|
-
|
|
315
|
+
this.stageResults[key] = { status: 'success', data: stepResult };
|
|
316
|
+
this.notifyStageResults();
|
|
73
317
|
this.progress.updateStage(i, 'success');
|
|
74
|
-
|
|
318
|
+
await this.emit(`step:${key}:progress`, 'success');
|
|
319
|
+
// emit step finish
|
|
320
|
+
await this.emitStepFinish({
|
|
321
|
+
stepIndex: i,
|
|
322
|
+
stepKey: key,
|
|
323
|
+
status: 'success',
|
|
324
|
+
data: stepResult,
|
|
325
|
+
stageResults: { ...this.stageResults },
|
|
326
|
+
});
|
|
75
327
|
}
|
|
76
328
|
catch (err) {
|
|
77
329
|
let handled;
|
|
@@ -87,13 +339,26 @@ class PipelineOrchestrator {
|
|
|
87
339
|
if (!handled && stage) {
|
|
88
340
|
handled = this.errorHandler.handle(err, stage.key);
|
|
89
341
|
}
|
|
90
|
-
|
|
342
|
+
// Унификация: всегда ApiError
|
|
343
|
+
const { toApiError } = await import('./rest-client.js');
|
|
344
|
+
const apiError = toApiError(handled !== null && handled !== void 0 ? handled : err);
|
|
345
|
+
this.stageResults[key] = { status: 'error', error: apiError };
|
|
346
|
+
this.notifyStageResults();
|
|
91
347
|
this.progress.updateStage(i, 'error');
|
|
348
|
+
await this.emit(`step:${key}:progress`, 'error');
|
|
349
|
+
// emit step error
|
|
350
|
+
await this.emitStepError({
|
|
351
|
+
stepIndex: i,
|
|
352
|
+
stepKey: key,
|
|
353
|
+
status: 'error',
|
|
354
|
+
error: apiError,
|
|
355
|
+
stageResults: { ...this.stageResults },
|
|
356
|
+
});
|
|
92
357
|
success = false;
|
|
93
358
|
break;
|
|
94
359
|
}
|
|
95
360
|
}
|
|
96
|
-
return {
|
|
361
|
+
return { stageResults: { ...this.stageResults }, success };
|
|
97
362
|
}
|
|
98
363
|
}
|
|
99
364
|
exports.PipelineOrchestrator = PipelineOrchestrator;
|
|
@@ -4,11 +4,16 @@ export declare class ProgressTracker {
|
|
|
4
4
|
private progress;
|
|
5
5
|
private listeners;
|
|
6
6
|
constructor(totalStages: number);
|
|
7
|
+
/**
|
|
8
|
+
* Возвращает текущий снимок состояния прогресса (не реактивный).
|
|
9
|
+
* Для отслеживания изменений используйте subscribeProgress.
|
|
10
|
+
*/
|
|
11
|
+
getProgressRef(): PipelineProgress;
|
|
7
12
|
updateStage(stage: number, status: PipelineProgress['stageStatuses'][number]): void;
|
|
8
13
|
getProgress(): {
|
|
9
14
|
currentStage: number;
|
|
10
15
|
totalStages: number;
|
|
11
|
-
stageStatuses: Array<"
|
|
16
|
+
stageStatuses: Array<import("./types").PipelineStepStatus>;
|
|
12
17
|
};
|
|
13
18
|
subscribe(listener: ProgressListener): () => void;
|
|
14
19
|
private notify;
|
package/dist/progress-tracker.js
CHANGED
|
@@ -10,6 +10,13 @@ class ProgressTracker {
|
|
|
10
10
|
stageStatuses: Array(totalStages).fill('pending'),
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Возвращает текущий снимок состояния прогресса (не реактивный).
|
|
15
|
+
* Для отслеживания изменений используйте subscribeProgress.
|
|
16
|
+
*/
|
|
17
|
+
getProgressRef() {
|
|
18
|
+
return this.progress;
|
|
19
|
+
}
|
|
13
20
|
updateStage(stage, status) {
|
|
14
21
|
this.progress.stageStatuses[stage] = status;
|
|
15
22
|
this.progress.currentStage = stage;
|
package/dist/types.d.ts
CHANGED
|
@@ -63,24 +63,64 @@ export type RestRequestConfig = import('axios').AxiosRequestConfig & {
|
|
|
63
63
|
skipRateLimit?: boolean;
|
|
64
64
|
requestId?: string;
|
|
65
65
|
};
|
|
66
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Конфиг одного шага (этапа) pipeline
|
|
68
|
+
* @template Input Тип входных данных шага
|
|
69
|
+
* @template Output Тип результата шага
|
|
70
|
+
*/
|
|
71
|
+
export type PipelineStageConfig<Input = any, Output = any> = {
|
|
72
|
+
/** Уникальный ключ шага */
|
|
67
73
|
key: string;
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
/** Асинхронная функция-запрос шага */
|
|
75
|
+
request: (input: Input, allResults?: Record<string, PipelineStepResult>) => Promise<Output>;
|
|
76
|
+
/** Условие выполнения шага */
|
|
77
|
+
condition?: (input: Input, prevResults: Record<string, PipelineStepResult>, sharedData?: Record<string, any>) => boolean;
|
|
78
|
+
/** Количество попыток при ошибке */
|
|
70
79
|
retryCount?: number;
|
|
80
|
+
/** Таймаут шага (мс) */
|
|
71
81
|
timeoutMs?: number;
|
|
82
|
+
/** Обработчик ошибок шага */
|
|
72
83
|
errorHandler?: (error: any, stageKey: string, sharedData?: Record<string, any>) => any;
|
|
73
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Статус выполнения шага pipeline
|
|
87
|
+
*/
|
|
88
|
+
export type PipelineStepStatus = 'pending' | 'loading' | 'success' | 'error' | 'skipped';
|
|
89
|
+
/**
|
|
90
|
+
* Результат выполнения шага pipeline
|
|
91
|
+
*/
|
|
92
|
+
export type PipelineStepResult = {
|
|
93
|
+
/** Статус шага */
|
|
94
|
+
status: PipelineStepStatus;
|
|
95
|
+
/** Данные результата (если успех) */
|
|
96
|
+
data?: any;
|
|
97
|
+
/** Ошибка (если error) */
|
|
98
|
+
error?: import('./types').ApiError;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Конфиг всего pipeline (массив этапов)
|
|
102
|
+
*/
|
|
74
103
|
export type PipelineConfig = {
|
|
75
|
-
stages: PipelineStageConfig
|
|
104
|
+
stages: PipelineStageConfig[];
|
|
76
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Прогресс выполнения pipeline
|
|
108
|
+
*/
|
|
77
109
|
export type PipelineProgress = {
|
|
78
110
|
currentStage: number;
|
|
79
111
|
totalStages: number;
|
|
80
|
-
stageStatuses: Array<
|
|
112
|
+
stageStatuses: Array<PipelineStepStatus>;
|
|
81
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Результаты всех шагов pipeline (ключ — имя шага)
|
|
116
|
+
*/
|
|
117
|
+
export type PipelineStageResults = Record<string, PipelineStepResult>;
|
|
118
|
+
/**
|
|
119
|
+
* Итоговый результат выполнения pipeline
|
|
120
|
+
*/
|
|
82
121
|
export type PipelineResult = {
|
|
83
|
-
|
|
84
|
-
|
|
122
|
+
/** Результаты по шагам */
|
|
123
|
+
stageResults: PipelineStageResults;
|
|
124
|
+
/** true, если pipeline завершился успешно */
|
|
85
125
|
success: boolean;
|
|
86
126
|
};
|