rest-pipeline-js 1.0.2 → 1.0.3
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/dist/error-handler.d.ts +7 -0
- package/dist/error-handler.js +10 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +23 -0
- package/dist/pipeline-orchestrator.d.ts +31 -0
- package/dist/pipeline-orchestrator.js +99 -0
- package/dist/progress-tracker.d.ts +16 -0
- package/dist/progress-tracker.js +35 -0
- package/dist/request-executor.d.ts +9 -0
- package/dist/request-executor.js +33 -0
- package/dist/rest-client.d.ts +14 -0
- package/dist/rest-client.js +168 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +2 -0
- package/package.json +2 -2
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ErrorHandler = void 0;
|
|
4
|
+
class ErrorHandler {
|
|
5
|
+
handle(error, stageKey) {
|
|
6
|
+
// TODO: реализовать классификацию и обработку ошибок
|
|
7
|
+
return { type: 'unknown', error, stageKey };
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.ErrorHandler = ErrorHandler;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
// Barrel file for pipeline-js module
|
|
18
|
+
__exportStar(require("./rest-client"), exports);
|
|
19
|
+
__exportStar(require("./types"), exports);
|
|
20
|
+
__exportStar(require("./request-executor"), exports);
|
|
21
|
+
__exportStar(require("./error-handler"), exports);
|
|
22
|
+
__exportStar(require("./progress-tracker"), exports);
|
|
23
|
+
__exportStar(require("./pipeline-orchestrator"), exports);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { PipelineConfig, PipelineResult } from './types';
|
|
2
|
+
export declare class PipelineOrchestrator {
|
|
3
|
+
private config;
|
|
4
|
+
private progress;
|
|
5
|
+
private errorHandler;
|
|
6
|
+
private executor;
|
|
7
|
+
private sharedData;
|
|
8
|
+
constructor(config: PipelineConfig, httpConfig: import('./types').HttpConfig, sharedData?: Record<string, unknown>);
|
|
9
|
+
/**
|
|
10
|
+
* Подписаться на изменения прогресса выполнения pipeline
|
|
11
|
+
* @param listener функция-обработчик изменений
|
|
12
|
+
* @returns функция для отписки
|
|
13
|
+
*/
|
|
14
|
+
subscribeProgress(listener: (progress: import('./types').PipelineProgress) => void): () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Получить текущий прогресс выполнения pipeline (snapshot, не реактивный)
|
|
17
|
+
*/
|
|
18
|
+
getProgress(): {
|
|
19
|
+
currentStage: number;
|
|
20
|
+
totalStages: number;
|
|
21
|
+
stageStatuses: Array<"pending" | "in-progress" | "success" | "error" | "skipped">;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* @param onStepPause
|
|
25
|
+
* Необязательный callback, вызывается после каждого шага (до перехода к следующему).
|
|
26
|
+
* Позволяет приостановить выполнение, запросить подтверждение пользователя или изменить результат шага.
|
|
27
|
+
* Должен вернуть (optionally изменённый) результат шага или промис с ним.
|
|
28
|
+
* Если не передан — пайплайн работает как раньше.
|
|
29
|
+
*/
|
|
30
|
+
run(onStepPause?: (stepIndex: number, stepResult: unknown, results: unknown[]) => Promise<unknown> | unknown): Promise<PipelineResult>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PipelineOrchestrator = void 0;
|
|
4
|
+
const error_handler_1 = require("./error-handler");
|
|
5
|
+
const progress_tracker_1 = require("./progress-tracker");
|
|
6
|
+
const request_executor_1 = require("./request-executor");
|
|
7
|
+
// import { metricsBus } from '@/core/metrics/metrics-bus';
|
|
8
|
+
class PipelineOrchestrator {
|
|
9
|
+
constructor(config, httpConfig, sharedData = {}) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.progress = new progress_tracker_1.ProgressTracker(config.stages.length);
|
|
12
|
+
this.errorHandler = new error_handler_1.ErrorHandler();
|
|
13
|
+
this.executor = new request_executor_1.RequestExecutor(httpConfig);
|
|
14
|
+
this.sharedData = sharedData;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Подписаться на изменения прогресса выполнения pipeline
|
|
18
|
+
* @param listener функция-обработчик изменений
|
|
19
|
+
* @returns функция для отписки
|
|
20
|
+
*/
|
|
21
|
+
subscribeProgress(listener) {
|
|
22
|
+
return this.progress.subscribe(listener);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Получить текущий прогресс выполнения pipeline (snapshot, не реактивный)
|
|
26
|
+
*/
|
|
27
|
+
getProgress() {
|
|
28
|
+
return this.progress.getProgress();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* @param onStepPause
|
|
32
|
+
* Необязательный callback, вызывается после каждого шага (до перехода к следующему).
|
|
33
|
+
* Позволяет приостановить выполнение, запросить подтверждение пользователя или изменить результат шага.
|
|
34
|
+
* Должен вернуть (optionally изменённый) результат шага или промис с ним.
|
|
35
|
+
* Если не передан — пайплайн работает как раньше.
|
|
36
|
+
*/
|
|
37
|
+
async run(onStepPause) {
|
|
38
|
+
const results = [];
|
|
39
|
+
const errors = [];
|
|
40
|
+
let success = true;
|
|
41
|
+
for (let i = 0; i < this.config.stages.length; i++) {
|
|
42
|
+
const stage = this.config.stages[i];
|
|
43
|
+
this.progress.updateStage(i, 'in-progress');
|
|
44
|
+
if (!stage) {
|
|
45
|
+
this.progress.updateStage(i, 'skipped');
|
|
46
|
+
results.push(undefined);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Проверка условия выполнения этапа
|
|
50
|
+
if (stage.condition && !stage.condition(results[i - 1], results, this.sharedData)) {
|
|
51
|
+
this.progress.updateStage(i, 'skipped');
|
|
52
|
+
results.push(undefined);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
let stepResult;
|
|
57
|
+
// Всегда передаём (prev, allResults) в request — best practice для pipeline
|
|
58
|
+
if (typeof stage.request === 'function') {
|
|
59
|
+
stepResult = await stage.request(results[i - 1], results);
|
|
60
|
+
}
|
|
61
|
+
else if (stage.key) {
|
|
62
|
+
const res = await this.executor.execute(stage.key, undefined, stage.retryCount, stage.timeoutMs);
|
|
63
|
+
stepResult = res.data;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
stepResult = undefined;
|
|
67
|
+
}
|
|
68
|
+
// --- Пользовательская пауза/подтверждение/изменение результата ---
|
|
69
|
+
if (onStepPause) {
|
|
70
|
+
stepResult = await onStepPause(i, stepResult, results);
|
|
71
|
+
}
|
|
72
|
+
results.push(stepResult);
|
|
73
|
+
this.progress.updateStage(i, 'success');
|
|
74
|
+
// ...existing code...
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
let handled;
|
|
78
|
+
if (stage && typeof stage.errorHandler === 'function') {
|
|
79
|
+
handled = stage.errorHandler(err, stage.key, this.sharedData);
|
|
80
|
+
}
|
|
81
|
+
else if (stage) {
|
|
82
|
+
handled = this.errorHandler.handle(err, stage.key);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
handled = this.errorHandler.handle(err, 'unknown');
|
|
86
|
+
}
|
|
87
|
+
if (!handled && stage) {
|
|
88
|
+
handled = this.errorHandler.handle(err, stage.key);
|
|
89
|
+
}
|
|
90
|
+
errors.push(handled);
|
|
91
|
+
this.progress.updateStage(i, 'error');
|
|
92
|
+
success = false;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { results, errors, success };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.PipelineOrchestrator = PipelineOrchestrator;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PipelineProgress } from './types';
|
|
2
|
+
type ProgressListener = (progress: PipelineProgress) => void;
|
|
3
|
+
export declare class ProgressTracker {
|
|
4
|
+
private progress;
|
|
5
|
+
private listeners;
|
|
6
|
+
constructor(totalStages: number);
|
|
7
|
+
updateStage(stage: number, status: PipelineProgress['stageStatuses'][number]): void;
|
|
8
|
+
getProgress(): {
|
|
9
|
+
currentStage: number;
|
|
10
|
+
totalStages: number;
|
|
11
|
+
stageStatuses: Array<"pending" | "in-progress" | "success" | "error" | "skipped">;
|
|
12
|
+
};
|
|
13
|
+
subscribe(listener: ProgressListener): () => void;
|
|
14
|
+
private notify;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProgressTracker = void 0;
|
|
4
|
+
class ProgressTracker {
|
|
5
|
+
constructor(totalStages) {
|
|
6
|
+
this.listeners = [];
|
|
7
|
+
this.progress = {
|
|
8
|
+
currentStage: 0,
|
|
9
|
+
totalStages,
|
|
10
|
+
stageStatuses: Array(totalStages).fill('pending'),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
updateStage(stage, status) {
|
|
14
|
+
this.progress.stageStatuses[stage] = status;
|
|
15
|
+
this.progress.currentStage = stage;
|
|
16
|
+
this.notify();
|
|
17
|
+
}
|
|
18
|
+
getProgress() {
|
|
19
|
+
return { ...this.progress };
|
|
20
|
+
}
|
|
21
|
+
subscribe(listener) {
|
|
22
|
+
this.listeners.push(listener);
|
|
23
|
+
// Немедленно уведомляем нового подписчика о текущем состоянии
|
|
24
|
+
listener({ ...this.progress });
|
|
25
|
+
return () => {
|
|
26
|
+
this.listeners = this.listeners.filter(l => l !== listener);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
notify() {
|
|
30
|
+
for (const listener of this.listeners) {
|
|
31
|
+
listener({ ...this.progress });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.ProgressTracker = ProgressTracker;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RestRequestConfig, HttpConfig, ApiResponse } from './types';
|
|
2
|
+
export declare class RequestExecutor {
|
|
3
|
+
private client;
|
|
4
|
+
constructor(httpConfig: HttpConfig);
|
|
5
|
+
/**
|
|
6
|
+
* Выполнение одного запроса с поддержкой retry и таймаута
|
|
7
|
+
*/
|
|
8
|
+
execute<T = any>(command: string, reqConfig?: RestRequestConfig, retryCount?: number, timeoutMs?: number): Promise<ApiResponse<T>>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RequestExecutor = void 0;
|
|
4
|
+
const rest_client_1 = require("./rest-client");
|
|
5
|
+
class RequestExecutor {
|
|
6
|
+
constructor(httpConfig) {
|
|
7
|
+
this.client = (0, rest_client_1.getRestClient)(httpConfig);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Выполнение одного запроса с поддержкой retry и таймаута
|
|
11
|
+
*/
|
|
12
|
+
async execute(command, reqConfig, retryCount = 0, timeoutMs = 10000) {
|
|
13
|
+
let attempt = 0;
|
|
14
|
+
let lastError = null;
|
|
15
|
+
while (attempt <= retryCount) {
|
|
16
|
+
try {
|
|
17
|
+
const result = await Promise.race([
|
|
18
|
+
this.client.request(command, reqConfig),
|
|
19
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeoutMs)),
|
|
20
|
+
]);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
lastError = err;
|
|
25
|
+
attempt++;
|
|
26
|
+
if (attempt > retryCount)
|
|
27
|
+
throw lastError;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw lastError;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.RequestExecutor = RequestExecutor;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HttpConfig, ApiError, ApiResponse, RestRequestConfig } from './types';
|
|
2
|
+
type RestClient = ReturnType<typeof createRestClient>;
|
|
3
|
+
export declare function toApiError(error: unknown): ApiError;
|
|
4
|
+
export declare function createRestClient(config: HttpConfig): {
|
|
5
|
+
request: <T = unknown>(command: string, req?: RestRequestConfig) => Promise<ApiResponse<T>>;
|
|
6
|
+
get: <T = unknown>(command: string, config?: Omit<RestRequestConfig, "method">) => Promise<ApiResponse<T>>;
|
|
7
|
+
post: <T = unknown>(command: string, data?: unknown, config?: Omit<RestRequestConfig, "method" | "data">) => Promise<ApiResponse<T>>;
|
|
8
|
+
put: <T = unknown>(command: string, data?: unknown, config?: Omit<RestRequestConfig, "method" | "data">) => Promise<ApiResponse<T>>;
|
|
9
|
+
delete: <T = unknown>(command: string, config?: Omit<RestRequestConfig, "method">) => Promise<ApiResponse<T>>;
|
|
10
|
+
cancellableRequest: <T = unknown>(key: string, command: string, config?: any) => Promise<ApiResponse<T>>;
|
|
11
|
+
cancelRequest: (key: string) => void;
|
|
12
|
+
};
|
|
13
|
+
export declare function getRestClient(config: HttpConfig): RestClient;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.toApiError = toApiError;
|
|
7
|
+
exports.createRestClient = createRestClient;
|
|
8
|
+
exports.getRestClient = getRestClient;
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
function toApiError(error) {
|
|
11
|
+
var _a;
|
|
12
|
+
if (axios_1.default.isCancel(error)) {
|
|
13
|
+
return {
|
|
14
|
+
message: 'Запрос был отменен',
|
|
15
|
+
code: 'REQUEST_CANCELLED',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
19
|
+
const axiosError = error;
|
|
20
|
+
return {
|
|
21
|
+
message: axiosError.message,
|
|
22
|
+
code: axiosError.code,
|
|
23
|
+
status: (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.status,
|
|
24
|
+
timestamp: new Date(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (error instanceof Error) {
|
|
28
|
+
return {
|
|
29
|
+
message: error.message,
|
|
30
|
+
timestamp: new Date(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
message: 'Произошла неизвестная ошибка',
|
|
35
|
+
timestamp: new Date(),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const restClientCache = new Map();
|
|
39
|
+
function createRestClient(config) {
|
|
40
|
+
const httpClient = axios_1.default.create({
|
|
41
|
+
baseURL: config.baseURL,
|
|
42
|
+
timeout: config.timeout,
|
|
43
|
+
headers: config.headers,
|
|
44
|
+
withCredentials: config.withCredentials,
|
|
45
|
+
});
|
|
46
|
+
// ...реализация rate limit, cache, interceptors, request, etc. (скопировать из rest.ts при необходимости)
|
|
47
|
+
// Для краткости: реализуйте полный функционал по мере необходимости.
|
|
48
|
+
async function request(command, req) {
|
|
49
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
50
|
+
const reqId = (_a = req === null || req === void 0 ? void 0 : req.requestId) !== null && _a !== void 0 ? _a : Math.random().toString(36).slice(2);
|
|
51
|
+
const methodUpper = ((_b = req === null || req === void 0 ? void 0 : req.method) !== null && _b !== void 0 ? _b : 'GET').toUpperCase();
|
|
52
|
+
const fullUrl = `${config.baseURL}${command}`;
|
|
53
|
+
(_d = (_c = config.metrics) === null || _c === void 0 ? void 0 : _c.onRequestStart) === null || _d === void 0 ? void 0 : _d.call(_c, {
|
|
54
|
+
id: reqId,
|
|
55
|
+
method: methodUpper,
|
|
56
|
+
url: fullUrl,
|
|
57
|
+
timestamp: Date.now(),
|
|
58
|
+
requestBody: req === null || req === void 0 ? void 0 : req.data,
|
|
59
|
+
requestParams: req === null || req === void 0 ? void 0 : req.params,
|
|
60
|
+
requestHeaders: req === null || req === void 0 ? void 0 : req.headers,
|
|
61
|
+
});
|
|
62
|
+
const startTs = Date.now();
|
|
63
|
+
try {
|
|
64
|
+
const response = await httpClient.request({
|
|
65
|
+
url: command,
|
|
66
|
+
...req,
|
|
67
|
+
});
|
|
68
|
+
const payload = {
|
|
69
|
+
data: response.data,
|
|
70
|
+
status: response.status,
|
|
71
|
+
statusText: response.statusText,
|
|
72
|
+
headers: response.headers,
|
|
73
|
+
};
|
|
74
|
+
const duration = Date.now() - startTs;
|
|
75
|
+
// --- вычисление размера ответа ---
|
|
76
|
+
let responseBytes = undefined;
|
|
77
|
+
const headers = response.headers;
|
|
78
|
+
const contentLengthHeader = headers['content-length'] || headers['Content-Length'] || undefined;
|
|
79
|
+
const parsedLength = contentLengthHeader ? Number(contentLengthHeader) : undefined;
|
|
80
|
+
if (Number.isFinite(parsedLength) && parsedLength !== 0) {
|
|
81
|
+
responseBytes = parsedLength;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
try {
|
|
85
|
+
const raw = response.data;
|
|
86
|
+
if (typeof raw === 'string') {
|
|
87
|
+
responseBytes = new TextEncoder().encode(raw).length;
|
|
88
|
+
}
|
|
89
|
+
else if (raw !== undefined) {
|
|
90
|
+
const str = JSON.stringify(raw);
|
|
91
|
+
responseBytes = new TextEncoder().encode(str).length;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// ignore sizing errors
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
(_f = (_e = config.metrics) === null || _e === void 0 ? void 0 : _e.onRequestEnd) === null || _f === void 0 ? void 0 : _f.call(_e, {
|
|
99
|
+
id: reqId,
|
|
100
|
+
durationMs: duration,
|
|
101
|
+
status: response.status,
|
|
102
|
+
bytes: responseBytes,
|
|
103
|
+
responseBody: response.data,
|
|
104
|
+
responseHeaders: response.headers,
|
|
105
|
+
});
|
|
106
|
+
return payload;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
const duration = Date.now() - startTs;
|
|
110
|
+
(_h = (_g = config.metrics) === null || _g === void 0 ? void 0 : _g.onRequestEnd) === null || _h === void 0 ? void 0 : _h.call(_g, {
|
|
111
|
+
id: reqId,
|
|
112
|
+
durationMs: duration,
|
|
113
|
+
error: toApiError(error),
|
|
114
|
+
});
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// --- Реализация cancellableRequest ---
|
|
119
|
+
const cancelTokenSources = new Map();
|
|
120
|
+
function cancelRequest(key) {
|
|
121
|
+
const source = cancelTokenSources.get(key);
|
|
122
|
+
if (source) {
|
|
123
|
+
source.cancel(`Запрос отменен по ключу: ${key}`);
|
|
124
|
+
cancelTokenSources.delete(key);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function cancellableRequest(key, command, config) {
|
|
128
|
+
cancelRequest(key);
|
|
129
|
+
const axios = await import('axios');
|
|
130
|
+
const source = axios.default.CancelToken.source();
|
|
131
|
+
cancelTokenSources.set(key, source);
|
|
132
|
+
try {
|
|
133
|
+
return await request(command, {
|
|
134
|
+
...config,
|
|
135
|
+
cancelToken: source.token,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
cancelTokenSources.delete(key);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
request,
|
|
144
|
+
get: (command, config) => request(command, { ...config, method: 'GET' }),
|
|
145
|
+
post: (command, data, config) => request(command, { ...config, method: 'POST', data }),
|
|
146
|
+
put: (command, data, config) => request(command, { ...config, method: 'PUT', data }),
|
|
147
|
+
delete: (command, config) => request(command, { ...config, method: 'DELETE' }),
|
|
148
|
+
cancellableRequest,
|
|
149
|
+
cancelRequest,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function getRestClient(config) {
|
|
153
|
+
var _a, _b;
|
|
154
|
+
const key = JSON.stringify({
|
|
155
|
+
baseURL: config.baseURL,
|
|
156
|
+
timeout: config.timeout,
|
|
157
|
+
withCredentials: config.withCredentials,
|
|
158
|
+
headers: (_a = config.headers) !== null && _a !== void 0 ? _a : {},
|
|
159
|
+
retry: (_b = config.retry) !== null && _b !== void 0 ? _b : {},
|
|
160
|
+
metrics: !!config.metrics,
|
|
161
|
+
});
|
|
162
|
+
const cachedClient = restClientCache.get(key);
|
|
163
|
+
if (cachedClient)
|
|
164
|
+
return cachedClient;
|
|
165
|
+
const client = createRestClient(config);
|
|
166
|
+
restClientCache.set(key, client);
|
|
167
|
+
return client;
|
|
168
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export interface RetryConfig {
|
|
2
|
+
attempts: number;
|
|
3
|
+
delayMs: number;
|
|
4
|
+
backoffMultiplier: number;
|
|
5
|
+
retriableStatus?: number[];
|
|
6
|
+
}
|
|
7
|
+
export type RetryOptions = Partial<RetryConfig>;
|
|
8
|
+
export interface CacheConfig {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
ttlMs: number;
|
|
11
|
+
}
|
|
12
|
+
export interface RateLimitConfig {
|
|
13
|
+
maxConcurrent?: number;
|
|
14
|
+
maxRequestsPerInterval?: number;
|
|
15
|
+
intervalMs?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface MetricsHandler {
|
|
18
|
+
onRequestStart?: (info: {
|
|
19
|
+
id: string;
|
|
20
|
+
method?: string;
|
|
21
|
+
url?: string;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
requestBody?: unknown;
|
|
24
|
+
requestParams?: unknown;
|
|
25
|
+
requestHeaders?: Record<string, string>;
|
|
26
|
+
}) => void;
|
|
27
|
+
onRequestEnd?: (info: {
|
|
28
|
+
id: string;
|
|
29
|
+
durationMs: number;
|
|
30
|
+
status?: number;
|
|
31
|
+
error?: ApiError;
|
|
32
|
+
bytes?: number;
|
|
33
|
+
responseBody?: unknown;
|
|
34
|
+
responseHeaders?: Record<string, string>;
|
|
35
|
+
}) => void;
|
|
36
|
+
}
|
|
37
|
+
export interface HttpConfig {
|
|
38
|
+
baseURL: string;
|
|
39
|
+
timeout?: number;
|
|
40
|
+
headers?: Record<string, string>;
|
|
41
|
+
withCredentials?: boolean;
|
|
42
|
+
retry?: RetryOptions;
|
|
43
|
+
cache?: CacheConfig;
|
|
44
|
+
rateLimit?: RateLimitConfig;
|
|
45
|
+
metrics?: MetricsHandler;
|
|
46
|
+
}
|
|
47
|
+
export interface ApiError {
|
|
48
|
+
message: string;
|
|
49
|
+
code?: string | number;
|
|
50
|
+
status?: number;
|
|
51
|
+
timestamp?: Date;
|
|
52
|
+
}
|
|
53
|
+
export interface ApiResponse<T = unknown> {
|
|
54
|
+
data: T;
|
|
55
|
+
status: number;
|
|
56
|
+
statusText: string;
|
|
57
|
+
headers: Record<string, string>;
|
|
58
|
+
}
|
|
59
|
+
export type RestRequestConfig = import('axios').AxiosRequestConfig & {
|
|
60
|
+
useCache?: boolean;
|
|
61
|
+
cacheTtlMs?: number;
|
|
62
|
+
cacheKey?: string;
|
|
63
|
+
skipRateLimit?: boolean;
|
|
64
|
+
requestId?: string;
|
|
65
|
+
};
|
|
66
|
+
export type PipelineStageConfig<Input, Output> = {
|
|
67
|
+
key: string;
|
|
68
|
+
request: (input: Input, allResults?: any) => Promise<Output>;
|
|
69
|
+
condition?: (input: Input, prevResults: any, sharedData?: Record<string, any>) => boolean;
|
|
70
|
+
retryCount?: number;
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
errorHandler?: (error: any, stageKey: string, sharedData?: Record<string, any>) => any;
|
|
73
|
+
};
|
|
74
|
+
export type PipelineConfig = {
|
|
75
|
+
stages: PipelineStageConfig<any, any>[];
|
|
76
|
+
};
|
|
77
|
+
export type PipelineProgress = {
|
|
78
|
+
currentStage: number;
|
|
79
|
+
totalStages: number;
|
|
80
|
+
stageStatuses: Array<'pending' | 'in-progress' | 'success' | 'error' | 'skipped'>;
|
|
81
|
+
};
|
|
82
|
+
export type PipelineResult = {
|
|
83
|
+
results: any[];
|
|
84
|
+
errors: any[];
|
|
85
|
+
success: boolean;
|
|
86
|
+
};
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rest-pipeline-js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Pipeline Orchestration Utilities for JavaScript REST API Clients",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/index.js",
|
|
6
6
|
"types": "src/types.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|