@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.
Files changed (122) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +51 -0
  3. package/lib/constants/ai-providers.d.ts +41 -0
  4. package/lib/constants/ai-providers.js +88 -0
  5. package/lib/constants/http-status.d.ts +24 -0
  6. package/lib/constants/http-status.js +29 -0
  7. package/lib/constants/index.d.ts +3 -0
  8. package/lib/constants/index.js +22 -0
  9. package/lib/constants/time.d.ts +10 -0
  10. package/lib/constants/time.js +16 -0
  11. package/lib/errors/app-errors.d.ts +30 -0
  12. package/lib/errors/app-errors.js +62 -0
  13. package/lib/errors/index.d.ts +1 -0
  14. package/lib/errors/index.js +20 -0
  15. package/lib/helpers/access-helpers.d.ts +40 -0
  16. package/lib/helpers/access-helpers.js +56 -0
  17. package/lib/helpers/crud-helpers.d.ts +16 -0
  18. package/lib/helpers/crud-helpers.js +34 -0
  19. package/lib/helpers/index.d.ts +4 -0
  20. package/lib/helpers/index.js +23 -0
  21. package/lib/helpers/mask-helpers.d.ts +33 -0
  22. package/lib/helpers/mask-helpers.js +54 -0
  23. package/lib/helpers/sse-helpers.d.ts +13 -0
  24. package/lib/helpers/sse-helpers.js +40 -0
  25. package/lib/index.d.ts +57 -0
  26. package/lib/index.js +86 -0
  27. package/lib/middleware/auth.d.ts +50 -0
  28. package/lib/middleware/auth.js +171 -0
  29. package/lib/middleware/index.d.ts +1 -0
  30. package/lib/middleware/index.js +20 -0
  31. package/lib/openapi/extend-zod.d.ts +1 -0
  32. package/lib/openapi/extend-zod.js +8 -0
  33. package/lib/openapi/index.d.ts +2 -0
  34. package/lib/openapi/index.js +10 -0
  35. package/lib/openapi/registry.d.ts +17 -0
  36. package/lib/openapi/registry.js +42 -0
  37. package/lib/openapi/routes/billing-routes.d.ts +1 -0
  38. package/lib/openapi/routes/billing-routes.js +69 -0
  39. package/lib/openapi/routes/index.d.ts +5 -0
  40. package/lib/openapi/routes/index.js +22 -0
  41. package/lib/openapi/routes/message-routes.d.ts +1 -0
  42. package/lib/openapi/routes/message-routes.js +108 -0
  43. package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
  44. package/lib/openapi/routes/pipeline-routes.js +90 -0
  45. package/lib/openapi/routes/plugin-routes.d.ts +1 -0
  46. package/lib/openapi/routes/plugin-routes.js +99 -0
  47. package/lib/openapi/routes/quota-routes.d.ts +1 -0
  48. package/lib/openapi/routes/quota-routes.js +65 -0
  49. package/lib/openapi/schema-registry.d.ts +25 -0
  50. package/lib/openapi/schema-registry.js +95 -0
  51. package/lib/routes/health.d.ts +47 -0
  52. package/lib/routes/health.js +81 -0
  53. package/lib/routes/index.d.ts +1 -0
  54. package/lib/routes/index.js +20 -0
  55. package/lib/services/admin-audit.d.ts +13 -0
  56. package/lib/services/admin-audit.js +31 -0
  57. package/lib/services/cache-service.d.ts +108 -0
  58. package/lib/services/cache-service.js +212 -0
  59. package/lib/services/compliance-client.d.ts +46 -0
  60. package/lib/services/compliance-client.js +102 -0
  61. package/lib/services/compliance-event-subscriber.d.ts +11 -0
  62. package/lib/services/compliance-event-subscriber.js +60 -0
  63. package/lib/services/compliance-queue.d.ts +11 -0
  64. package/lib/services/compliance-queue.js +38 -0
  65. package/lib/services/entity-events.d.ts +44 -0
  66. package/lib/services/entity-events.js +63 -0
  67. package/lib/services/http-client.d.ts +108 -0
  68. package/lib/services/http-client.js +285 -0
  69. package/lib/services/index.d.ts +10 -0
  70. package/lib/services/index.js +40 -0
  71. package/lib/services/quota.d.ts +59 -0
  72. package/lib/services/quota.js +137 -0
  73. package/lib/services/retry-strategy.d.ts +74 -0
  74. package/lib/services/retry-strategy.js +127 -0
  75. package/lib/types/billing.d.ts +47 -0
  76. package/lib/types/billing.js +5 -0
  77. package/lib/types/common.d.ts +161 -0
  78. package/lib/types/common.js +53 -0
  79. package/lib/types/error-codes.d.ts +38 -0
  80. package/lib/types/error-codes.js +77 -0
  81. package/lib/types/feature-flags.d.ts +38 -0
  82. package/lib/types/feature-flags.js +107 -0
  83. package/lib/types/http.d.ts +37 -0
  84. package/lib/types/http.js +5 -0
  85. package/lib/types/index.d.ts +7 -0
  86. package/lib/types/index.js +26 -0
  87. package/lib/types/pipeline.d.ts +70 -0
  88. package/lib/types/pipeline.js +44 -0
  89. package/lib/types/quota-tiers.d.ts +23 -0
  90. package/lib/types/quota-tiers.js +26 -0
  91. package/lib/utils/alias-resolver.d.ts +16 -0
  92. package/lib/utils/alias-resolver.js +49 -0
  93. package/lib/utils/headers.d.ts +18 -0
  94. package/lib/utils/headers.js +24 -0
  95. package/lib/utils/identity.d.ts +61 -0
  96. package/lib/utils/identity.js +75 -0
  97. package/lib/utils/index.d.ts +7 -0
  98. package/lib/utils/index.js +26 -0
  99. package/lib/utils/logger.d.ts +28 -0
  100. package/lib/utils/logger.js +77 -0
  101. package/lib/utils/object.d.ts +13 -0
  102. package/lib/utils/object.js +21 -0
  103. package/lib/utils/params.d.ts +89 -0
  104. package/lib/utils/params.js +148 -0
  105. package/lib/utils/response.d.ts +142 -0
  106. package/lib/utils/response.js +237 -0
  107. package/lib/validation/ai-schemas.d.ts +61 -0
  108. package/lib/validation/ai-schemas.js +81 -0
  109. package/lib/validation/common-schemas.d.ts +72 -0
  110. package/lib/validation/common-schemas.js +58 -0
  111. package/lib/validation/index.d.ts +6 -0
  112. package/lib/validation/index.js +25 -0
  113. package/lib/validation/message-schemas.d.ts +79 -0
  114. package/lib/validation/message-schemas.js +42 -0
  115. package/lib/validation/middleware.d.ts +60 -0
  116. package/lib/validation/middleware.js +77 -0
  117. package/lib/validation/pipeline-schemas.d.ts +135 -0
  118. package/lib/validation/pipeline-schemas.js +85 -0
  119. package/lib/validation/plugin-schemas.d.ts +127 -0
  120. package/lib/validation/plugin-schemas.js +84 -0
  121. package/openapi.yaml +292 -0
  122. 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
+ }