@private.me/xbind 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -7
- package/dist-standalone/_deps/mldsa-wasm/dist/mldsa.js +1920 -1
- package/dist-standalone/_deps/shared/cjs/errors.js +729 -1
- package/dist-standalone/_deps/shared/cjs/index.js +463 -1
- package/dist-standalone/_deps/shared/cjs/types.js +315 -1
- package/dist-standalone/_deps/shared/errors.js +244 -1
- package/dist-standalone/_deps/shared/index.js +72 -1
- package/dist-standalone/_deps/shared/types.js +86 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/index.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/pagination.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/progress.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/search.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/types.js +1 -1
- package/dist-standalone/_deps/ux-helpers/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/index.js +1 -1
- package/dist-standalone/_deps/ux-helpers/pagination.js +1 -1
- package/dist-standalone/_deps/ux-helpers/progress.js +1 -1
- package/dist-standalone/_deps/ux-helpers/search.js +1 -1
- package/dist-standalone/_deps/xchange/auto-accept.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/auto-accept.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/errors.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/index.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/invite-client.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/lazy-init.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/trust-integration.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/xchange.js +1 -1
- package/dist-standalone/_deps/xchange/errors.js +1 -1
- package/dist-standalone/_deps/xchange/index.js +1 -1
- package/dist-standalone/_deps/xchange/invite-client.js +1 -1
- package/dist-standalone/_deps/xchange/lazy-init.js +1 -1
- package/dist-standalone/_deps/xchange/trust-integration.js +1 -1
- package/dist-standalone/_deps/xchange/xchange.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/discovery.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/errors.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/index.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/registry.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/schema.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/types.js +1 -1
- package/dist-standalone/_deps/xregistry/discovery.js +1 -1
- package/dist-standalone/_deps/xregistry/errors.js +1 -1
- package/dist-standalone/_deps/xregistry/index.js +1 -1
- package/dist-standalone/_deps/xregistry/registry.js +1 -1
- package/dist-standalone/_deps/xregistry/schema.js +1 -1
- package/dist-standalone/_deps/xregistry/types.js +1 -1
- package/dist-standalone/agent-call.js +659 -1
- package/dist-standalone/agent-sdk.js +328 -1
- package/dist-standalone/agent.js +1800 -1
- package/dist-standalone/approval.js +193 -1
- package/dist-standalone/async-iterators.js +382 -1
- package/dist-standalone/auth.js +219 -1
- package/dist-standalone/auto-accept.js +229 -1
- package/dist-standalone/backup-config.js +201 -1
- package/dist-standalone/backup.js +326 -1
- package/dist-standalone/batch-operations.js +388 -1
- package/dist-standalone/cancellation.js +477 -1
- package/dist-standalone/checkpoint.js +186 -1
- package/dist-standalone/circuit-breaker.js +468 -1
- package/dist-standalone/cjs/agent-call.js +701 -1
- package/dist-standalone/cjs/agent-sdk.js +332 -1
- package/dist-standalone/cjs/agent.js +1837 -1
- package/dist-standalone/cjs/approval.js +199 -1
- package/dist-standalone/cjs/async-iterators.js +392 -1
- package/dist-standalone/cjs/auth.js +225 -1
- package/dist-standalone/cjs/auto-accept.js +233 -1
- package/dist-standalone/cjs/backup-config.js +207 -1
- package/dist-standalone/cjs/backup.js +330 -1
- package/dist-standalone/cjs/batch-operations.js +397 -1
- package/dist-standalone/cjs/cancellation.js +490 -1
- package/dist-standalone/cjs/checkpoint.js +193 -1
- package/dist-standalone/cjs/circuit-breaker.js +476 -1
- package/dist-standalone/cjs/cli/init.js +492 -1
- package/dist-standalone/cjs/config-validation.js +522 -1
- package/dist-standalone/cjs/connect.js +312 -1
- package/dist-standalone/cjs/connection-pool.js +506 -1
- package/dist-standalone/cjs/correlation-id.js +339 -1
- package/dist-standalone/cjs/crypto-utils.js +176 -1
- package/dist-standalone/cjs/debug-mode.js +534 -1
- package/dist-standalone/cjs/did-document.js +101 -1
- package/dist-standalone/cjs/did-privateme.js +130 -1
- package/dist-standalone/cjs/did-web.js +201 -1
- package/dist-standalone/cjs/discovery.js +462 -1
- package/dist-standalone/cjs/dual-mode.js +251 -1
- package/dist-standalone/cjs/email-templates.js +313 -1
- package/dist-standalone/cjs/email-transport.js +239 -1
- package/dist-standalone/cjs/envelope.js +538 -1
- package/dist-standalone/cjs/errors.js +913 -1
- package/dist-standalone/cjs/event-emitter.js +461 -1
- package/dist-standalone/cjs/gateway-state.js +55 -1
- package/dist-standalone/cjs/gateway-transport.js +120 -1
- package/dist-standalone/cjs/graceful-degradation.js +403 -1
- package/dist-standalone/cjs/guardrails.js +223 -1
- package/dist-standalone/cjs/health-check.js +336 -1
- package/dist-standalone/cjs/http-compat.js +272 -1
- package/dist-standalone/cjs/http-status-map.js +571 -1
- package/dist-standalone/cjs/identity.js +645 -1
- package/dist-standalone/cjs/index.js +406 -1
- package/dist-standalone/cjs/invitation.js +421 -1
- package/dist-standalone/cjs/invite.js +328 -1
- package/dist-standalone/cjs/key-agreement.js +335 -1
- package/dist-standalone/cjs/lazy-init.js +300 -1
- package/dist-standalone/cjs/logger.js +291 -1
- package/dist-standalone/cjs/mdns-discovery.js +202 -1
- package/dist-standalone/cjs/nonce-store.js +80 -1
- package/dist-standalone/cjs/pairing-manager.js +223 -1
- package/dist-standalone/cjs/plugin-system.js +264 -1
- package/dist-standalone/cjs/plugins/logging.js +168 -1
- package/dist-standalone/cjs/plugins/metrics.js +181 -1
- package/dist-standalone/cjs/plugins/validation.js +302 -1
- package/dist-standalone/cjs/policy.js +320 -1
- package/dist-standalone/cjs/progress-callbacks.js +583 -1
- package/dist-standalone/cjs/redis-nonce-store.js +76 -1
- package/dist-standalone/cjs/registry-middleware.js +50 -1
- package/dist-standalone/cjs/retry-strategies.js +544 -1
- package/dist-standalone/cjs/retry-transport.js +102 -1
- package/dist-standalone/cjs/runtime/browser.js +533 -1
- package/dist-standalone/cjs/runtime/edge.js +526 -1
- package/dist-standalone/cjs/runtime/react-native.js +394 -1
- package/dist-standalone/cjs/security-policy.js +245 -1
- package/dist-standalone/cjs/serialization.js +1040 -1
- package/dist-standalone/cjs/split-channel.js +225 -1
- package/dist-standalone/cjs/subscription-proof.js +230 -1
- package/dist-standalone/cjs/succession.js +148 -1
- package/dist-standalone/cjs/timeouts.js +412 -1
- package/dist-standalone/cjs/trace-context.js +424 -1
- package/dist-standalone/cjs/trace-spans.js +495 -1
- package/dist-standalone/cjs/transport.js +63 -1
- package/dist-standalone/cjs/trust-registry.js +991 -1
- package/dist-standalone/cjs/types/error-response.js +56 -1
- package/dist-standalone/cjs/vault-auth.js +178 -1
- package/dist-standalone/cjs/vault-store-loader.js +194 -1
- package/dist-standalone/cjs/verify.js +25 -1
- package/dist-standalone/cjs/version-info.js +543 -1
- package/dist-standalone/cjs/xfetch.js +340 -1
- package/dist-standalone/cli/init.js +455 -1
- package/dist-standalone/cli/setup.js +514 -1
- package/dist-standalone/cli/types.js +27 -1
- package/dist-standalone/cli/xbind.js +148 -1
- package/dist-standalone/config-validation.js +513 -1
- package/dist-standalone/connect.js +274 -1
- package/dist-standalone/connection-pool.js +500 -1
- package/dist-standalone/correlation-id.js +326 -1
- package/dist-standalone/crypto-utils.js +157 -1
- package/dist-standalone/debug-mode.js +510 -1
- package/dist-standalone/did-document.js +96 -1
- package/dist-standalone/did-privateme.js +121 -1
- package/dist-standalone/did-web.js +196 -1
- package/dist-standalone/discovery.js +458 -1
- package/dist-standalone/dual-mode.js +247 -1
- package/dist-standalone/email-templates.js +309 -1
- package/dist-standalone/email-transport.js +232 -1
- package/dist-standalone/envelope.js +525 -1
- package/dist-standalone/errors.js +896 -1
- package/dist-standalone/event-emitter.js +456 -1
- package/dist-standalone/gateway-state.js +51 -1
- package/dist-standalone/gateway-transport.js +116 -1
- package/dist-standalone/graceful-degradation.js +396 -1
- package/dist-standalone/guardrails.js +216 -1
- package/dist-standalone/health-check.js +332 -1
- package/dist-standalone/http-compat.js +267 -1
- package/dist-standalone/http-status-map.js +561 -1
- package/dist-standalone/identity.js +619 -1
- package/dist-standalone/index.js +78 -1
- package/dist-standalone/invitation.js +415 -1
- package/dist-standalone/invite.js +324 -1
- package/dist-standalone/key-agreement.js +325 -1
- package/dist-standalone/lazy-init.js +295 -1
- package/dist-standalone/logger.js +285 -1
- package/dist-standalone/mdns-discovery.js +195 -1
- package/dist-standalone/nonce-store.js +76 -1
- package/dist-standalone/pairing-manager.js +219 -1
- package/dist-standalone/plugin-system.js +257 -1
- package/dist-standalone/plugins/logging.d.ts +84 -0
- package/dist-standalone/plugins/logging.js +163 -0
- package/dist-standalone/plugins/metrics.d.ts +111 -0
- package/dist-standalone/plugins/metrics.js +176 -0
- package/dist-standalone/plugins/validation.d.ts +104 -0
- package/dist-standalone/plugins/validation.js +297 -0
- package/dist-standalone/policy.js +315 -1
- package/dist-standalone/progress-callbacks.js +576 -1
- package/dist-standalone/redis-nonce-store.js +72 -1
- package/dist-standalone/registry-middleware.js +47 -1
- package/dist-standalone/retry-strategies.js +534 -1
- package/dist-standalone/retry-transport.js +98 -1
- package/dist-standalone/runtime/browser.d.ts +311 -0
- package/dist-standalone/runtime/browser.js +516 -0
- package/dist-standalone/runtime/edge.d.ts +282 -0
- package/dist-standalone/runtime/edge.js +511 -0
- package/dist-standalone/runtime/react-native.d.ts +157 -0
- package/dist-standalone/runtime/react-native.js +383 -0
- package/dist-standalone/security-policy.js +239 -1
- package/dist-standalone/serialization.js +1031 -1
- package/dist-standalone/split-channel.js +219 -1
- package/dist-standalone/subscription-proof.js +224 -1
- package/dist-standalone/succession.js +142 -1
- package/dist-standalone/timeouts.js +398 -1
- package/dist-standalone/trace-context.js +414 -1
- package/dist-standalone/trace-spans.js +488 -1
- package/dist-standalone/transport.js +59 -1
- package/dist-standalone/trust-registry.js +950 -1
- package/dist-standalone/types/error-response.d.ts +209 -0
- package/dist-standalone/types/error-response.js +52 -0
- package/dist-standalone/vault-auth.js +174 -1
- package/dist-standalone/vault-store-loader.js +187 -1
- package/dist-standalone/verify.js +16 -1
- package/dist-standalone/version-info.js +530 -1
- package/dist-standalone/xfetch.js +335 -1
- package/package.json +4 -10
- package/share1.dat +0 -0
- package/dist-standalone/_deps/mldsa-wasm/LICENSE +0 -24
- package/dist-standalone/_deps/mldsa-wasm/package.json +0 -46
- package/dist-standalone/_deps/shared/cjs/package.json +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/package.json +0 -1
- package/dist-standalone/_deps/xchange/cjs/package.json +0 -1
- package/dist-standalone/_deps/xregistry/cjs/package.json +0 -1
- package/dist-standalone/cjs/package.json +0 -3
- package/dist-standalone/package.json +0 -10
|
@@ -1 +1,50 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Express middleware for protecting trust registry endpoints with bearer auth.
|
|
4
|
+
*
|
|
5
|
+
* GET/HEAD requests pass through (public reads). POST/PUT/DELETE require
|
|
6
|
+
* a valid Bearer token matching the configured admin token.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import express from 'express';
|
|
11
|
+
* import { createRegistryAuthMiddleware } from '@private.me/xbind';
|
|
12
|
+
*
|
|
13
|
+
* const app = express();
|
|
14
|
+
* app.use('/registry', createRegistryAuthMiddleware(process.env.REGISTRY_ADMIN_TOKEN!));
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.createRegistryAuthMiddleware = createRegistryAuthMiddleware;
|
|
19
|
+
/**
|
|
20
|
+
* Create an Express-compatible middleware that protects write operations
|
|
21
|
+
* on registry endpoints with bearer token authentication.
|
|
22
|
+
*
|
|
23
|
+
* GET and HEAD requests pass through without authentication.
|
|
24
|
+
* POST, PUT, and DELETE require `Authorization: Bearer <token>`.
|
|
25
|
+
*
|
|
26
|
+
* @param token - The admin token to validate against.
|
|
27
|
+
* @returns Express middleware function.
|
|
28
|
+
*/
|
|
29
|
+
function createRegistryAuthMiddleware(token) {
|
|
30
|
+
return (req, res, next) => {
|
|
31
|
+
const method = req.method.toUpperCase();
|
|
32
|
+
if (method === 'GET' || method === 'HEAD') {
|
|
33
|
+
next();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const authHeader = typeof req.headers['authorization'] === 'string'
|
|
37
|
+
? req.headers['authorization']
|
|
38
|
+
: undefined;
|
|
39
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
40
|
+
res.status(401).json({ error: 'UNAUTHORIZED' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const provided = authHeader.slice('Bearer '.length);
|
|
44
|
+
if (provided !== token) {
|
|
45
|
+
res.status(401).json({ error: 'UNAUTHORIZED' });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
next();
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -1 +1,544 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module retry-strategies
|
|
4
|
+
* Configurable retry strategies for enhanced reliability.
|
|
5
|
+
*
|
|
6
|
+
* Provides flexible retry policies with exponential backoff, jitter, circuit breakers,
|
|
7
|
+
* and error-specific retry logic. Designed for production-grade fault tolerance.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { RetryStrategy, CircuitBreaker } from '@private.me/xbind/retry-strategies';
|
|
12
|
+
*
|
|
13
|
+
* // Exponential backoff with circuit breaker
|
|
14
|
+
* const strategy = RetryStrategy.exponentialBackoff({
|
|
15
|
+
* maxAttempts: 5,
|
|
16
|
+
* initialDelayMs: 1000,
|
|
17
|
+
* maxDelayMs: 30000,
|
|
18
|
+
* multiplier: 2,
|
|
19
|
+
* jitter: true
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const circuitBreaker = new CircuitBreaker({
|
|
23
|
+
* failureThreshold: 5,
|
|
24
|
+
* resetTimeoutMs: 60000
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.RetryStrategy = exports.CircuitBreaker = exports.NoRetryStrategy = exports.FixedDelayStrategy = exports.LinearBackoffStrategy = exports.ExponentialBackoffStrategy = void 0;
|
|
30
|
+
exports.executeWithRetry = executeWithRetry;
|
|
31
|
+
const shared_1 = require("../_deps/shared/index.js");
|
|
32
|
+
/* ── Retry Strategy Implementations ── */
|
|
33
|
+
/**
|
|
34
|
+
* Exponential backoff retry strategy.
|
|
35
|
+
*
|
|
36
|
+
* Delay formula: min(initialDelay * multiplier^attempt, maxDelay) ± jitter
|
|
37
|
+
*
|
|
38
|
+
* Best for: Network requests, API calls, database operations.
|
|
39
|
+
* Avoids overwhelming failing services with exponentially increasing delays.
|
|
40
|
+
*/
|
|
41
|
+
class ExponentialBackoffStrategy {
|
|
42
|
+
name = 'ExponentialBackoff';
|
|
43
|
+
maxAttempts;
|
|
44
|
+
initialDelayMs;
|
|
45
|
+
maxDelayMs;
|
|
46
|
+
multiplier;
|
|
47
|
+
jitter;
|
|
48
|
+
jitterFactor;
|
|
49
|
+
retryableErrors;
|
|
50
|
+
constructor(config = {}) {
|
|
51
|
+
this.maxAttempts = config.maxAttempts ?? 3;
|
|
52
|
+
this.initialDelayMs = config.initialDelayMs ?? 1000;
|
|
53
|
+
this.maxDelayMs = config.maxDelayMs ?? 30000;
|
|
54
|
+
this.multiplier = config.multiplier ?? 2;
|
|
55
|
+
this.jitter = config.jitter ?? true;
|
|
56
|
+
this.jitterFactor = config.jitterFactor ?? 0.2;
|
|
57
|
+
this.retryableErrors = config.retryableErrors
|
|
58
|
+
? new Set(config.retryableErrors)
|
|
59
|
+
: new Set(['SEND_FAILED', 'NETWORK_ERROR', 'RECIPIENT_UNREACHABLE', 'TIMEOUT']);
|
|
60
|
+
}
|
|
61
|
+
shouldRetry(context) {
|
|
62
|
+
// Check if we've exceeded max attempts
|
|
63
|
+
if (context.attempt >= this.maxAttempts) {
|
|
64
|
+
return {
|
|
65
|
+
shouldRetry: false,
|
|
66
|
+
reason: `Maximum attempts (${this.maxAttempts}) exceeded`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Check if error is retryable
|
|
70
|
+
if (!this.retryableErrors.has(context.error)) {
|
|
71
|
+
return {
|
|
72
|
+
shouldRetry: false,
|
|
73
|
+
reason: `Error type '${context.error}' is not retryable`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Calculate exponential backoff delay
|
|
77
|
+
const baseDelay = this.initialDelayMs * Math.pow(this.multiplier, context.attempt);
|
|
78
|
+
const cappedDelay = Math.min(baseDelay, this.maxDelayMs);
|
|
79
|
+
// Apply jitter if enabled
|
|
80
|
+
const finalDelay = this.jitter ? this.applyJitter(cappedDelay) : cappedDelay;
|
|
81
|
+
return {
|
|
82
|
+
shouldRetry: true,
|
|
83
|
+
delayMs: Math.round(finalDelay),
|
|
84
|
+
reason: `Attempt ${context.attempt + 1}/${this.maxAttempts}`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
applyJitter(delayMs) {
|
|
88
|
+
// Use crypto.getRandomValues for secure random jitter (OWASP-compliant)
|
|
89
|
+
const randomArray = new Uint32Array(1);
|
|
90
|
+
crypto.getRandomValues(randomArray);
|
|
91
|
+
const random = randomArray[0] / 0xffffffff; // Normalize to [0, 1]
|
|
92
|
+
// Apply jitter: delay ± (delay * jitterFactor)
|
|
93
|
+
const jitterRange = delayMs * this.jitterFactor;
|
|
94
|
+
const jitter = (random * 2 - 1) * jitterRange; // Range: [-jitterRange, +jitterRange]
|
|
95
|
+
return delayMs + jitter;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.ExponentialBackoffStrategy = ExponentialBackoffStrategy;
|
|
99
|
+
/**
|
|
100
|
+
* Linear backoff retry strategy.
|
|
101
|
+
*
|
|
102
|
+
* Delay formula: min(initialDelay + attempt * increment, maxDelay) ± jitter
|
|
103
|
+
*
|
|
104
|
+
* Best for: Rate-limited APIs, gradual load recovery.
|
|
105
|
+
* More predictable than exponential, suitable when service recovery is linear.
|
|
106
|
+
*/
|
|
107
|
+
class LinearBackoffStrategy {
|
|
108
|
+
name = 'LinearBackoff';
|
|
109
|
+
maxAttempts;
|
|
110
|
+
delayIncrementMs;
|
|
111
|
+
initialDelayMs;
|
|
112
|
+
maxDelayMs;
|
|
113
|
+
jitter;
|
|
114
|
+
jitterFactor;
|
|
115
|
+
retryableErrors;
|
|
116
|
+
constructor(config = {}) {
|
|
117
|
+
this.maxAttempts = config.maxAttempts ?? 3;
|
|
118
|
+
this.delayIncrementMs = config.delayIncrementMs ?? 1000;
|
|
119
|
+
this.initialDelayMs = config.initialDelayMs ?? 1000;
|
|
120
|
+
this.maxDelayMs = config.maxDelayMs ?? 30000;
|
|
121
|
+
this.jitter = config.jitter ?? true;
|
|
122
|
+
this.jitterFactor = config.jitterFactor ?? 0.1;
|
|
123
|
+
this.retryableErrors = config.retryableErrors
|
|
124
|
+
? new Set(config.retryableErrors)
|
|
125
|
+
: new Set(['SEND_FAILED', 'NETWORK_ERROR', 'RECIPIENT_UNREACHABLE', 'TIMEOUT']);
|
|
126
|
+
}
|
|
127
|
+
shouldRetry(context) {
|
|
128
|
+
if (context.attempt >= this.maxAttempts) {
|
|
129
|
+
return {
|
|
130
|
+
shouldRetry: false,
|
|
131
|
+
reason: `Maximum attempts (${this.maxAttempts}) exceeded`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (!this.retryableErrors.has(context.error)) {
|
|
135
|
+
return {
|
|
136
|
+
shouldRetry: false,
|
|
137
|
+
reason: `Error type '${context.error}' is not retryable`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Calculate linear backoff delay
|
|
141
|
+
const baseDelay = this.initialDelayMs + context.attempt * this.delayIncrementMs;
|
|
142
|
+
const cappedDelay = Math.min(baseDelay, this.maxDelayMs);
|
|
143
|
+
// Apply jitter if enabled
|
|
144
|
+
const finalDelay = this.jitter ? this.applyJitter(cappedDelay) : cappedDelay;
|
|
145
|
+
return {
|
|
146
|
+
shouldRetry: true,
|
|
147
|
+
delayMs: Math.round(finalDelay),
|
|
148
|
+
reason: `Attempt ${context.attempt + 1}/${this.maxAttempts}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
applyJitter(delayMs) {
|
|
152
|
+
const randomArray = new Uint32Array(1);
|
|
153
|
+
crypto.getRandomValues(randomArray);
|
|
154
|
+
const random = randomArray[0] / 0xffffffff;
|
|
155
|
+
const jitterRange = delayMs * this.jitterFactor;
|
|
156
|
+
const jitter = (random * 2 - 1) * jitterRange;
|
|
157
|
+
return delayMs + jitter;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.LinearBackoffStrategy = LinearBackoffStrategy;
|
|
161
|
+
/**
|
|
162
|
+
* Fixed delay retry strategy.
|
|
163
|
+
*
|
|
164
|
+
* Delay formula: delayMs ± jitter (constant for all attempts)
|
|
165
|
+
*
|
|
166
|
+
* Best for: Simple retry scenarios, testing.
|
|
167
|
+
* Predictable timing, no backoff complexity.
|
|
168
|
+
*/
|
|
169
|
+
class FixedDelayStrategy {
|
|
170
|
+
name = 'FixedDelay';
|
|
171
|
+
maxAttempts;
|
|
172
|
+
delayMs;
|
|
173
|
+
jitter;
|
|
174
|
+
jitterFactor;
|
|
175
|
+
retryableErrors;
|
|
176
|
+
constructor(config = {}) {
|
|
177
|
+
this.maxAttempts = config.maxAttempts ?? 3;
|
|
178
|
+
this.delayMs = config.delayMs ?? 1000;
|
|
179
|
+
this.jitter = config.jitter ?? false;
|
|
180
|
+
this.jitterFactor = config.jitterFactor ?? 0.1;
|
|
181
|
+
this.retryableErrors = config.retryableErrors
|
|
182
|
+
? new Set(config.retryableErrors)
|
|
183
|
+
: new Set(['SEND_FAILED', 'NETWORK_ERROR', 'RECIPIENT_UNREACHABLE', 'TIMEOUT']);
|
|
184
|
+
}
|
|
185
|
+
shouldRetry(context) {
|
|
186
|
+
if (context.attempt >= this.maxAttempts) {
|
|
187
|
+
return {
|
|
188
|
+
shouldRetry: false,
|
|
189
|
+
reason: `Maximum attempts (${this.maxAttempts}) exceeded`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (!this.retryableErrors.has(context.error)) {
|
|
193
|
+
return {
|
|
194
|
+
shouldRetry: false,
|
|
195
|
+
reason: `Error type '${context.error}' is not retryable`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const finalDelay = this.jitter ? this.applyJitter(this.delayMs) : this.delayMs;
|
|
199
|
+
return {
|
|
200
|
+
shouldRetry: true,
|
|
201
|
+
delayMs: Math.round(finalDelay),
|
|
202
|
+
reason: `Attempt ${context.attempt + 1}/${this.maxAttempts}`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
applyJitter(delayMs) {
|
|
206
|
+
const randomArray = new Uint32Array(1);
|
|
207
|
+
crypto.getRandomValues(randomArray);
|
|
208
|
+
const random = randomArray[0] / 0xffffffff;
|
|
209
|
+
const jitterRange = delayMs * this.jitterFactor;
|
|
210
|
+
const jitter = (random * 2 - 1) * jitterRange;
|
|
211
|
+
return delayMs + jitter;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
exports.FixedDelayStrategy = FixedDelayStrategy;
|
|
215
|
+
/**
|
|
216
|
+
* No retry strategy (fail immediately).
|
|
217
|
+
*
|
|
218
|
+
* Best for: Operations that must succeed on first try, testing.
|
|
219
|
+
*/
|
|
220
|
+
class NoRetryStrategy {
|
|
221
|
+
name = 'NoRetry';
|
|
222
|
+
shouldRetry(_context) {
|
|
223
|
+
return {
|
|
224
|
+
shouldRetry: false,
|
|
225
|
+
reason: 'No retry policy - fail immediately',
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
exports.NoRetryStrategy = NoRetryStrategy;
|
|
230
|
+
/* ── Circuit Breaker ── */
|
|
231
|
+
/**
|
|
232
|
+
* Circuit breaker for preventing cascading failures.
|
|
233
|
+
*
|
|
234
|
+
* States:
|
|
235
|
+
* - CLOSED: Normal operation, requests pass through
|
|
236
|
+
* - OPEN: Circuit tripped, requests fail immediately
|
|
237
|
+
* - HALF_OPEN: Testing recovery, limited requests allowed
|
|
238
|
+
*
|
|
239
|
+
* Best practices:
|
|
240
|
+
* - Use with retry strategies for comprehensive fault tolerance
|
|
241
|
+
* - Monitor circuit state changes for alerting
|
|
242
|
+
* - Tune thresholds based on service characteristics
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* const breaker = new CircuitBreaker({
|
|
247
|
+
* failureThreshold: 5,
|
|
248
|
+
* resetTimeoutMs: 60000,
|
|
249
|
+
* successThreshold: 2
|
|
250
|
+
* });
|
|
251
|
+
*
|
|
252
|
+
* // Check before operation
|
|
253
|
+
* if (breaker.allowRequest()) {
|
|
254
|
+
* const result = await operation();
|
|
255
|
+
* if (result.ok) {
|
|
256
|
+
* breaker.recordSuccess();
|
|
257
|
+
* } else {
|
|
258
|
+
* breaker.recordFailure();
|
|
259
|
+
* }
|
|
260
|
+
* }
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
class CircuitBreaker {
|
|
264
|
+
state = 'CLOSED';
|
|
265
|
+
consecutiveFailures = 0;
|
|
266
|
+
consecutiveSuccesses = 0;
|
|
267
|
+
openedAt;
|
|
268
|
+
totalRequests = 0;
|
|
269
|
+
totalFailures = 0;
|
|
270
|
+
totalSuccesses = 0;
|
|
271
|
+
failureTimestamps = [];
|
|
272
|
+
failureThreshold;
|
|
273
|
+
resetTimeoutMs;
|
|
274
|
+
successThreshold;
|
|
275
|
+
windowMs;
|
|
276
|
+
constructor(config = {}) {
|
|
277
|
+
this.failureThreshold = config.failureThreshold ?? 5;
|
|
278
|
+
this.resetTimeoutMs = config.resetTimeoutMs ?? 60000;
|
|
279
|
+
this.successThreshold = config.successThreshold ?? 2;
|
|
280
|
+
this.windowMs = config.windowMs ?? 60000;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Check if request should be allowed through circuit.
|
|
284
|
+
*
|
|
285
|
+
* @returns True if request should proceed, false if circuit is open
|
|
286
|
+
*/
|
|
287
|
+
allowRequest() {
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
// Clean up old failure timestamps outside the window
|
|
290
|
+
this.failureTimestamps = this.failureTimestamps.filter(timestamp => now - timestamp < this.windowMs);
|
|
291
|
+
// CLOSED: Allow all requests
|
|
292
|
+
if (this.state === 'CLOSED') {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
// OPEN: Check if reset timeout has elapsed
|
|
296
|
+
if (this.state === 'OPEN') {
|
|
297
|
+
if (this.openedAt && now - this.openedAt >= this.resetTimeoutMs) {
|
|
298
|
+
// Transition to HALF_OPEN
|
|
299
|
+
this.state = 'HALF_OPEN';
|
|
300
|
+
this.consecutiveSuccesses = 0;
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
return false; // Circuit still open
|
|
304
|
+
}
|
|
305
|
+
// HALF_OPEN: Allow limited requests to test recovery
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Record a successful operation.
|
|
310
|
+
*/
|
|
311
|
+
recordSuccess() {
|
|
312
|
+
this.totalRequests++;
|
|
313
|
+
this.totalSuccesses++;
|
|
314
|
+
this.consecutiveFailures = 0;
|
|
315
|
+
if (this.state === 'HALF_OPEN') {
|
|
316
|
+
this.consecutiveSuccesses++;
|
|
317
|
+
// Enough successes to close circuit
|
|
318
|
+
if (this.consecutiveSuccesses >= this.successThreshold) {
|
|
319
|
+
this.state = 'CLOSED';
|
|
320
|
+
this.consecutiveSuccesses = 0;
|
|
321
|
+
this.openedAt = undefined;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Record a failed operation.
|
|
327
|
+
*/
|
|
328
|
+
recordFailure() {
|
|
329
|
+
this.totalRequests++;
|
|
330
|
+
this.totalFailures++;
|
|
331
|
+
this.consecutiveFailures++;
|
|
332
|
+
this.consecutiveSuccesses = 0;
|
|
333
|
+
const now = Date.now();
|
|
334
|
+
this.failureTimestamps.push(now);
|
|
335
|
+
// Trip circuit if threshold exceeded
|
|
336
|
+
if (this.state === 'CLOSED' || this.state === 'HALF_OPEN') {
|
|
337
|
+
// Count failures within the time window
|
|
338
|
+
const recentFailures = this.failureTimestamps.filter(timestamp => now - timestamp < this.windowMs).length;
|
|
339
|
+
if (recentFailures >= this.failureThreshold) {
|
|
340
|
+
this.state = 'OPEN';
|
|
341
|
+
this.openedAt = now;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Get current circuit breaker statistics.
|
|
347
|
+
*/
|
|
348
|
+
getStats() {
|
|
349
|
+
return {
|
|
350
|
+
state: this.state,
|
|
351
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
352
|
+
consecutiveSuccesses: this.consecutiveSuccesses,
|
|
353
|
+
openedAt: this.openedAt,
|
|
354
|
+
totalRequests: this.totalRequests,
|
|
355
|
+
totalFailures: this.totalFailures,
|
|
356
|
+
totalSuccesses: this.totalSuccesses,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Reset circuit breaker to initial state.
|
|
361
|
+
*/
|
|
362
|
+
reset() {
|
|
363
|
+
this.state = 'CLOSED';
|
|
364
|
+
this.consecutiveFailures = 0;
|
|
365
|
+
this.consecutiveSuccesses = 0;
|
|
366
|
+
this.openedAt = undefined;
|
|
367
|
+
this.totalRequests = 0;
|
|
368
|
+
this.totalFailures = 0;
|
|
369
|
+
this.totalSuccesses = 0;
|
|
370
|
+
this.failureTimestamps = [];
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Force circuit to open (for testing or manual intervention).
|
|
374
|
+
*/
|
|
375
|
+
forceOpen() {
|
|
376
|
+
this.state = 'OPEN';
|
|
377
|
+
this.openedAt = Date.now();
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Force circuit to close (for testing or manual recovery).
|
|
381
|
+
*/
|
|
382
|
+
forceClose() {
|
|
383
|
+
this.state = 'CLOSED';
|
|
384
|
+
this.consecutiveFailures = 0;
|
|
385
|
+
this.consecutiveSuccesses = 0;
|
|
386
|
+
this.openedAt = undefined;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
exports.CircuitBreaker = CircuitBreaker;
|
|
390
|
+
/* ── Factory Functions ── */
|
|
391
|
+
/**
|
|
392
|
+
* Factory for creating retry strategies with common configurations.
|
|
393
|
+
*/
|
|
394
|
+
class RetryStrategy {
|
|
395
|
+
/**
|
|
396
|
+
* Create exponential backoff strategy.
|
|
397
|
+
*
|
|
398
|
+
* @param config - Configuration options
|
|
399
|
+
* @returns Exponential backoff strategy
|
|
400
|
+
*/
|
|
401
|
+
static exponentialBackoff(config) {
|
|
402
|
+
return new ExponentialBackoffStrategy(config);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Create linear backoff strategy.
|
|
406
|
+
*
|
|
407
|
+
* @param config - Configuration options
|
|
408
|
+
* @returns Linear backoff strategy
|
|
409
|
+
*/
|
|
410
|
+
static linearBackoff(config) {
|
|
411
|
+
return new LinearBackoffStrategy(config);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Create fixed delay strategy.
|
|
415
|
+
*
|
|
416
|
+
* @param config - Configuration options
|
|
417
|
+
* @returns Fixed delay strategy
|
|
418
|
+
*/
|
|
419
|
+
static fixedDelay(config) {
|
|
420
|
+
return new FixedDelayStrategy(config);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Create no retry strategy (fail immediately).
|
|
424
|
+
*
|
|
425
|
+
* @returns No retry strategy
|
|
426
|
+
*/
|
|
427
|
+
static noRetry() {
|
|
428
|
+
return new NoRetryStrategy();
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Create aggressive retry strategy for critical operations.
|
|
432
|
+
*
|
|
433
|
+
* Config: 5 attempts, 500ms initial, max 10s, exponential backoff.
|
|
434
|
+
*
|
|
435
|
+
* @returns Aggressive retry strategy
|
|
436
|
+
*/
|
|
437
|
+
static aggressive() {
|
|
438
|
+
return new ExponentialBackoffStrategy({
|
|
439
|
+
maxAttempts: 5,
|
|
440
|
+
initialDelayMs: 500,
|
|
441
|
+
maxDelayMs: 10000,
|
|
442
|
+
multiplier: 2,
|
|
443
|
+
jitter: true,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Create conservative retry strategy for non-critical operations.
|
|
448
|
+
*
|
|
449
|
+
* Config: 2 attempts, 2s initial, max 5s, linear backoff.
|
|
450
|
+
*
|
|
451
|
+
* @returns Conservative retry strategy
|
|
452
|
+
*/
|
|
453
|
+
static conservative() {
|
|
454
|
+
return new LinearBackoffStrategy({
|
|
455
|
+
maxAttempts: 2,
|
|
456
|
+
initialDelayMs: 2000,
|
|
457
|
+
delayIncrementMs: 1000,
|
|
458
|
+
maxDelayMs: 5000,
|
|
459
|
+
jitter: true,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Create default retry strategy (balanced).
|
|
464
|
+
*
|
|
465
|
+
* Config: 3 attempts, 1s initial, max 30s, exponential backoff.
|
|
466
|
+
*
|
|
467
|
+
* @returns Default retry strategy
|
|
468
|
+
*/
|
|
469
|
+
static default() {
|
|
470
|
+
return new ExponentialBackoffStrategy({
|
|
471
|
+
maxAttempts: 3,
|
|
472
|
+
initialDelayMs: 1000,
|
|
473
|
+
maxDelayMs: 30000,
|
|
474
|
+
multiplier: 2,
|
|
475
|
+
jitter: true,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
exports.RetryStrategy = RetryStrategy;
|
|
480
|
+
/* ── Retry Executor ── */
|
|
481
|
+
/**
|
|
482
|
+
* Execute an operation with retry strategy and optional circuit breaker.
|
|
483
|
+
*
|
|
484
|
+
* @param operation - Async operation to execute
|
|
485
|
+
* @param strategy - Retry strategy to use
|
|
486
|
+
* @param circuitBreaker - Optional circuit breaker
|
|
487
|
+
* @returns Result of the operation
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```typescript
|
|
491
|
+
* const result = await executeWithRetry(
|
|
492
|
+
* async () => transport.send(envelope, did),
|
|
493
|
+
* RetryStrategy.exponentialBackoff(),
|
|
494
|
+
* circuitBreaker
|
|
495
|
+
* );
|
|
496
|
+
* ```
|
|
497
|
+
*/
|
|
498
|
+
async function executeWithRetry(operation, strategy, circuitBreaker) {
|
|
499
|
+
const startTime = Date.now();
|
|
500
|
+
let attempt = 0;
|
|
501
|
+
let lastError = 'NETWORK_ERROR'; // Default error
|
|
502
|
+
while (true) {
|
|
503
|
+
// Check circuit breaker
|
|
504
|
+
if (circuitBreaker && !circuitBreaker.allowRequest()) {
|
|
505
|
+
return (0, shared_1.err)('SEND_FAILED');
|
|
506
|
+
}
|
|
507
|
+
// Execute operation
|
|
508
|
+
const result = await operation();
|
|
509
|
+
if (result.ok) {
|
|
510
|
+
// Success - record and return
|
|
511
|
+
circuitBreaker?.recordSuccess();
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
514
|
+
// Failure - record error
|
|
515
|
+
lastError = result.error;
|
|
516
|
+
circuitBreaker?.recordFailure();
|
|
517
|
+
// Check retry decision
|
|
518
|
+
const elapsedMs = Date.now() - startTime;
|
|
519
|
+
const context = {
|
|
520
|
+
attempt,
|
|
521
|
+
error: lastError,
|
|
522
|
+
startTime,
|
|
523
|
+
elapsedMs,
|
|
524
|
+
};
|
|
525
|
+
const decision = strategy.shouldRetry(context);
|
|
526
|
+
if (!decision.shouldRetry) {
|
|
527
|
+
// No more retries - return last error
|
|
528
|
+
return (0, shared_1.err)(lastError);
|
|
529
|
+
}
|
|
530
|
+
// Wait before retry
|
|
531
|
+
if (decision.delayMs && decision.delayMs > 0) {
|
|
532
|
+
await sleep(decision.delayMs);
|
|
533
|
+
}
|
|
534
|
+
attempt++;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Sleep for specified milliseconds.
|
|
539
|
+
*
|
|
540
|
+
* @param ms - Duration in milliseconds
|
|
541
|
+
*/
|
|
542
|
+
function sleep(ms) {
|
|
543
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
544
|
+
}
|