@pipeline-builder/api-core 3.1.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/LICENSE +202 -0
- package/README.md +51 -0
- package/lib/constants/ai-providers.d.ts +41 -0
- package/lib/constants/ai-providers.js +88 -0
- package/lib/constants/http-status.d.ts +24 -0
- package/lib/constants/http-status.js +29 -0
- package/lib/constants/index.d.ts +3 -0
- package/lib/constants/index.js +22 -0
- package/lib/constants/time.d.ts +10 -0
- package/lib/constants/time.js +16 -0
- package/lib/errors/app-errors.d.ts +30 -0
- package/lib/errors/app-errors.js +62 -0
- package/lib/errors/index.d.ts +1 -0
- package/lib/errors/index.js +20 -0
- package/lib/helpers/access-helpers.d.ts +40 -0
- package/lib/helpers/access-helpers.js +56 -0
- package/lib/helpers/crud-helpers.d.ts +16 -0
- package/lib/helpers/crud-helpers.js +34 -0
- package/lib/helpers/index.d.ts +4 -0
- package/lib/helpers/index.js +23 -0
- package/lib/helpers/mask-helpers.d.ts +33 -0
- package/lib/helpers/mask-helpers.js +54 -0
- package/lib/helpers/sse-helpers.d.ts +13 -0
- package/lib/helpers/sse-helpers.js +40 -0
- package/lib/index.d.ts +57 -0
- package/lib/index.js +86 -0
- package/lib/middleware/auth.d.ts +50 -0
- package/lib/middleware/auth.js +171 -0
- package/lib/middleware/index.d.ts +1 -0
- package/lib/middleware/index.js +20 -0
- package/lib/openapi/extend-zod.d.ts +1 -0
- package/lib/openapi/extend-zod.js +8 -0
- package/lib/openapi/index.d.ts +2 -0
- package/lib/openapi/index.js +10 -0
- package/lib/openapi/registry.d.ts +17 -0
- package/lib/openapi/registry.js +42 -0
- package/lib/openapi/routes/billing-routes.d.ts +1 -0
- package/lib/openapi/routes/billing-routes.js +69 -0
- package/lib/openapi/routes/index.d.ts +5 -0
- package/lib/openapi/routes/index.js +22 -0
- package/lib/openapi/routes/message-routes.d.ts +1 -0
- package/lib/openapi/routes/message-routes.js +108 -0
- package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
- package/lib/openapi/routes/pipeline-routes.js +90 -0
- package/lib/openapi/routes/plugin-routes.d.ts +1 -0
- package/lib/openapi/routes/plugin-routes.js +99 -0
- package/lib/openapi/routes/quota-routes.d.ts +1 -0
- package/lib/openapi/routes/quota-routes.js +65 -0
- package/lib/openapi/schema-registry.d.ts +25 -0
- package/lib/openapi/schema-registry.js +95 -0
- package/lib/routes/health.d.ts +47 -0
- package/lib/routes/health.js +81 -0
- package/lib/routes/index.d.ts +1 -0
- package/lib/routes/index.js +20 -0
- package/lib/services/admin-audit.d.ts +13 -0
- package/lib/services/admin-audit.js +31 -0
- package/lib/services/cache-service.d.ts +108 -0
- package/lib/services/cache-service.js +212 -0
- package/lib/services/compliance-client.d.ts +46 -0
- package/lib/services/compliance-client.js +102 -0
- package/lib/services/compliance-event-subscriber.d.ts +11 -0
- package/lib/services/compliance-event-subscriber.js +60 -0
- package/lib/services/compliance-queue.d.ts +11 -0
- package/lib/services/compliance-queue.js +38 -0
- package/lib/services/entity-events.d.ts +44 -0
- package/lib/services/entity-events.js +63 -0
- package/lib/services/http-client.d.ts +108 -0
- package/lib/services/http-client.js +285 -0
- package/lib/services/index.d.ts +10 -0
- package/lib/services/index.js +40 -0
- package/lib/services/quota.d.ts +59 -0
- package/lib/services/quota.js +137 -0
- package/lib/services/retry-strategy.d.ts +74 -0
- package/lib/services/retry-strategy.js +127 -0
- package/lib/types/billing.d.ts +47 -0
- package/lib/types/billing.js +5 -0
- package/lib/types/common.d.ts +161 -0
- package/lib/types/common.js +53 -0
- package/lib/types/error-codes.d.ts +38 -0
- package/lib/types/error-codes.js +77 -0
- package/lib/types/feature-flags.d.ts +38 -0
- package/lib/types/feature-flags.js +107 -0
- package/lib/types/http.d.ts +37 -0
- package/lib/types/http.js +5 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/index.js +26 -0
- package/lib/types/pipeline.d.ts +70 -0
- package/lib/types/pipeline.js +44 -0
- package/lib/types/quota-tiers.d.ts +23 -0
- package/lib/types/quota-tiers.js +26 -0
- package/lib/utils/alias-resolver.d.ts +16 -0
- package/lib/utils/alias-resolver.js +49 -0
- package/lib/utils/headers.d.ts +18 -0
- package/lib/utils/headers.js +24 -0
- package/lib/utils/identity.d.ts +61 -0
- package/lib/utils/identity.js +75 -0
- package/lib/utils/index.d.ts +7 -0
- package/lib/utils/index.js +26 -0
- package/lib/utils/logger.d.ts +28 -0
- package/lib/utils/logger.js +77 -0
- package/lib/utils/object.d.ts +13 -0
- package/lib/utils/object.js +21 -0
- package/lib/utils/params.d.ts +89 -0
- package/lib/utils/params.js +148 -0
- package/lib/utils/response.d.ts +142 -0
- package/lib/utils/response.js +237 -0
- package/lib/validation/ai-schemas.d.ts +61 -0
- package/lib/validation/ai-schemas.js +81 -0
- package/lib/validation/common-schemas.d.ts +72 -0
- package/lib/validation/common-schemas.js +58 -0
- package/lib/validation/index.d.ts +6 -0
- package/lib/validation/index.js +25 -0
- package/lib/validation/message-schemas.d.ts +79 -0
- package/lib/validation/message-schemas.js +42 -0
- package/lib/validation/middleware.d.ts +60 -0
- package/lib/validation/middleware.js +77 -0
- package/lib/validation/pipeline-schemas.d.ts +135 -0
- package/lib/validation/pipeline-schemas.js +85 -0
- package/lib/validation/plugin-schemas.d.ts +127 -0
- package/lib/validation/plugin-schemas.js +84 -0
- package/openapi.yaml +292 -0
- package/package.json +127 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { QuotaType, QuotaCheckResult } from '../types/common';
|
|
2
|
+
/**
|
|
3
|
+
* Quota service client interface.
|
|
4
|
+
*/
|
|
5
|
+
export interface QuotaService {
|
|
6
|
+
/** Check if quota is available (fail-open on error). */
|
|
7
|
+
check(orgId: string, quotaType: QuotaType, authHeader: string, requestId?: string): Promise<QuotaCheckResult>;
|
|
8
|
+
/** Increment quota usage. Returns a promise so callers can optionally handle errors. */
|
|
9
|
+
increment(orgId: string, quotaType: QuotaType, authHeader: string, amount?: number, requestId?: string): Promise<void>;
|
|
10
|
+
/** Update quota limits. Returns true on success. */
|
|
11
|
+
updateLimits(orgId: string, limits: Partial<Record<QuotaType, number>>, authHeader: string, requestId?: string): Promise<boolean>;
|
|
12
|
+
/** Reset quota usage. Returns true on success. */
|
|
13
|
+
reset(orgId: string, quotaType?: QuotaType, authHeader?: string, requestId?: string): Promise<boolean>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Configuration for quota service client.
|
|
17
|
+
*/
|
|
18
|
+
export interface QuotaServiceConfig {
|
|
19
|
+
/** Quota service host (default: env QUOTA_SERVICE_HOST or 'quota') */
|
|
20
|
+
host?: string;
|
|
21
|
+
/** Quota service port (default: env QUOTA_SERVICE_PORT or 3000) */
|
|
22
|
+
port?: number;
|
|
23
|
+
/** Request timeout in milliseconds (default: 5000) */
|
|
24
|
+
timeout?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a quota service client.
|
|
28
|
+
*
|
|
29
|
+
* @param config - Optional service configuration
|
|
30
|
+
* @returns Quota service client
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const quotaService = createQuotaService();
|
|
35
|
+
*
|
|
36
|
+
* // Check quota before processing
|
|
37
|
+
* const quota = await quotaService.check(orgId, 'apiCalls', authHeader);
|
|
38
|
+
* if (!quota.allowed) {
|
|
39
|
+
* return res.status(429).json({ error: 'Quota exceeded' });
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* // Increment quota after success
|
|
43
|
+
* quotaService.increment(orgId, 'apiCalls', authHeader).catch(err => logger.warn('Quota increment failed', { error: err }));
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function createQuotaService(config?: QuotaServiceConfig): QuotaService;
|
|
47
|
+
/**
|
|
48
|
+
* Fire-and-forget quota increment with standardized error logging.
|
|
49
|
+
*
|
|
50
|
+
* Wraps `quotaService.increment()` with a `.catch()` that logs a warning.
|
|
51
|
+
* Eliminates the identical one-liner repeated across every read route.
|
|
52
|
+
*
|
|
53
|
+
* @param quotaService - Quota service client
|
|
54
|
+
* @param orgId - Organization ID
|
|
55
|
+
* @param quotaType - Quota type to increment
|
|
56
|
+
* @param authHeader - Authorization header value
|
|
57
|
+
* @param logWarn - Logging function for warnings
|
|
58
|
+
*/
|
|
59
|
+
export declare function incrementQuota(quotaService: QuotaService, orgId: string, quotaType: QuotaType, authHeader: string, logWarn: (message: string, data?: unknown) => void): void;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.createQuotaService = createQuotaService;
|
|
6
|
+
exports.incrementQuota = incrementQuota;
|
|
7
|
+
const http_client_1 = require("./http-client");
|
|
8
|
+
const logger_1 = require("../utils/logger");
|
|
9
|
+
/** Retry options for quota calls — fail fast since quota is fail-open. */
|
|
10
|
+
const QUOTA_REQUEST_OPTIONS = {
|
|
11
|
+
maxRateLimitRetries: 1,
|
|
12
|
+
maxRetries: 1,
|
|
13
|
+
};
|
|
14
|
+
const logger = (0, logger_1.createLogger)('quota');
|
|
15
|
+
/**
|
|
16
|
+
* Create a fail-open quota result (allows the request).
|
|
17
|
+
*/
|
|
18
|
+
function createFailOpenResult() {
|
|
19
|
+
return {
|
|
20
|
+
allowed: true,
|
|
21
|
+
limit: -1,
|
|
22
|
+
used: 0,
|
|
23
|
+
remaining: -1,
|
|
24
|
+
resetAt: new Date().toISOString(),
|
|
25
|
+
unlimited: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build common request headers with optional request ID for distributed tracing.
|
|
30
|
+
*/
|
|
31
|
+
function buildHeaders(orgId, authHeader, requestId) {
|
|
32
|
+
const headers = { 'x-org-id': orgId };
|
|
33
|
+
if (authHeader)
|
|
34
|
+
headers.Authorization = authHeader;
|
|
35
|
+
if (requestId)
|
|
36
|
+
headers['X-Request-Id'] = requestId;
|
|
37
|
+
return headers;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create a quota service client.
|
|
41
|
+
*
|
|
42
|
+
* @param config - Optional service configuration
|
|
43
|
+
* @returns Quota service client
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const quotaService = createQuotaService();
|
|
48
|
+
*
|
|
49
|
+
* // Check quota before processing
|
|
50
|
+
* const quota = await quotaService.check(orgId, 'apiCalls', authHeader);
|
|
51
|
+
* if (!quota.allowed) {
|
|
52
|
+
* return res.status(429).json({ error: 'Quota exceeded' });
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* // Increment quota after success
|
|
56
|
+
* quotaService.increment(orgId, 'apiCalls', authHeader).catch(err => logger.warn('Quota increment failed', { error: err }));
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
function createQuotaService(config = {}) {
|
|
60
|
+
const serviceConfig = {
|
|
61
|
+
host: config.host ?? process.env.QUOTA_SERVICE_HOST ?? 'quota',
|
|
62
|
+
port: config.port ?? parseInt(process.env.QUOTA_SERVICE_PORT ?? '3000', 10),
|
|
63
|
+
timeout: config.timeout ?? 5000,
|
|
64
|
+
};
|
|
65
|
+
const client = (0, http_client_1.createSafeClient)(serviceConfig);
|
|
66
|
+
return {
|
|
67
|
+
async check(orgId, quotaType, authHeader, requestId) {
|
|
68
|
+
const path = `/quotas/${encodeURIComponent(orgId)}/${encodeURIComponent(quotaType)}`;
|
|
69
|
+
const response = await client.get(path, { headers: buildHeaders(orgId, authHeader, requestId), ...QUOTA_REQUEST_OPTIONS });
|
|
70
|
+
if (!response) {
|
|
71
|
+
logger.warn('QUOTA_FAIL_OPEN: Quota service unreachable, allowing request', { orgId, quotaType });
|
|
72
|
+
return createFailOpenResult();
|
|
73
|
+
}
|
|
74
|
+
if (response.statusCode !== 200 || !response.body.success || !response.body.data?.status) {
|
|
75
|
+
logger.warn('QUOTA_FAIL_OPEN: Quota check returned non-ok, allowing request', {
|
|
76
|
+
orgId, quotaType, statusCode: response.statusCode, message: response.body.message,
|
|
77
|
+
});
|
|
78
|
+
return createFailOpenResult();
|
|
79
|
+
}
|
|
80
|
+
return response.body.data.status;
|
|
81
|
+
},
|
|
82
|
+
async increment(orgId, quotaType, authHeader, amount = 1, requestId) {
|
|
83
|
+
const path = `/quotas/${encodeURIComponent(orgId)}/increment`;
|
|
84
|
+
const response = await client
|
|
85
|
+
.post(path, { quotaType, amount }, { headers: buildHeaders(orgId, authHeader, requestId), ...QUOTA_REQUEST_OPTIONS });
|
|
86
|
+
if (!response || response.statusCode !== 200) {
|
|
87
|
+
logger.warn('Failed to increment quota', {
|
|
88
|
+
orgId, quotaType, amount, statusCode: response?.statusCode,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
logger.debug('Quota incremented', { orgId, quotaType, amount });
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
async updateLimits(orgId, limits, authHeader, requestId) {
|
|
96
|
+
const path = `/quotas/${encodeURIComponent(orgId)}`;
|
|
97
|
+
const response = await client.put(path, limits, { headers: buildHeaders(orgId, authHeader, requestId) });
|
|
98
|
+
if (!response || response.statusCode !== 200) {
|
|
99
|
+
logger.warn('Failed to update quota limits', {
|
|
100
|
+
orgId, limits, statusCode: response?.statusCode,
|
|
101
|
+
});
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
logger.info('Quota limits updated', { orgId, limits });
|
|
105
|
+
return true;
|
|
106
|
+
},
|
|
107
|
+
async reset(orgId, quotaType, authHeader, requestId) {
|
|
108
|
+
const path = `/quotas/${encodeURIComponent(orgId)}/reset`;
|
|
109
|
+
const body = quotaType ? { quotaType } : {};
|
|
110
|
+
const response = await client.post(path, body, { headers: buildHeaders(orgId, authHeader ?? '', requestId) });
|
|
111
|
+
if (!response || response.statusCode !== 200) {
|
|
112
|
+
logger.warn('Failed to reset quota', {
|
|
113
|
+
orgId, quotaType, statusCode: response?.statusCode,
|
|
114
|
+
});
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
logger.info('Quota reset', { orgId, quotaType: quotaType ?? 'all' });
|
|
118
|
+
return true;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Fire-and-forget quota increment with standardized error logging.
|
|
124
|
+
*
|
|
125
|
+
* Wraps `quotaService.increment()` with a `.catch()` that logs a warning.
|
|
126
|
+
* Eliminates the identical one-liner repeated across every read route.
|
|
127
|
+
*
|
|
128
|
+
* @param quotaService - Quota service client
|
|
129
|
+
* @param orgId - Organization ID
|
|
130
|
+
* @param quotaType - Quota type to increment
|
|
131
|
+
* @param authHeader - Authorization header value
|
|
132
|
+
* @param logWarn - Logging function for warnings
|
|
133
|
+
*/
|
|
134
|
+
function incrementQuota(quotaService, orgId, quotaType, authHeader, logWarn) {
|
|
135
|
+
quotaService.increment(orgId, quotaType, authHeader).catch((err) => logWarn('Quota increment failed', { error: err instanceof Error ? err.message : String(err) }));
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVvdGEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvcXVvdGEudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7O0FBb0Z0QyxnREF1RkM7QUFjRCx3Q0FVQztBQWpNRCwrQ0FBaUU7QUFFakUsNENBQStDO0FBRS9DLDBFQUEwRTtBQUMxRSxNQUFNLHFCQUFxQixHQUErRDtJQUN4RixtQkFBbUIsRUFBRSxDQUFDO0lBQ3RCLFVBQVUsRUFBRSxDQUFDO0NBQ2QsQ0FBQztBQUVGLE1BQU0sTUFBTSxHQUFHLElBQUEscUJBQVksRUFBQyxPQUFPLENBQUMsQ0FBQztBQTRCckM7O0dBRUc7QUFDSCxTQUFTLG9CQUFvQjtJQUMzQixPQUFPO1FBQ0wsT0FBTyxFQUFFLElBQUk7UUFDYixLQUFLLEVBQUUsQ0FBQyxDQUFDO1FBQ1QsSUFBSSxFQUFFLENBQUM7UUFDUCxTQUFTLEVBQUUsQ0FBQyxDQUFDO1FBQ2IsT0FBTyxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFO1FBQ2pDLFNBQVMsRUFBRSxJQUFJO0tBQ2hCLENBQUM7QUFDSixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxTQUFTLFlBQVksQ0FBQyxLQUFhLEVBQUUsVUFBbUIsRUFBRSxTQUFrQjtJQUMxRSxNQUFNLE9BQU8sR0FBMkIsRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDOUQsSUFBSSxVQUFVO1FBQUUsT0FBTyxDQUFDLGFBQWEsR0FBRyxVQUFVLENBQUM7SUFDbkQsSUFBSSxTQUFTO1FBQUUsT0FBTyxDQUFDLGNBQWMsQ0FBQyxHQUFHLFNBQVMsQ0FBQztJQUNuRCxPQUFPLE9BQU8sQ0FBQztBQUNqQixDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFDSCxTQUFnQixrQkFBa0IsQ0FBQyxTQUE2QixFQUFFO0lBQ2hFLE1BQU0sYUFBYSxHQUFrQjtRQUNuQyxJQUFJLEVBQUUs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LEVBQUU7b0JBQ25DLEtBQUssRUFBRSxTQUFTLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxVQUFVO2lCQUNuRCxDQUFDLENBQUM7Z0JBQ0gsT0FBTyxLQUFLLENBQUM7WUFDZixDQUFDO1lBRUQsTUFBTSxDQUFDLElBQUksQ0FBQyxhQUFhLEVBQUUsRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLFNBQVMsSUFBSSxLQUFLLEVBQUUsQ0FBQyxDQUFDO1lBQ3JFLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztLQUNGLENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxTQUFnQixjQUFjLENBQzVCLFlBQTBCLEVBQzFCLEtBQWEsRUFDYixTQUFvQixFQUNwQixVQUFrQixFQUNsQixPQUFrRDtJQUVsRCxZQUFZLENBQUMsU0FBUyxDQUFDLEtBQUssRUFBRSxTQUFTLEVBQUUsVUFBVSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBWSxFQUFFLEVBQUUsQ0FDMUUsT0FBTyxDQUFDLHdCQUF3QixFQUFFLEVBQUUsS0FBSyxFQUFFLEdBQUcsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQy9GLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IGNyZWF0ZVNhZmVDbGllbnQsIFJlcXVlc3RPcHRpb25zIH0gZnJvbSAnLi9odHRwLWNsaWVudCc7XG5pbXBvcnQgeyBRdW90YVR5cGUsIFF1b3RhQ2hlY2tSZXN1bHQsIFNlcnZpY2VDb25maWcgfSBmcm9tICcuLi90eXBlcy9jb21tb24nO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyJztcblxuLyoqIFJldHJ5IG9wdGlvbnMgZm9yIHF1b3RhIGNhbGxzIOKAlCBmYWlsIGZhc3Qgc2luY2UgcXVvdGEgaXMgZmFpbC1vcGVuLiAqL1xuY29uc3QgUVVPVEFfUkVRVUVTVF9PUFRJT05TOiBQaWNrPFJlcXVlc3RPcHRpb25zLCAnbWF4UmF0ZUxpbWl0UmV0cmllcycgfCAnbWF4UmV0cmllcyc+ID0ge1xuICBtYXhSYXRlTGltaXRSZXRyaWVzOiAxLFxuICBtYXhSZXRyaWVzOiAxLFxufTtcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdxdW90YScpO1xuXG4vKipcbiAqIFF1b3RhIHNlcnZpY2UgY2xpZW50IGludGVyZmFjZS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBRdW90YVNlcnZpY2Uge1xuICAvKiogQ2hlY2sgaWYgcXVvdGEgaXMgYXZhaWxhYmxlIChmYWlsLW9wZW4gb24gZXJyb3IpLiAqL1xuICBjaGVjayhvcmdJZDogc3RyaW5nLCBxdW90YVR5cGU6IFF1b3RhVHlwZSwgYXV0aEhlYWRlcjogc3RyaW5nLCByZXF1ZXN0SWQ/OiBzdHJpbmcpOiBQcm9taXNlPFF1b3RhQ2hlY2tSZXN1bHQ+O1xuICAvKiogSW5jcmVtZW50IHF1b3RhIHVzYWdlLiBSZXR1cm5zIGEgcHJvbWlzZSBzbyBjYWxsZXJzIGNhbiBvcHRpb25hbGx5IGhhbmRsZSBlcnJvcnMuICovXG4gIGluY3JlbWVudChvcmdJZDogc3RyaW5nLCBxdW90YVR5cGU6IFF1b3RhVHlwZSwgYXV0aEhlYWRlcjogc3RyaW5nLCBhbW91bnQ/OiBudW1iZXIsIHJlcXVlc3RJZD86IHN0cmluZyk6IFByb21pc2U8dm9pZD47XG4gIC8qKiBVcGRhdGUgcXVvdGEgbGltaXRzLiBSZXR1cm5zIHRydWUgb24gc3VjY2Vzcy4gKi9cbiAgdXBkYXRlTGltaXRzKG9yZ0lkOiBzdHJpbmcsIGxpbWl0czogUGFydGlhbDxSZWNvcmQ8UXVvdGFUeXBlLCBudW1iZXI+PiwgYXV0aEhlYWRlcjogc3RyaW5nLCByZXF1ZXN0SWQ/OiBzdHJpbmcpOiBQcm9taXNlPGJvb2xlYW4+O1xuICAvKiogUmVzZXQgcXVvdGEgdXNhZ2UuIFJldHVybnMgdHJ1ZSBvbiBzdWNjZXNzLiAqL1xuICByZXNldChvcmdJZDogc3RyaW5nLCBxdW90YVR5cGU/OiBRdW90YVR5cGUsIGF1dGhIZWFkZXI/OiBzdHJpbmcsIHJlcXVlc3RJZD86IHN0cmluZyk6IFByb21pc2U8Ym9vbGVhbj47XG59XG5cbi8qKlxuICogQ29uZmlndXJhdGlvbiBmb3IgcXVvdGEgc2VydmljZSBjbGllbnQuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUXVvdGFTZXJ2aWNlQ29uZmlnIHtcbiAgLyoqIFF1b3RhIHNlcnZpY2UgaG9zdCAoZGVmYXVsdDogZW52IFFVT1RBX1NFUlZJQ0VfSE9TVCBvciAncXVvdGEnKSAqL1xuICBob3N0Pzogc3RyaW5nO1xuICAvKiogUXVvdGEgc2VydmljZSBwb3J0IChkZWZhdWx0OiBlbnYgUVVPVEFfU0VSVklDRV9QT1JUIG9yIDMwMDApICovXG4gIHBvcnQ/OiBudW1iZXI7XG4gIC8qKiBSZXF1ZXN0IHRpbWVvdXQgaW4gbWlsbGlzZWNvbmRzIChkZWZhdWx0OiA1MDAwKSAqL1xuICB0aW1lb3V0PzogbnVtYmVyO1xufVxuXG4vKipcbiAqIENyZWF0ZSBhIGZhaWwtb3BlbiBxdW90YSByZXN1bHQgKGFsbG93cyB0aGUgcmVxdWVzdCkuXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZUZhaWxPcGVuUmVzdWx0KCk6IFF1b3RhQ2hlY2tSZXN1bHQge1xuICByZXR1cm4ge1xuICAgIGFsbG93ZWQ6IHRydWUsXG4gICAgbGltaXQ6IC0xLFxuICAgIHVzZWQ6IDAsXG4gICAgcmVtYWluaW5nOiAtMSxcbiAgICByZXNldEF0OiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gICAgdW5saW1pdGVkOiB0cnVlLFxuICB9O1xufVxuXG4vKipcbiAqIEJ1aWxkIGNvbW1vbiByZXF1ZXN0IGhlYWRlcnMgd2l0aCBvcHRpb25hbCByZXF1ZXN0IElEIGZvciBkaXN0cmlidXRlZCB0cmFjaW5nLlxuICovXG5mdW5jdGlvbiBidWlsZEhlYWRlcnMob3JnSWQ6IHN0cmluZywgYXV0aEhlYWRlcj86IHN0cmluZywgcmVxdWVzdElkPzogc3RyaW5nKTogUmVjb3JkPHN0cmluZywgc3RyaW5nPiB7XG4gIGNvbnN0IGhlYWRlcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7ICd4LW9yZy1pZCc6IG9yZ0lkIH07XG4gIGlmIChhdXRoSGVhZGVyKSBoZWFkZXJzLkF1dGhvcml6YXRpb24gPSBhdXRoSGVhZGVyO1xuICBpZiAocmVxdWVzdElkKSBoZWFkZXJzWydYLVJlcXVlc3QtSWQnXSA9IHJlcXVlc3RJZDtcbiAgcmV0dXJuIGhlYWRlcnM7XG59XG5cbi8qKlxuICogQ3JlYXRlIGEgcXVvdGEgc2VydmljZSBjbGllbnQuXG4gKlxuICogQHBhcmFtIGNvbmZpZyAtIE9wdGlvbmFsIHNlcnZpY2UgY29uZmlndXJhdGlvblxuICogQHJldHVybnMgUXVvdGEgc2VydmljZSBjbGllbnRcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogY29uc3QgcXVvdGFTZXJ2aWNlID0gY3JlYXRlUXVvdGFTZXJ2aWNlKCk7XG4gKlxuICogLy8gQ2hlY2sgcXVvdGEgYmVmb3JlIHByb2Nlc3NpbmdcbiAqIGNvbnN0IHF1b3RhID0gYXdhaXQgcXVvdGFTZXJ2aWNlLmNoZWNrKG9yZ0lkLCAnYXBpQ2FsbHMnLCBhdXRoSGVhZGVyKTtcbiAqIGlmICghcXVvdGEuYWxsb3dlZCkge1xuICogICByZXR1cm4gcmVzLnN0YXR1cyg0MjkpLmpzb24oeyBlcnJvcjogJ1F1b3RhIGV4Y2VlZGVkJyB9KTtcbiAqIH1cbiAqXG4gKiAvLyBJbmNyZW1lbnQgcXVvdGEgYWZ0ZXIgc3VjY2Vzc1xuICogcXVvdGFTZXJ2aWNlLmluY3JlbWVudChvcmdJZCwgJ2FwaUNhbGxzJywgYXV0aEhlYWRlcikuY2F0Y2goZXJyID0+IGxvZ2dlci53YXJuKCdRdW90YSBpbmNyZW1lbnQgZmFpbGVkJywgeyBlcnJvcjogZXJyIH0pKTtcbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlUXVvdGFTZXJ2aWNlKGNvbmZpZzogUXVvdGFTZXJ2aWNlQ29uZmlnID0ge30pOiBRdW90YVNlcnZpY2Uge1xuICBjb25zdCBzZXJ2aWNlQ29uZmlnOiBTZXJ2aWNlQ29uZmlnID0ge1xuICAgIGhvc3Q6IGNvbmZpZy5ob3N0ID8/IHByb2Nlc3MuZW52LlFVT1RBX1NFUlZJQ0VfSE9TVCA/PyAncXVvdGEnLFxuICAgIHBvcnQ6IGNvbmZpZy5wb3J0ID8/IHBhcnNlSW50KHByb2Nlc3MuZW52LlFVT1RBX1NFUlZJQ0VfUE9SVCA/PyAnMzAwMCcsIDEwKSxcbiAgICB0aW1lb3V0OiBjb25maWcudGltZW91dCA/PyA1MDAwLFxuICB9O1xuXG4gIGNvbnN0IGNsaWVudCA9IGNyZWF0ZVNhZmVDbGllbnQoc2VydmljZUNvbmZpZyk7XG5cbiAgcmV0dXJuIHtcbiAgICBhc3luYyBjaGVjayhvcmdJZDogc3RyaW5nLCBxdW90YVR5cGU6IFF1b3RhVHlwZSwgYXV0aEhlYWRlcjogc3RyaW5nLCByZXF1ZXN0SWQ/OiBzdHJpbmcpOiBQcm9taXNlPFF1b3RhQ2hlY2tSZXN1bHQ+IHtcbiAgICAgIGNvbnN0IHBhdGggPSBgL3F1b3Rhcy8ke2VuY29kZVVSSUNvbXBvbmVudChvcmdJZCl9LyR7ZW5jb2RlVVJJQ29tcG9uZW50KHF1b3RhVHlwZSl9YDtcblxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBjbGllbnQuZ2V0PHtcbiAgICAgICAgc3VjY2VzczogYm9vbGVhbjtcbiAgICAgICAgZGF0YT86IHsgcXVvdGFUeXBlOiBzdHJpbmc7IHN0YXR1czogUXVvdGFDaGVja1Jlc3VsdCB9O1xuICAgICAgICBtZXNzYWdlPzogc3RyaW5nO1xuICAgICAgfT4ocGF0aCwgeyBoZWFkZXJzOiBidWlsZEhlYWRlcnMob3JnSWQsIGF1dGhIZWFkZXIsIHJlcXVlc3RJZCksIC4uLlFVT1RBX1JFUVVFU1RfT1BUSU9OUyB9KTtcblxuICAgICAgaWYgKCFyZXNwb25zZSkge1xuICAgICAgICBsb2dnZXIud2FybignUVVPVEFfRkFJTF9PUEVOOiBRdW90YSBzZXJ2aWNlIHVucmVhY2hhYmxlLCBhbGxvd2luZyByZXF1ZXN0JywgeyBvcmdJZCwgcXVvdGFUeXBlIH0pO1xuICAgICAgICByZXR1cm4gY3JlYXRlRmFpbE9wZW5SZXN1bHQoKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHJlc3BvbnNlLnN0YXR1c0NvZGUgIT09IDIwMCB8fCAhcmVzcG9uc2UuYm9keS5zdWNjZXNzIHx8ICFyZXNwb25zZS5ib2R5LmRhdGE/LnN0YXR1cykge1xuICAgICAgICBsb2dnZXIud2FybignUVVPVEFfRkFJTF9PUEVOOiBRdW90YSBjaGVjayByZXR1cm5lZCBub24tb2ssIGFsbG93aW5nIHJlcXVlc3QnLCB7XG4gICAgICAgICAgb3JnSWQsIHF1b3RhVHlwZSwgc3RhdHVzQ29kZTogcmVzcG9uc2Uuc3RhdHVzQ29kZSwgbWVzc2FnZTogcmVzcG9uc2UuYm9keS5tZXNzYWdlLFxuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuIGNyZWF0ZUZhaWxPcGVuUmVzdWx0KCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiByZXNwb25zZS5ib2R5LmRhdGEuc3RhdHVzO1xuICAgIH0sXG5cbiAgICBhc3luYyBpbmNyZW1lbnQob3JnSWQ6IHN0cmluZywgcXVvdGFUeXBlOiBRdW90YVR5cGUsIGF1dGhIZWFkZXI6IHN0cmluZywgYW1vdW50OiBudW1iZXIgPSAxLCByZXF1ZXN0SWQ/OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgIGNvbnN0IHBhdGggPSBgL3F1b3Rhcy8ke2VuY29kZVVSSUNvbXBvbmVudChvcmdJZCl9L2luY3JlbWVudGA7XG5cbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgY2xpZW50XG4gICAgICAgIC5wb3N0KHBhdGgsIHsgcXVvdGFUeXBlLCBhbW91bnQgfSwgeyBoZWFkZXJzOiBidWlsZEhlYWRlcnMob3JnSWQsIGF1dGhIZWFkZXIsIHJlcXVlc3RJZCksIC4uLlFVT1RBX1JFUVVFU1RfT1BUSU9OUyB9KTtcblxuICAgICAgaWYgKCFyZXNwb25zZSB8fCByZXNwb25zZS5zdGF0dXNDb2RlICE9PSAyMDApIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oJ0ZhaWxlZCB0byBpbmNyZW1lbnQgcXVvdGEnLCB7XG4gICAgICAgICAgb3JnSWQsIHF1b3RhVHlwZSwgYW1vdW50LCBzdGF0dXNDb2RlOiByZXNwb25zZT8uc3RhdHVzQ29kZSxcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBsb2dnZXIuZGVidWcoJ1F1b3RhIGluY3JlbWVudGVkJywgeyBvcmdJZCwgcXVvdGFUeXBlLCBhbW91bnQgfSk7XG4gICAgICB9XG4gICAgfSxcblxuICAgIGFzeW5jIHVwZGF0ZUxpbWl0cyhcbiAgICAgIG9yZ0lkOiBzdHJpbmcsXG4gICAgICBsaW1pdHM6IFBhcnRpYWw8UmVjb3JkPFF1b3RhVHlwZSwgbnVtYmVyPj4sXG4gICAgICBhdXRoSGVhZGVyOiBzdHJpbmcsXG4gICAgICByZXF1ZXN0SWQ/OiBzdHJpbmcsXG4gICAgKTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgICBjb25zdCBwYXRoID0gYC9xdW90YXMvJHtlbmNvZGVVUklDb21wb25lbnQob3JnSWQpfWA7XG5cbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgY2xpZW50LnB1dChwYXRoLCBsaW1pdHMsIHsgaGVhZGVyczogYnVpbGRIZWFkZXJzKG9yZ0lkLCBhdXRoSGVhZGVyLCByZXF1ZXN0SWQpIH0pO1xuXG4gICAgICBpZiAoIXJlc3BvbnNlIHx8IHJlc3BvbnNlLnN0YXR1c0NvZGUgIT09IDIwMCkge1xuICAgICAgICBsb2dnZXIud2FybignRmFpbGVkIHRvIHVwZGF0ZSBxdW90YSBsaW1pdHMnLCB7XG4gICAgICAgICAgb3JnSWQsIGxpbWl0cywgc3RhdHVzQ29kZTogcmVzcG9uc2U/LnN0YXR1c0NvZGUsXG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG5cbiAgICAgIGxvZ2dlci5pbmZvKCdRdW90YSBsaW1pdHMgdXBkYXRlZCcsIHsgb3JnSWQsIGxpbWl0cyB9KTtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH0sXG5cbiAgICBhc3luYyByZXNldChvcmdJZDogc3RyaW5nLCBxdW90YVR5cGU/OiBRdW90YVR5cGUsIGF1dGhIZWFkZXI/OiBzdHJpbmcsIHJlcXVlc3RJZD86IHN0cmluZyk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgICAgY29uc3QgcGF0aCA9IGAvcXVvdGFzLyR7ZW5jb2RlVVJJQ29tcG9uZW50KG9yZ0lkKX0vcmVzZXRgO1xuXG4gICAgICBjb25zdCBib2R5ID0gcXVvdGFUeXBlID8geyBxdW90YVR5cGUgfSA6IHt9O1xuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBjbGllbnQucG9zdChwYXRoLCBib2R5LCB7IGhlYWRlcnM6IGJ1aWxkSGVhZGVycyhvcmdJZCwgYXV0aEhlYWRlciA/PyAnJywgcmVxdWVzdElkKSB9KTtcblxuICAgICAgaWYgKCFyZXNwb25zZSB8fCByZXNwb25zZS5zdGF0dXNDb2RlICE9PSAyMDApIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oJ0ZhaWxlZCB0byByZXNldCBxdW90YScsIHtcbiAgICAgICAgICBvcmdJZCwgcXVvdGFUeXBlLCBzdGF0dXNDb2RlOiByZXNwb25zZT8uc3RhdHVzQ29kZSxcbiAgICAgICAgfSk7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cblxuICAgICAgbG9nZ2VyLmluZm8oJ1F1b3RhIHJlc2V0JywgeyBvcmdJZCwgcXVvdGFUeXBlOiBxdW90YVR5cGUgPz8gJ2FsbCcgfSk7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9LFxuICB9O1xufVxuXG4vKipcbiAqIEZpcmUtYW5kLWZvcmdldCBxdW90YSBpbmNyZW1lbnQgd2l0aCBzdGFuZGFyZGl6ZWQgZXJyb3IgbG9nZ2luZy5cbiAqXG4gKiBXcmFwcyBgcXVvdGFTZXJ2aWNlLmluY3JlbWVudCgpYCB3aXRoIGEgYC5jYXRjaCgpYCB0aGF0IGxvZ3MgYSB3YXJuaW5nLlxuICogRWxpbWluYXRlcyB0aGUgaWRlbnRpY2FsIG9uZS1saW5lciByZXBlYXRlZCBhY3Jvc3MgZXZlcnkgcmVhZCByb3V0ZS5cbiAqXG4gKiBAcGFyYW0gcXVvdGFTZXJ2aWNlIC0gUXVvdGEgc2VydmljZSBjbGllbnRcbiAqIEBwYXJhbSBvcmdJZCAtIE9yZ2FuaXphdGlvbiBJRFxuICogQHBhcmFtIHF1b3RhVHlwZSAtIFF1b3RhIHR5cGUgdG8gaW5jcmVtZW50XG4gKiBAcGFyYW0gYXV0aEhlYWRlciAtIEF1dGhvcml6YXRpb24gaGVhZGVyIHZhbHVlXG4gKiBAcGFyYW0gbG9nV2FybiAtIExvZ2dpbmcgZnVuY3Rpb24gZm9yIHdhcm5pbmdzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpbmNyZW1lbnRRdW90YShcbiAgcXVvdGFTZXJ2aWNlOiBRdW90YVNlcnZpY2UsXG4gIG9yZ0lkOiBzdHJpbmcsXG4gIHF1b3RhVHlwZTogUXVvdGFUeXBlLFxuICBhdXRoSGVhZGVyOiBzdHJpbmcsXG4gIGxvZ1dhcm46IChtZXNzYWdlOiBzdHJpbmcsIGRhdGE/OiB1bmtub3duKSA9PiB2b2lkLFxuKTogdm9pZCB7XG4gIHF1b3RhU2VydmljZS5pbmNyZW1lbnQob3JnSWQsIHF1b3RhVHlwZSwgYXV0aEhlYWRlcikuY2F0Y2goKGVycjogdW5rbm93bikgPT5cbiAgICBsb2dXYXJuKCdRdW90YSBpbmNyZW1lbnQgZmFpbGVkJywgeyBlcnJvcjogZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpIH0pLFxuICApO1xufVxuIl19
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
/**
|
|
3
|
+
* Default retry configuration (env: `HTTP_CLIENT_MAX_RETRIES`, `HTTP_CLIENT_RETRY_DELAY_MS`).
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_MAX_RETRIES: number;
|
|
6
|
+
export declare const DEFAULT_RETRY_DELAY_MS: number;
|
|
7
|
+
export declare const DEFAULT_MAX_RATE_LIMIT_RETRIES: number;
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for retry behavior.
|
|
10
|
+
*/
|
|
11
|
+
export interface RetryConfig {
|
|
12
|
+
/** Maximum retry attempts for transient failures (default: 2) */
|
|
13
|
+
maxRetries: number;
|
|
14
|
+
/** Base delay between retries in ms — doubles each attempt (default: 200) */
|
|
15
|
+
retryDelayMs: number;
|
|
16
|
+
/** Maximum retry attempts specifically for 429 rate limiting (default: 4) */
|
|
17
|
+
maxRateLimitRetries: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Result of a retry decision: whether to retry, and how long to wait.
|
|
21
|
+
*/
|
|
22
|
+
export interface RetryDecision {
|
|
23
|
+
/** Whether the request should be retried */
|
|
24
|
+
shouldRetry: boolean;
|
|
25
|
+
/** Delay in milliseconds before retrying (with jitter applied) */
|
|
26
|
+
delayMs: number;
|
|
27
|
+
/** Human-readable reason for the retry decision */
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse a `Retry-After` header value into milliseconds.
|
|
32
|
+
* Supports numeric seconds (e.g. "5") and HTTP-date format.
|
|
33
|
+
* Returns `undefined` for missing or invalid values.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseRetryAfter(header: string | string[] | undefined): number | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Apply +/-25% random jitter to a delay to prevent thundering herd.
|
|
38
|
+
*/
|
|
39
|
+
export declare function addJitter(delay: number): number;
|
|
40
|
+
/**
|
|
41
|
+
* Calculate exponential backoff delay for a given attempt.
|
|
42
|
+
*
|
|
43
|
+
* @param baseDelay - Base delay in milliseconds
|
|
44
|
+
* @param attempt - Zero-based attempt number
|
|
45
|
+
* @returns Delay in milliseconds (without jitter)
|
|
46
|
+
*/
|
|
47
|
+
export declare function calculateBackoff(baseDelay: number, attempt: number): number;
|
|
48
|
+
/**
|
|
49
|
+
* Determine whether a status code represents a transient server error
|
|
50
|
+
* that is eligible for retry (502, 503, 504).
|
|
51
|
+
*/
|
|
52
|
+
export declare function isTransientStatusCode(statusCode: number): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Determine whether a status code indicates rate limiting (429).
|
|
55
|
+
*/
|
|
56
|
+
export declare function isRateLimited(statusCode: number): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Determine whether a failed response should be retried, and compute the delay.
|
|
59
|
+
*
|
|
60
|
+
* @param statusCode - HTTP status code of the response
|
|
61
|
+
* @param headers - Response headers (used to read Retry-After for 429)
|
|
62
|
+
* @param attempt - Zero-based attempt number
|
|
63
|
+
* @param config - Retry configuration
|
|
64
|
+
* @returns A RetryDecision indicating whether to retry and the delay
|
|
65
|
+
*/
|
|
66
|
+
export declare function getRetryDecision(statusCode: number, headers: http.IncomingHttpHeaders, attempt: number, config: RetryConfig): RetryDecision;
|
|
67
|
+
/**
|
|
68
|
+
* Determine whether a connection/timeout error should be retried, and compute the delay.
|
|
69
|
+
*
|
|
70
|
+
* @param attempt - Zero-based attempt number
|
|
71
|
+
* @param config - Retry configuration
|
|
72
|
+
* @returns A RetryDecision indicating whether to retry and the delay
|
|
73
|
+
*/
|
|
74
|
+
export declare function getErrorRetryDecision(attempt: number, config: RetryConfig): RetryDecision;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.DEFAULT_MAX_RATE_LIMIT_RETRIES = exports.DEFAULT_RETRY_DELAY_MS = exports.DEFAULT_MAX_RETRIES = void 0;
|
|
6
|
+
exports.parseRetryAfter = parseRetryAfter;
|
|
7
|
+
exports.addJitter = addJitter;
|
|
8
|
+
exports.calculateBackoff = calculateBackoff;
|
|
9
|
+
exports.isTransientStatusCode = isTransientStatusCode;
|
|
10
|
+
exports.isRateLimited = isRateLimited;
|
|
11
|
+
exports.getRetryDecision = getRetryDecision;
|
|
12
|
+
exports.getErrorRetryDecision = getErrorRetryDecision;
|
|
13
|
+
/**
|
|
14
|
+
* Default retry configuration (env: `HTTP_CLIENT_MAX_RETRIES`, `HTTP_CLIENT_RETRY_DELAY_MS`).
|
|
15
|
+
*/
|
|
16
|
+
exports.DEFAULT_MAX_RETRIES = parseInt(process.env.HTTP_CLIENT_MAX_RETRIES || '2', 10);
|
|
17
|
+
exports.DEFAULT_RETRY_DELAY_MS = parseInt(process.env.HTTP_CLIENT_RETRY_DELAY_MS || '200', 10);
|
|
18
|
+
exports.DEFAULT_MAX_RATE_LIMIT_RETRIES = parseInt(process.env.HTTP_CLIENT_MAX_RATE_LIMIT_RETRIES || '4', 10);
|
|
19
|
+
/** Max Retry-After value we'll honor (60 seconds). */
|
|
20
|
+
const MAX_RETRY_AFTER_MS = 60_000;
|
|
21
|
+
/** HTTP status codes considered transient server errors eligible for retry. */
|
|
22
|
+
const TRANSIENT_STATUS_CODES = [502, 503, 504];
|
|
23
|
+
/**
|
|
24
|
+
* Parse a `Retry-After` header value into milliseconds.
|
|
25
|
+
* Supports numeric seconds (e.g. "5") and HTTP-date format.
|
|
26
|
+
* Returns `undefined` for missing or invalid values.
|
|
27
|
+
*/
|
|
28
|
+
function parseRetryAfter(header) {
|
|
29
|
+
if (!header)
|
|
30
|
+
return undefined;
|
|
31
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
32
|
+
if (!value)
|
|
33
|
+
return undefined;
|
|
34
|
+
// Try numeric seconds first
|
|
35
|
+
const seconds = Number(value);
|
|
36
|
+
if (!isNaN(seconds) && seconds >= 0) {
|
|
37
|
+
return Math.min(seconds * 1000, MAX_RETRY_AFTER_MS);
|
|
38
|
+
}
|
|
39
|
+
// Try HTTP-date
|
|
40
|
+
const date = Date.parse(value);
|
|
41
|
+
if (!isNaN(date)) {
|
|
42
|
+
const delayMs = date - Date.now();
|
|
43
|
+
if (delayMs > 0)
|
|
44
|
+
return Math.min(delayMs, MAX_RETRY_AFTER_MS);
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Apply +/-25% random jitter to a delay to prevent thundering herd.
|
|
50
|
+
*/
|
|
51
|
+
function addJitter(delay) {
|
|
52
|
+
const jitter = delay * 0.25 * (2 * Math.random() - 1); // -25% to +25%
|
|
53
|
+
return Math.max(0, Math.round(delay + jitter));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Calculate exponential backoff delay for a given attempt.
|
|
57
|
+
*
|
|
58
|
+
* @param baseDelay - Base delay in milliseconds
|
|
59
|
+
* @param attempt - Zero-based attempt number
|
|
60
|
+
* @returns Delay in milliseconds (without jitter)
|
|
61
|
+
*/
|
|
62
|
+
function calculateBackoff(baseDelay, attempt) {
|
|
63
|
+
return baseDelay * Math.pow(2, attempt);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Determine whether a status code represents a transient server error
|
|
67
|
+
* that is eligible for retry (502, 503, 504).
|
|
68
|
+
*/
|
|
69
|
+
function isTransientStatusCode(statusCode) {
|
|
70
|
+
return TRANSIENT_STATUS_CODES.includes(statusCode);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Determine whether a status code indicates rate limiting (429).
|
|
74
|
+
*/
|
|
75
|
+
function isRateLimited(statusCode) {
|
|
76
|
+
return statusCode === 429;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Determine whether a failed response should be retried, and compute the delay.
|
|
80
|
+
*
|
|
81
|
+
* @param statusCode - HTTP status code of the response
|
|
82
|
+
* @param headers - Response headers (used to read Retry-After for 429)
|
|
83
|
+
* @param attempt - Zero-based attempt number
|
|
84
|
+
* @param config - Retry configuration
|
|
85
|
+
* @returns A RetryDecision indicating whether to retry and the delay
|
|
86
|
+
*/
|
|
87
|
+
function getRetryDecision(statusCode, headers, attempt, config) {
|
|
88
|
+
// 429 rate limiting — use Retry-After or longer backoff (4x base)
|
|
89
|
+
if (isRateLimited(statusCode) && attempt < config.maxRateLimitRetries) {
|
|
90
|
+
const retryAfter = parseRetryAfter(headers['retry-after']);
|
|
91
|
+
const rawDelay = retryAfter ?? (config.retryDelayMs * 4 * Math.pow(2, attempt));
|
|
92
|
+
return {
|
|
93
|
+
shouldRetry: true,
|
|
94
|
+
delayMs: addJitter(rawDelay),
|
|
95
|
+
reason: 'Rate limited (429)',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// 5xx transient server errors — standard exponential backoff
|
|
99
|
+
if (isTransientStatusCode(statusCode) && attempt < config.maxRetries) {
|
|
100
|
+
const rawDelay = calculateBackoff(config.retryDelayMs, attempt);
|
|
101
|
+
return {
|
|
102
|
+
shouldRetry: true,
|
|
103
|
+
delayMs: addJitter(rawDelay),
|
|
104
|
+
reason: `Transient server error (${statusCode})`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { shouldRetry: false, delayMs: 0, reason: 'Not retryable' };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Determine whether a connection/timeout error should be retried, and compute the delay.
|
|
111
|
+
*
|
|
112
|
+
* @param attempt - Zero-based attempt number
|
|
113
|
+
* @param config - Retry configuration
|
|
114
|
+
* @returns A RetryDecision indicating whether to retry and the delay
|
|
115
|
+
*/
|
|
116
|
+
function getErrorRetryDecision(attempt, config) {
|
|
117
|
+
if (attempt < config.maxRetries) {
|
|
118
|
+
const rawDelay = calculateBackoff(config.retryDelayMs, attempt);
|
|
119
|
+
return {
|
|
120
|
+
shouldRetry: true,
|
|
121
|
+
delayMs: addJitter(rawDelay),
|
|
122
|
+
reason: 'Connection or timeout error',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return { shouldRetry: false, delayMs: 0, reason: 'Max retries exceeded' };
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0cnktc3RyYXRlZ3kuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvcmV0cnktc3RyYXRlZ3kudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7OztBQThDdEMsMENBbUJDO0FBS0QsOEJBR0M7QUFTRCw0Q0FFQztBQU1ELHNEQUVDO0FBS0Qsc0NBRUM7QUFXRCw0Q0E0QkM7QUFTRCxzREFjQztBQTdKRDs7R0FFRztBQUNVLFFBQUEsbUJBQW1CLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsdUJBQXVCLElBQUksR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQy9FLFFBQUEsc0JBQXNCLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsMEJBQTBCLElBQUksS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ3ZGLFFBQUEsOEJBQThCLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsa0NBQWtDLElBQUksR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBRWxILHNEQUFzRDtBQUN0RCxNQUFNLGtCQUFrQixHQUFHLE1BQU0sQ0FBQztBQUVsQywrRUFBK0U7QUFDL0UsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUM7QUEwQi9DOzs7O0dBSUc7QUFDSCxTQUFnQixlQUFlLENBQUMsTUFBcUM7SUFDbkUsSUFBSSxDQUFDLE1BQU07UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUM5QixNQUFNLEtBQUssR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUN6RCxJQUFJLENBQUMsS0FBSztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBRTdCLDRCQUE0QjtJQUM1QixNQUFN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LEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxlQUFlLEVBQUUsQ0FBQztBQUNyRSxDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsU0FBZ0IscUJBQXFCLENBQ25DLE9BQWUsRUFDZixNQUFtQjtJQUVuQixJQUFJLE9BQU8sR0FBRyxNQUFNLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDaEMsTUFBTSxRQUFRLEdBQUcsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLFlBQVksRUFBRSxPQUFPLENBQUMsQ0FBQztRQUNoRSxPQUFPO1lBQ0wsV0FBVyxFQUFFLElBQUk7WUFDakIsT0FBTyxFQUFFLFNBQVMsQ0FBQyxRQUFRLENBQUM7WUFDNUIsTUFBTSxFQUFFLDZCQUE2QjtTQUN0QyxDQUFDO0lBQ0osQ0FBQztJQUVELE9BQU8sRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxDQUFDLEVBQUUsTUFBTSxFQUFFLHNCQUFzQixFQUFFLENBQUM7QUFDNUUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgKiBhcyBodHRwIGZyb20gJ2h0dHAnO1xuXG4vKipcbiAqIERlZmF1bHQgcmV0cnkgY29uZmlndXJhdGlvbiAoZW52OiBgSFRUUF9DTElFTlRfTUFYX1JFVFJJRVNgLCBgSFRUUF9DTElFTlRfUkVUUllfREVMQVlfTVNgKS5cbiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfTUFYX1JFVFJJRVMgPSBwYXJzZUludChwcm9jZXNzLmVudi5IVFRQX0NMSUVOVF9NQVhfUkVUUklFUyB8fCAnMicsIDEwKTtcbmV4cG9ydCBjb25zdCBERUZBVUxUX1JFVFJZX0RFTEFZX01TID0gcGFyc2VJbnQocHJvY2Vzcy5lbnYuSFRUUF9DTElFTlRfUkVUUllfREVMQVlfTVMgfHwgJzIwMCcsIDEwKTtcbmV4cG9ydCBjb25zdCBERUZBVUxUX01BWF9SQVRFX0xJTUlUX1JFVFJJRVMgPSBwYXJzZUludChwcm9jZXNzLmVudi5IVFRQX0NMSUVOVF9NQVhfUkFURV9MSU1JVF9SRVRSSUVTIHx8ICc0JywgMTApO1xuXG4vKiogTWF4IFJldHJ5LUFmdGVyIHZhbHVlIHdlJ2xsIGhvbm9yICg2MCBzZWNvbmRzKS4gKi9cbmNvbnN0IE1BWF9SRVRSWV9BRlRFUl9NUyA9IDYwXzAwMDtcblxuLyoqIEhUVFAgc3RhdHVzIGNvZGVzIGNvbnNpZGVyZWQgdHJhbnNpZW50IHNlcnZlciBlcnJvcnMgZWxpZ2libGUgZm9yIHJldHJ5LiAqL1xuY29uc3QgVFJBTlNJRU5UX1NUQVRVU19DT0RFUyA9IFs1MDIsIDUwMywgNTA0XTtcblxuLyoqXG4gKiBDb25maWd1cmF0aW9uIGZvciByZXRyeSBiZWhhdmlvci5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBSZXRyeUNvbmZpZyB7XG4gIC8qKiBNYXhpbXVtIHJldHJ5IGF0dGVtcHRzIGZvciB0cmFuc2llbnQgZmFpbHVyZXMgKGRlZmF1bHQ6IDIpICovXG4gIG1heFJldHJpZXM6IG51bWJlcjtcbiAgLyoqIEJhc2UgZGVsYXkgYmV0d2VlbiByZXRyaWVzIGluIG1zIOKAlCBkb3VibGVzIGVhY2ggYXR0ZW1wdCAoZGVmYXVsdDogMjAwKSAqL1xuICByZXRyeURlbGF5TXM6IG51bWJlcjtcbiAgLyoqIE1heGltdW0gcmV0cnkgYXR0ZW1wdHMgc3BlY2lmaWNhbGx5IGZvciA0MjkgcmF0ZSBsaW1pdGluZyAoZGVmYXVsdDogNCkgKi9cbiAgbWF4UmF0ZUxpbWl0UmV0cmllczogbnVtYmVyO1xufVxuXG4vKipcbiAqIFJlc3VsdCBvZiBhIHJldHJ5IGRlY2lzaW9uOiB3aGV0aGVyIHRvIHJldHJ5LCBhbmQgaG93IGxvbmcgdG8gd2FpdC5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBSZXRyeURlY2lzaW9uIHtcbiAgLyoqIFdoZXRoZXIgdGhlIHJlcXVlc3Qgc2hvdWxkIGJlIHJldHJpZWQgKi9cbiAgc2hvdWxkUmV0cnk6IGJvb2xlYW47XG4gIC8qKiBEZWxheSBpbiBtaWxsaXNlY29uZHMgYmVmb3JlIHJldHJ5aW5nICh3aXRoIGppdHRlciBhcHBsaWVkKSAqL1xuICBkZWxheU1zOiBudW1iZXI7XG4gIC8qKiBIdW1hbi1yZWFkYWJsZSByZWFzb24gZm9yIHRoZSByZXRyeSBkZWNpc2lvbiAqL1xuICByZWFzb246IHN0cmluZztcbn1cblxuLyoqXG4gKiBQYXJzZSBhIGBSZXRyeS1BZnRlcmAgaGVhZGVyIHZhbHVlIGludG8gbWlsbGlzZWNvbmRzLlxuICogU3VwcG9ydHMgbnVtZXJpYyBzZWNvbmRzIChlLmcuIFwiNVwiKSBhbmQgSFRUUC1kYXRlIGZvcm1hdC5cbiAqIFJldHVybnMgYHVuZGVmaW5lZGAgZm9yIG1pc3Npbmcgb3IgaW52YWxpZCB2YWx1ZXMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVJldHJ5QWZ0ZXIoaGVhZGVyOiBzdHJpbmcgfCBzdHJpbmdbXSB8IHVuZGVmaW5lZCk6IG51bWJlciB8IHVuZGVmaW5lZCB7XG4gIGlmICghaGVhZGVyKSByZXR1cm4gdW5kZWZpbmVkO1xuICBjb25zdCB2YWx1ZSA9IEFycmF5LmlzQXJyYXkoaGVhZGVyKSA/IGhlYWRlclswXSA6IGhlYWRlcjtcbiAgaWYgKCF2YWx1ZSkgcmV0dXJuIHVuZGVmaW5lZDtcblxuICAvLyBUcnkgbnVtZXJpYyBzZWNvbmRzIGZpcnN0XG4gIGNvbnN0IHNlY29uZHMgPSBOdW1iZXIodmFsdWUpO1xuICBpZiAoIWlzTmFOKHNlY29uZHMpICYmIHNlY29uZHMgPj0gMCkge1xuICAgIHJldHVybiBNYXRoLm1pbihzZWNvbmRzICogMTAwMCwgTUFYX1JFVFJZX0FGVEVSX01TKTtcbiAgfVxuXG4gIC8vIFRyeSBIVFRQLWRhdGVcbiAgY29uc3QgZGF0ZSA9IERhdGUucGFyc2UodmFsdWUpO1xuICBpZiAoIWlzTmFOKGRhdGUpKSB7XG4gICAgY29uc3QgZGVsYXlNcyA9IGRhdGUgLSBEYXRlLm5vdygpO1xuICAgIGlmIChkZWxheU1zID4gMCkgcmV0dXJuIE1hdGgubWluKGRlbGF5TXMsIE1BWF9SRVRSWV9BRlRFUl9NUyk7XG4gIH1cblxuICByZXR1cm4gdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIEFwcGx5ICsvLTI1JSByYW5kb20gaml0dGVyIHRvIGEgZGVsYXkgdG8gcHJldmVudCB0aHVuZGVyaW5nIGhlcmQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhZGRKaXR0ZXIoZGVsYXk6IG51bWJlcik6IG51bWJlciB7XG4gIGNvbnN0IGppdHRlciA9IGRlbGF5ICogMC4yNSAqICgyICogTWF0aC5yYW5kb20oKSAtIDEpOyAvLyAtMjUlIHRvICsyNSVcbiAgcmV0dXJuIE1hdGgubWF4KDAsIE1hdGgucm91bmQoZGVsYXkgKyBqaXR0ZXIpKTtcbn1cblxuLyoqXG4gKiBDYWxjdWxhdGUgZXhwb25lbnRpYWwgYmFja29mZiBkZWxheSBmb3IgYSBnaXZlbiBhdHRlbXB0LlxuICpcbiAqIEBwYXJhbSBiYXNlRGVsYXkgLSBCYXNlIGRlbGF5IGluIG1pbGxpc2Vjb25kc1xuICogQHBhcmFtIGF0dGVtcHQgLSBaZXJvLWJhc2VkIGF0dGVtcHQgbnVtYmVyXG4gKiBAcmV0dXJucyBEZWxheSBpbiBtaWxsaXNlY29uZHMgKHdpdGhvdXQgaml0dGVyKVxuICovXG5leHBvcnQgZnVuY3Rpb24gY2FsY3VsYXRlQmFja29mZihiYXNlRGVsYXk6IG51bWJlciwgYXR0ZW1wdDogbnVtYmVyKTogbnVtYmVyIHtcbiAgcmV0dXJuIGJhc2VEZWxheSAqIE1hdGgucG93KDIsIGF0dGVtcHQpO1xufVxuXG4vKipcbiAqIERldGVybWluZSB3aGV0aGVyIGEgc3RhdHVzIGNvZGUgcmVwcmVzZW50cyBhIHRyYW5zaWVudCBzZXJ2ZXIgZXJyb3JcbiAqIHRoYXQgaXMgZWxpZ2libGUgZm9yIHJldHJ5ICg1MDIsIDUwMywgNTA0KS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzVHJhbnNpZW50U3RhdHVzQ29kZShzdGF0dXNDb2RlOiBudW1iZXIpOiBib29sZWFuIHtcbiAgcmV0dXJuIFRSQU5TSUVOVF9TVEFUVVNfQ09ERVMuaW5jbHVkZXMoc3RhdHVzQ29kZSk7XG59XG5cbi8qKlxuICogRGV0ZXJtaW5lIHdoZXRoZXIgYSBzdGF0dXMgY29kZSBpbmRpY2F0ZXMgcmF0ZSBsaW1pdGluZyAoNDI5KS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzUmF0ZUxpbWl0ZWQoc3RhdHVzQ29kZTogbnVtYmVyKTogYm9vbGVhbiB7XG4gIHJldHVybiBzdGF0dXNDb2RlID09PSA0Mjk7XG59XG5cbi8qKlxuICogRGV0ZXJtaW5lIHdoZXRoZXIgYSBmYWlsZWQgcmVzcG9uc2Ugc2hvdWxkIGJlIHJldHJpZWQsIGFuZCBjb21wdXRlIHRoZSBkZWxheS5cbiAqXG4gKiBAcGFyYW0gc3RhdHVzQ29kZSAtIEhUVFAgc3RhdHVzIGNvZGUgb2YgdGhlIHJlc3BvbnNlXG4gKiBAcGFyYW0gaGVhZGVycyAtIFJlc3BvbnNlIGhlYWRlcnMgKHVzZWQgdG8gcmVhZCBSZXRyeS1BZnRlciBmb3IgNDI5KVxuICogQHBhcmFtIGF0dGVtcHQgLSBaZXJvLWJhc2VkIGF0dGVtcHQgbnVtYmVyXG4gKiBAcGFyYW0gY29uZmlnIC0gUmV0cnkgY29uZmlndXJhdGlvblxuICogQHJldHVybnMgQSBSZXRyeURlY2lzaW9uIGluZGljYXRpbmcgd2hldGhlciB0byByZXRyeSBhbmQgdGhlIGRlbGF5XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRSZXRyeURlY2lzaW9uKFxuICBzdGF0dXNDb2RlOiBudW1iZXIsXG4gIGhlYWRlcnM6IGh0dHAuSW5jb21pbmdIdHRwSGVhZGVycyxcbiAgYXR0ZW1wdDogbnVtYmVyLFxuICBjb25maWc6IFJldHJ5Q29uZmlnLFxuKTogUmV0cnlEZWNpc2lvbiB7XG4gIC8vIDQyOSByYXRlIGxpbWl0aW5nIOKAlCB1c2UgUmV0cnktQWZ0ZXIgb3IgbG9uZ2VyIGJhY2tvZmYgKDR4IGJhc2UpXG4gIGlmIChpc1JhdGVMaW1pdGVkKHN0YXR1c0NvZGUpICYmIGF0dGVtcHQgPCBjb25maWcubWF4UmF0ZUxpbWl0UmV0cmllcykge1xuICAgIGNvbnN0IHJldHJ5QWZ0ZXIgPSBwYXJzZVJldHJ5QWZ0ZXIoaGVhZGVyc1sncmV0cnktYWZ0ZXInXSk7XG4gICAgY29uc3QgcmF3RGVsYXkgPSByZXRyeUFmdGVyID8/IChjb25maWcucmV0cnlEZWxheU1zICogNCAqIE1hdGgucG93KDIsIGF0dGVtcHQpKTtcbiAgICByZXR1cm4ge1xuICAgICAgc2hvdWxkUmV0cnk6IHRydWUsXG4gICAgICBkZWxheU1zOiBhZGRKaXR0ZXIocmF3RGVsYXkpLFxuICAgICAgcmVhc29uOiAnUmF0ZSBsaW1pdGVkICg0MjkpJyxcbiAgICB9O1xuICB9XG5cbiAgLy8gNXh4IHRyYW5zaWVudCBzZXJ2ZXIgZXJyb3JzIOKAlCBzdGFuZGFyZCBleHBvbmVudGlhbCBiYWNrb2ZmXG4gIGlmIChpc1RyYW5zaWVudFN0YXR1c0NvZGUoc3RhdHVzQ29kZSkgJiYgYXR0ZW1wdCA8IGNvbmZpZy5tYXhSZXRyaWVzKSB7XG4gICAgY29uc3QgcmF3RGVsYXkgPSBjYWxjdWxhdGVCYWNrb2ZmKGNvbmZpZy5yZXRyeURlbGF5TXMsIGF0dGVtcHQpO1xuICAgIHJldHVybiB7XG4gICAgICBzaG91bGRSZXRyeTogdHJ1ZSxcbiAgICAgIGRlbGF5TXM6IGFkZEppdHRlcihyYXdEZWxheSksXG4gICAgICByZWFzb246IGBUcmFuc2llbnQgc2VydmVyIGVycm9yICgke3N0YXR1c0NvZGV9KWAsXG4gICAgfTtcbiAgfVxuXG4gIHJldHVybiB7IHNob3VsZFJldHJ5OiBmYWxzZSwgZGVsYXlNczogMCwgcmVhc29uOiAnTm90IHJldHJ5YWJsZScgfTtcbn1cblxuLyoqXG4gKiBEZXRlcm1pbmUgd2hldGhlciBhIGNvbm5lY3Rpb24vdGltZW91dCBlcnJvciBzaG91bGQgYmUgcmV0cmllZCwgYW5kIGNvbXB1dGUgdGhlIGRlbGF5LlxuICpcbiAqIEBwYXJhbSBhdHRlbXB0IC0gWmVyby1iYXNlZCBhdHRlbXB0IG51bWJlclxuICogQHBhcmFtIGNvbmZpZyAtIFJldHJ5IGNvbmZpZ3VyYXRpb25cbiAqIEByZXR1cm5zIEEgUmV0cnlEZWNpc2lvbiBpbmRpY2F0aW5nIHdoZXRoZXIgdG8gcmV0cnkgYW5kIHRoZSBkZWxheVxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0RXJyb3JSZXRyeURlY2lzaW9uKFxuICBhdHRlbXB0OiBudW1iZXIsXG4gIGNvbmZpZzogUmV0cnlDb25maWcsXG4pOiBSZXRyeURlY2lzaW9uIHtcbiAgaWYgKGF0dGVtcHQgPCBjb25maWcubWF4UmV0cmllcykge1xuICAgIGNvbnN0IHJhd0RlbGF5ID0gY2FsY3VsYXRlQmFja29mZihjb25maWcucmV0cnlEZWxheU1zLCBhdHRlbXB0KTtcbiAgICByZXR1cm4ge1xuICAgICAgc2hvdWxkUmV0cnk6IHRydWUsXG4gICAgICBkZWxheU1zOiBhZGRKaXR0ZXIocmF3RGVsYXkpLFxuICAgICAgcmVhc29uOiAnQ29ubmVjdGlvbiBvciB0aW1lb3V0IGVycm9yJyxcbiAgICB9O1xuICB9XG5cbiAgcmV0dXJuIHsgc2hvdWxkUmV0cnk6IGZhbHNlLCBkZWxheU1zOiAwLCByZWFzb246ICdNYXggcmV0cmllcyBleGNlZWRlZCcgfTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { QuotaTier } from './quota-tiers';
|
|
2
|
+
/** Billing interval for subscriptions. */
|
|
3
|
+
export type BillingInterval = 'monthly' | 'annual';
|
|
4
|
+
/** Subscription lifecycle status. */
|
|
5
|
+
export type SubscriptionStatus = 'active' | 'canceled' | 'past_due' | 'trialing' | 'incomplete';
|
|
6
|
+
/** Payment transaction status. */
|
|
7
|
+
export type PaymentStatus = 'succeeded' | 'pending' | 'failed' | 'refunded';
|
|
8
|
+
/** Billing event types for audit logging. */
|
|
9
|
+
export type BillingEventType = 'subscription_created' | 'subscription_updated' | 'subscription_canceled' | 'subscription_reactivated' | 'plan_changed' | 'interval_changed' | 'payment_succeeded' | 'payment_failed';
|
|
10
|
+
/** Price definition for a plan (in cents). */
|
|
11
|
+
export interface PlanPrices {
|
|
12
|
+
monthly: number;
|
|
13
|
+
annual: number;
|
|
14
|
+
}
|
|
15
|
+
/** Plan definition returned by the billing API. */
|
|
16
|
+
export interface PlanDefinition {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
tier: QuotaTier;
|
|
21
|
+
prices: PlanPrices;
|
|
22
|
+
features: string[];
|
|
23
|
+
isDefault: boolean;
|
|
24
|
+
sortOrder: number;
|
|
25
|
+
}
|
|
26
|
+
/** Subscription info returned by the billing API. */
|
|
27
|
+
export interface SubscriptionInfo {
|
|
28
|
+
id: string;
|
|
29
|
+
orgId: string;
|
|
30
|
+
planId: string;
|
|
31
|
+
status: SubscriptionStatus;
|
|
32
|
+
interval: BillingInterval;
|
|
33
|
+
currentPeriodStart: string;
|
|
34
|
+
currentPeriodEnd: string;
|
|
35
|
+
cancelAtPeriodEnd: boolean;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
updatedAt: string;
|
|
38
|
+
}
|
|
39
|
+
/** Billing event info returned by the admin API. */
|
|
40
|
+
export interface BillingEventInfo {
|
|
41
|
+
id: string;
|
|
42
|
+
orgId: string;
|
|
43
|
+
subscriptionId?: string;
|
|
44
|
+
type: BillingEventType;
|
|
45
|
+
details: Record<string, unknown>;
|
|
46
|
+
createdAt: string;
|
|
47
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmlsbGluZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eXBlcy9iaWxsaW5nLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB0eXBlIHsgUXVvdGFUaWVyIH0gZnJvbSAnLi9xdW90YS10aWVycyc7XG5cbi8qKiBCaWxsaW5nIGludGVydmFsIGZvciBzdWJzY3JpcHRpb25zLiAqL1xuZXhwb3J0IHR5cGUgQmlsbGluZ0ludGVydmFsID0gJ21vbnRobHknIHwgJ2FubnVhbCc7XG5cbi8qKiBTdWJzY3JpcHRpb24gbGlmZWN5Y2xlIHN0YXR1cy4gKi9cbmV4cG9ydCB0eXBlIFN1YnNjcmlwdGlvblN0YXR1cyA9ICdhY3RpdmUnIHwgJ2NhbmNlbGVkJyB8ICdwYXN0X2R1ZScgfCAndHJpYWxpbmcnIHwgJ2luY29tcGxldGUnO1xuXG4vKiogUGF5bWVudCB0cmFuc2FjdGlvbiBzdGF0dXMuICovXG5leHBvcnQgdHlwZSBQYXltZW50U3RhdHVzID0gJ3N1Y2NlZWRlZCcgfCAncGVuZGluZycgfCAnZmFpbGVkJyB8ICdyZWZ1bmRlZCc7XG5cbi8qKiBCaWxsaW5nIGV2ZW50IHR5cGVzIGZvciBhdWRpdCBsb2dnaW5nLiAqL1xuZXhwb3J0IHR5cGUgQmlsbGluZ0V2ZW50VHlwZSA9XG4gIHwgJ3N1YnNjcmlwdGlvbl9jcmVhdGVkJ1xuICB8ICdzdWJzY3JpcHRpb25fdXBkYXRlZCdcbiAgfCAnc3Vic2NyaXB0aW9uX2NhbmNlbGVkJ1xuICB8ICdzdWJzY3JpcHRpb25fcmVhY3RpdmF0ZWQnXG4gIHwgJ3BsYW5fY2hhbmdlZCdcbiAgfCAnaW50ZXJ2YWxfY2hhbmdlZCdcbiAgfCAncGF5bWVudF9zdWNjZWVkZWQnXG4gIHwgJ3BheW1lbnRfZmFpbGVkJztcblxuLyoqIFByaWNlIGRlZmluaXRpb24gZm9yIGEgcGxhbiAoaW4gY2VudHMpLiAqL1xuZXhwb3J0IGludGVyZmFjZSBQbGFuUHJpY2VzIHtcbiAgbW9udGhseTogbnVtYmVyO1xuICBhbm51YWw6IG51bWJlcjtcbn1cblxuLyoqIFBsYW4gZGVmaW5pdGlvbiByZXR1cm5lZCBieSB0aGUgYmlsbGluZyBBUEkuICovXG5leHBvcnQgaW50ZXJmYWNlIFBsYW5EZWZpbml0aW9uIHtcbiAgaWQ6IHN0cmluZztcbiAgbmFtZTogc3RyaW5nO1xuICBkZXNjcmlwdGlvbjogc3RyaW5nO1xuICB0aWVyOiBRdW90YVRpZXI7XG4gIHByaWNlczogUGxhblByaWNlcztcbiAgZmVhdHVyZXM6IHN0cmluZ1tdO1xuICBpc0RlZmF1bHQ6IGJvb2xlYW47XG4gIHNvcnRPcmRlcjogbnVtYmVyO1xufVxuXG4vKiogU3Vic2NyaXB0aW9uIGluZm8gcmV0dXJuZWQgYnkgdGhlIGJpbGxpbmcgQVBJLiAqL1xuZXhwb3J0IGludGVyZmFjZSBTdWJzY3JpcHRpb25JbmZvIHtcbiAgaWQ6IHN0cmluZztcbiAgb3JnSWQ6IHN0cmluZztcbiAgcGxhbklkOiBzdHJpbmc7XG4gIHN0YXR1czogU3Vic2NyaXB0aW9uU3RhdHVzO1xuICBpbnRlcnZhbDogQmlsbGluZ0ludGVydmFsO1xuICBjdXJyZW50UGVyaW9kU3RhcnQ6IHN0cmluZztcbiAgY3VycmVudFBlcmlvZEVuZDogc3RyaW5nO1xuICBjYW5jZWxBdFBlcmlvZEVuZDogYm9vbGVhbjtcbiAgY3JlYXRlZEF0OiBzdHJpbmc7XG4gIHVwZGF0ZWRBdDogc3RyaW5nO1xufVxuXG4vKiogQmlsbGluZyBldmVudCBpbmZvIHJldHVybmVkIGJ5IHRoZSBhZG1pbiBBUEkuICovXG5leHBvcnQgaW50ZXJmYWNlIEJpbGxpbmdFdmVudEluZm8ge1xuICBpZDogc3RyaW5nO1xuICBvcmdJZDogc3RyaW5nO1xuICBzdWJzY3JpcHRpb25JZD86IHN0cmluZztcbiAgdHlwZTogQmlsbGluZ0V2ZW50VHlwZTtcbiAgZGV0YWlsczogUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIGNyZWF0ZWRBdDogc3RyaW5nO1xufVxuIl19
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota type identifiers.
|
|
3
|
+
*/
|
|
4
|
+
export type QuotaType = 'plugins' | 'pipelines' | 'apiCalls';
|
|
5
|
+
/**
|
|
6
|
+
* Valid quota type values.
|
|
7
|
+
*/
|
|
8
|
+
export declare const VALID_QUOTA_TYPES: readonly ["plugins", "pipelines", "apiCalls"];
|
|
9
|
+
/**
|
|
10
|
+
* Type guard to check if a value is a valid QuotaType.
|
|
11
|
+
*
|
|
12
|
+
* @param value - Value to check
|
|
13
|
+
* @returns True if value is a valid QuotaType
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* if (isValidQuotaType(req.body.quotaType)) {
|
|
18
|
+
* // quotaType is guaranteed to be QuotaType
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function isValidQuotaType(value: unknown): value is QuotaType;
|
|
23
|
+
/**
|
|
24
|
+
* Validate and assert that a value is a valid QuotaType.
|
|
25
|
+
* Throws an error if validation fails.
|
|
26
|
+
*
|
|
27
|
+
* @param value - Value to validate
|
|
28
|
+
* @param fieldName - Name of the field being validated (for error messages)
|
|
29
|
+
* @returns The validated QuotaType
|
|
30
|
+
* @throws Error if value is not a valid QuotaType
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* try {
|
|
35
|
+
* const quotaType = validateQuotaType(req.body.quotaType, 'quotaType');
|
|
36
|
+
* // Use quotaType safely
|
|
37
|
+
* } catch (err) {
|
|
38
|
+
* return sendError(res, 400, err.message);
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateQuotaType(value: unknown, fieldName?: string): QuotaType;
|
|
43
|
+
/**
|
|
44
|
+
* Result from quota check operation.
|
|
45
|
+
*/
|
|
46
|
+
export interface QuotaCheckResult {
|
|
47
|
+
/** Whether the request is allowed */
|
|
48
|
+
allowed: boolean;
|
|
49
|
+
/** Maximum quota limit (-1 for unlimited) */
|
|
50
|
+
limit: number;
|
|
51
|
+
/** Current usage count */
|
|
52
|
+
used: number;
|
|
53
|
+
/** Remaining quota (-1 for unlimited) */
|
|
54
|
+
remaining: number;
|
|
55
|
+
/** ISO timestamp when quota resets */
|
|
56
|
+
resetAt: string;
|
|
57
|
+
/** Whether quota is unlimited */
|
|
58
|
+
unlimited: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Quota information for error responses.
|
|
62
|
+
*/
|
|
63
|
+
export interface QuotaInfo {
|
|
64
|
+
type: QuotaType;
|
|
65
|
+
limit: number;
|
|
66
|
+
used: number;
|
|
67
|
+
remaining: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Standard API success response.
|
|
71
|
+
*/
|
|
72
|
+
export interface ApiSuccessResponse<T = unknown> {
|
|
73
|
+
success: true;
|
|
74
|
+
statusCode: number;
|
|
75
|
+
data?: T;
|
|
76
|
+
message?: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Standard API error response.
|
|
80
|
+
*/
|
|
81
|
+
export interface ApiErrorResponse {
|
|
82
|
+
success: false;
|
|
83
|
+
statusCode: number;
|
|
84
|
+
message: string;
|
|
85
|
+
code?: string;
|
|
86
|
+
details?: unknown;
|
|
87
|
+
quota?: QuotaInfo;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Combined API response type.
|
|
91
|
+
*/
|
|
92
|
+
export type ApiResponse<T = unknown> = ApiSuccessResponse<T> | ApiErrorResponse;
|
|
93
|
+
/**
|
|
94
|
+
* JWT payload from access tokens.
|
|
95
|
+
*
|
|
96
|
+
* Users can belong to multiple organizations. The token is scoped to one
|
|
97
|
+
* active organization at a time. The `role` field is the user's per-org
|
|
98
|
+
* role in that organization (from the UserOrganization junction collection),
|
|
99
|
+
* and `isAdmin` is derived as `role === 'admin' || role === 'owner'`.
|
|
100
|
+
*
|
|
101
|
+
* Use `POST /auth/switch-org` to change the active organization, which
|
|
102
|
+
* re-issues tokens with the new org's role and context.
|
|
103
|
+
*/
|
|
104
|
+
export interface JwtPayload {
|
|
105
|
+
/** User ID (subject) */
|
|
106
|
+
sub: string;
|
|
107
|
+
/** Username */
|
|
108
|
+
username: string;
|
|
109
|
+
/** User email */
|
|
110
|
+
email: string;
|
|
111
|
+
/** Per-org role in the active organization ('owner' | 'admin' | 'member'). Not a global role. */
|
|
112
|
+
role: 'owner' | 'admin' | 'member';
|
|
113
|
+
/** Derived: true when role is 'admin' or 'owner' in the active organization */
|
|
114
|
+
isAdmin?: boolean;
|
|
115
|
+
/** Organization's quota tier ('developer' | 'pro' | 'unlimited') */
|
|
116
|
+
tier?: string;
|
|
117
|
+
/** Resolved feature flags for this user/org */
|
|
118
|
+
features?: string[];
|
|
119
|
+
/** Active organization ID (from UserOrganization membership) */
|
|
120
|
+
organizationId?: string;
|
|
121
|
+
/** Active organization name */
|
|
122
|
+
organizationName?: string;
|
|
123
|
+
/** Token type */
|
|
124
|
+
type: 'access' | 'refresh';
|
|
125
|
+
/** Issued at timestamp */
|
|
126
|
+
iat?: number;
|
|
127
|
+
/** Expiration timestamp */
|
|
128
|
+
exp?: number;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Extended Express Request with user property.
|
|
132
|
+
*/
|
|
133
|
+
declare global {
|
|
134
|
+
namespace Express {
|
|
135
|
+
interface Request {
|
|
136
|
+
user?: JwtPayload;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Service configuration for internal HTTP client.
|
|
142
|
+
*/
|
|
143
|
+
export interface ServiceConfig {
|
|
144
|
+
/** Service hostname */
|
|
145
|
+
host: string;
|
|
146
|
+
/** Service port */
|
|
147
|
+
port: number;
|
|
148
|
+
/** Request timeout in milliseconds */
|
|
149
|
+
timeout?: number;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Health check response.
|
|
153
|
+
*/
|
|
154
|
+
export interface HealthCheckResponse {
|
|
155
|
+
status: 'healthy' | 'unhealthy';
|
|
156
|
+
service: string;
|
|
157
|
+
timestamp: string;
|
|
158
|
+
uptime: number;
|
|
159
|
+
version?: string;
|
|
160
|
+
dependencies?: Record<string, 'connected' | 'disconnected' | 'unknown'>;
|
|
161
|
+
}
|