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