@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.
Files changed (217) hide show
  1. package/README.md +55 -7
  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.d.ts +84 -0
  174. package/dist-standalone/plugins/logging.js +163 -0
  175. package/dist-standalone/plugins/metrics.d.ts +111 -0
  176. package/dist-standalone/plugins/metrics.js +176 -0
  177. package/dist-standalone/plugins/validation.d.ts +104 -0
  178. package/dist-standalone/plugins/validation.js +297 -0
  179. package/dist-standalone/policy.js +315 -1
  180. package/dist-standalone/progress-callbacks.js +576 -1
  181. package/dist-standalone/redis-nonce-store.js +72 -1
  182. package/dist-standalone/registry-middleware.js +47 -1
  183. package/dist-standalone/retry-strategies.js +534 -1
  184. package/dist-standalone/retry-transport.js +98 -1
  185. package/dist-standalone/runtime/browser.d.ts +311 -0
  186. package/dist-standalone/runtime/browser.js +516 -0
  187. package/dist-standalone/runtime/edge.d.ts +282 -0
  188. package/dist-standalone/runtime/edge.js +511 -0
  189. package/dist-standalone/runtime/react-native.d.ts +157 -0
  190. package/dist-standalone/runtime/react-native.js +383 -0
  191. package/dist-standalone/security-policy.js +239 -1
  192. package/dist-standalone/serialization.js +1031 -1
  193. package/dist-standalone/split-channel.js +219 -1
  194. package/dist-standalone/subscription-proof.js +224 -1
  195. package/dist-standalone/succession.js +142 -1
  196. package/dist-standalone/timeouts.js +398 -1
  197. package/dist-standalone/trace-context.js +414 -1
  198. package/dist-standalone/trace-spans.js +488 -1
  199. package/dist-standalone/transport.js +59 -1
  200. package/dist-standalone/trust-registry.js +950 -1
  201. package/dist-standalone/types/error-response.d.ts +209 -0
  202. package/dist-standalone/types/error-response.js +52 -0
  203. package/dist-standalone/vault-auth.js +174 -1
  204. package/dist-standalone/vault-store-loader.js +187 -1
  205. package/dist-standalone/verify.js +16 -1
  206. package/dist-standalone/version-info.js +530 -1
  207. package/dist-standalone/xfetch.js +335 -1
  208. package/package.json +4 -10
  209. package/share1.dat +0 -0
  210. package/dist-standalone/_deps/mldsa-wasm/LICENSE +0 -24
  211. package/dist-standalone/_deps/mldsa-wasm/package.json +0 -46
  212. package/dist-standalone/_deps/shared/cjs/package.json +0 -1
  213. package/dist-standalone/_deps/ux-helpers/cjs/package.json +0 -1
  214. package/dist-standalone/_deps/xchange/cjs/package.json +0 -1
  215. package/dist-standalone/_deps/xregistry/cjs/package.json +0 -1
  216. package/dist-standalone/cjs/package.json +0 -3
  217. package/dist-standalone/package.json +0 -10
@@ -1 +1,500 @@
1
- export class ConnectionPool{options;connections=new Map;metrics;cleanupInterval=null;constructor(t={}){this.options={maxConnections:t.maxConnections??10,minConnections:t.minConnections??2,keepAliveTimeout:t.keepAliveTimeout??3e4,idleTimeout:t.idleTimeout??6e4,requestTimeout:t.requestTimeout??3e4,enableMetrics:t.enableMetrics??!0,retryOnFailure:t.retryOnFailure??!0,maxRetries:t.maxRetries??3},this.metrics={totalRequests:0,reuseCount:0,failedRequests:0,requestDurations:[],byOrigin:new Map},this.startCleanup()}async fetch(t,e){const s=Date.now(),n=this.getOrigin(t);try{const o=await this.acquireConnection(n);this.options.enableMetrics&&(this.metrics.totalRequests++,o.requestCount>0&&this.metrics.reuseCount++);const i={...e,signal:e?.signal??o.controller.signal,keepalive:!0},r=await fetch(t,i);if(o.requestCount++,o.lastUsedAt=Date.now(),o.state="idle",this.options.enableMetrics){const t=Date.now()-s;this.recordMetrics(n,t)}return r}catch(s){if(this.options.enableMetrics&&this.metrics.failedRequests++,this.options.retryOnFailure&&!0!==e?.signal?.aborted)return this.retryRequest(t,e,1);throw s}}async retryRequest(t,e,s){if(s>this.options.maxRetries)throw new Error(`Request failed after ${this.options.maxRetries} retries: ${t}`);const n=100*Math.pow(2,s-1);await new Promise(t=>setTimeout(t,n));const o=this.getOrigin(t);try{const s=await this.acquireConnection(o),n={...e,signal:e?.signal??s.controller.signal,keepalive:!0},i=await fetch(t,n);return s.requestCount++,s.lastUsedAt=Date.now(),s.state="idle",i}catch(n){if(s>=this.options.maxRetries)throw new Error(`Request failed after ${this.options.maxRetries} retries: ${t}`);return this.retryRequest(t,e,s+1)}}async acquireConnection(t){let e=this.connections.get(t);e||(e=[],this.connections.set(t,e));const s=e.find(t=>"idle"===t.state);if(s)return s.state="active",s;if(e.length<this.options.maxConnections){const s=this.createConnection(t);return e.push(s),s}return this.waitForConnection(t)}async waitForConnection(t){const e=Date.now(),s=this.options.requestTimeout;for(;Date.now()-e<s;){const e=this.connections.get(t);if(!e)throw new Error(`Connection pool for ${t} disappeared`);const s=e.find(t=>"idle"===t.state);if(s)return s.state="active",s;await new Promise(t=>setTimeout(t,10))}throw new Error(`Timeout waiting for connection to ${t} after ${s}ms`)}createConnection(t){const e=new Uint8Array(8);crypto.getRandomValues(e);return{id:`conn_${Array.from(e).map(t=>t.toString(16).padStart(2,"0")).join("")}`,origin:t,createdAt:Date.now(),lastUsedAt:Date.now(),requestCount:0,state:"active",controller:new AbortController}}getOrigin(t){try{const e=new URL(t);return`${e.protocol}//${e.host}`}catch(e){throw new Error(`Invalid URL: ${t}`)}}recordMetrics(t,e){this.metrics.requestDurations.push(e),this.metrics.requestDurations.length>1e3&&this.metrics.requestDurations.shift();let s=this.metrics.byOrigin.get(t);s||(s={requests:0,durations:[]},this.metrics.byOrigin.set(t,s)),s.requests++,s.durations.push(e),s.durations.length>100&&s.durations.shift()}getMetrics(){let t=0,e=0,s=0;const n=new Map;for(const[o,i]of this.connections.entries()){t+=i.length,e+=i.filter(t=>"active"===t.state).length,s+=i.filter(t=>"idle"===t.state).length;const r=this.metrics.byOrigin.get(o);if(r){const t=r.durations.length>0?r.durations.reduce((t,e)=>t+e,0)/r.durations.length:0;n.set(o,{connections:i.length,requests:r.requests,avgDuration:Math.round(t)})}}const o=this.metrics.requestDurations.length>0?Math.round(this.metrics.requestDurations.reduce((t,e)=>t+e,0)/this.metrics.requestDurations.length):0,i=this.metrics.totalRequests>0?this.metrics.reuseCount/this.metrics.totalRequests:0;return{totalConnections:t,activeConnections:e,idleConnections:s,totalRequests:this.metrics.totalRequests,reuseCount:this.metrics.reuseCount,failedRequests:this.metrics.failedRequests,avgRequestDuration:o,hitRate:i,byOrigin:n}}resetMetrics(){this.metrics={totalRequests:0,reuseCount:0,failedRequests:0,requestDurations:[],byOrigin:new Map}}startCleanup(){this.cleanupInterval=setInterval(()=>{this.cleanup()},1e4)}cleanup(){const t=Date.now();for(const[e,s]of this.connections.entries()){const n=s.filter(e=>t-e.lastUsedAt>this.options.idleTimeout&&"idle"===e.state?(e.controller.abort(),e.state="closed",!1):"closed"!==e.state);for(;n.length<this.options.minConnections&&n.length<this.options.maxConnections;){const t=this.createConnection(e);t.state="idle",n.push(t)}n.length>0?this.connections.set(e,n):this.connections.delete(e)}}cleanupNow(){this.cleanup()}async close(){this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null);for(const[t,e]of this.connections.entries())for(const t of e)t.controller.abort(),t.state="closed";this.connections.clear()}getConnectionCount(t){const e=this.connections.get(t);return e?e.length:0}getOrigins(){return Array.from(this.connections.keys())}isHealthy(){const t=this.getMetrics();return!((t.totalRequests>0?t.failedRequests/t.totalRequests:0)>.1)&&!(t.avgRequestDuration>5e3)}}let globalPool=null;export function getGlobalPool(t){return globalPool||(globalPool=new ConnectionPool(t)),globalPool}export async function resetGlobalPool(){globalPool&&(await globalPool.close(),globalPool=null)}
1
+ /**
2
+ * @module connection-pool
3
+ * HTTP connection pooling for xBind registry/gateway requests
4
+ *
5
+ * Provides connection reuse, keep-alive support, and automatic cleanup
6
+ * to enhance performance for applications making frequent requests.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const pool = new ConnectionPool({
11
+ * maxConnections: 50,
12
+ * keepAliveTimeout: 30000,
13
+ * });
14
+ *
15
+ * const response = await pool.fetch('https://api.example.com/data');
16
+ * const data = await response.json();
17
+ * ```
18
+ */
19
+ /**
20
+ * Connection pool for HTTP requests
21
+ *
22
+ * Manages connection lifecycle, keep-alive, and automatic cleanup
23
+ * to optimize performance for frequent requests to the same origins.
24
+ */
25
+ export class ConnectionPool {
26
+ options;
27
+ connections = new Map();
28
+ metrics;
29
+ cleanupInterval = null;
30
+ /**
31
+ * Create a new connection pool
32
+ *
33
+ * @param options - Pool configuration options
34
+ */
35
+ constructor(options = {}) {
36
+ this.options = {
37
+ maxConnections: options.maxConnections ?? 10,
38
+ minConnections: options.minConnections ?? 2,
39
+ keepAliveTimeout: options.keepAliveTimeout ?? 30000,
40
+ idleTimeout: options.idleTimeout ?? 60000,
41
+ requestTimeout: options.requestTimeout ?? 30000,
42
+ enableMetrics: options.enableMetrics ?? true,
43
+ retryOnFailure: options.retryOnFailure ?? true,
44
+ maxRetries: options.maxRetries ?? 3,
45
+ };
46
+ this.metrics = {
47
+ totalRequests: 0,
48
+ reuseCount: 0,
49
+ failedRequests: 0,
50
+ requestDurations: [],
51
+ byOrigin: new Map(),
52
+ };
53
+ // Start automatic cleanup
54
+ this.startCleanup();
55
+ }
56
+ /**
57
+ * Make an HTTP request using the connection pool
58
+ *
59
+ * @param url - Target URL
60
+ * @param init - Fetch options
61
+ * @returns Response promise
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const response = await pool.fetch('https://api.example.com/data', {
66
+ * method: 'POST',
67
+ * headers: { 'Content-Type': 'application/json' },
68
+ * body: JSON.stringify({ key: 'value' }),
69
+ * });
70
+ * ```
71
+ */
72
+ async fetch(url, init) {
73
+ const startTime = Date.now();
74
+ const origin = this.getOrigin(url);
75
+ try {
76
+ // Get or create connection
77
+ const connection = await this.acquireConnection(origin);
78
+ // Update metrics
79
+ if (this.options.enableMetrics) {
80
+ this.metrics.totalRequests++;
81
+ if (connection.requestCount > 0) {
82
+ this.metrics.reuseCount++;
83
+ }
84
+ }
85
+ // Make request with keep-alive
86
+ const requestInit = {
87
+ ...init,
88
+ signal: init?.signal ?? connection.controller.signal,
89
+ // @ts-ignore - keepalive is a valid fetch option
90
+ keepalive: true,
91
+ };
92
+ const response = await fetch(url, requestInit);
93
+ // Update connection state
94
+ connection.requestCount++;
95
+ connection.lastUsedAt = Date.now();
96
+ connection.state = 'idle';
97
+ // Record metrics
98
+ if (this.options.enableMetrics) {
99
+ const duration = Date.now() - startTime;
100
+ this.recordMetrics(origin, duration);
101
+ }
102
+ return response;
103
+ }
104
+ catch (error) {
105
+ // Record failure
106
+ if (this.options.enableMetrics) {
107
+ this.metrics.failedRequests++;
108
+ }
109
+ // Retry if enabled
110
+ if (this.options.retryOnFailure && init?.signal?.aborted !== true) {
111
+ return this.retryRequest(url, init, 1);
112
+ }
113
+ throw error;
114
+ }
115
+ }
116
+ /**
117
+ * Retry a failed request with exponential backoff
118
+ */
119
+ async retryRequest(url, init, attempt) {
120
+ if (attempt > this.options.maxRetries) {
121
+ throw new Error(`Request failed after ${this.options.maxRetries} retries: ${url}`);
122
+ }
123
+ // Exponential backoff: 100ms, 200ms, 400ms...
124
+ const delay = 100 * Math.pow(2, attempt - 1);
125
+ await new Promise(resolve => setTimeout(resolve, delay));
126
+ const origin = this.getOrigin(url);
127
+ try {
128
+ // Get or create connection
129
+ const connection = await this.acquireConnection(origin);
130
+ // Make request with keep-alive
131
+ const requestInit = {
132
+ ...init,
133
+ signal: init?.signal ?? connection.controller.signal,
134
+ // @ts-ignore - keepalive is a valid fetch option
135
+ keepalive: true,
136
+ };
137
+ const response = await fetch(url, requestInit);
138
+ // Update connection state
139
+ connection.requestCount++;
140
+ connection.lastUsedAt = Date.now();
141
+ connection.state = 'idle';
142
+ return response;
143
+ }
144
+ catch (error) {
145
+ if (attempt >= this.options.maxRetries) {
146
+ throw new Error(`Request failed after ${this.options.maxRetries} retries: ${url}`);
147
+ }
148
+ return this.retryRequest(url, init, attempt + 1);
149
+ }
150
+ }
151
+ /**
152
+ * Acquire a connection from the pool or create a new one
153
+ */
154
+ async acquireConnection(origin) {
155
+ let pool = this.connections.get(origin);
156
+ if (!pool) {
157
+ pool = [];
158
+ this.connections.set(origin, pool);
159
+ }
160
+ // Find idle connection
161
+ const idleConnection = pool.find(conn => conn.state === 'idle');
162
+ if (idleConnection) {
163
+ idleConnection.state = 'active';
164
+ return idleConnection;
165
+ }
166
+ // Check if we can create a new connection
167
+ if (pool.length < this.options.maxConnections) {
168
+ const connection = this.createConnection(origin);
169
+ pool.push(connection);
170
+ return connection;
171
+ }
172
+ // Wait for an available connection
173
+ return this.waitForConnection(origin);
174
+ }
175
+ /**
176
+ * Wait for a connection to become available
177
+ */
178
+ async waitForConnection(origin) {
179
+ const startTime = Date.now();
180
+ const timeout = this.options.requestTimeout;
181
+ while (Date.now() - startTime < timeout) {
182
+ const pool = this.connections.get(origin);
183
+ if (!pool) {
184
+ throw new Error(`Connection pool for ${origin} disappeared`);
185
+ }
186
+ const idleConnection = pool.find(conn => conn.state === 'idle');
187
+ if (idleConnection) {
188
+ idleConnection.state = 'active';
189
+ return idleConnection;
190
+ }
191
+ // Wait a bit before checking again
192
+ await new Promise(resolve => setTimeout(resolve, 10));
193
+ }
194
+ throw new Error(`Timeout waiting for connection to ${origin} after ${timeout}ms`);
195
+ }
196
+ /**
197
+ * Create a new connection
198
+ */
199
+ createConnection(origin) {
200
+ const randomBytes = new Uint8Array(8);
201
+ crypto.getRandomValues(randomBytes);
202
+ const id = Array.from(randomBytes)
203
+ .map(b => b.toString(16).padStart(2, '0'))
204
+ .join('');
205
+ return {
206
+ id: `conn_${id}`,
207
+ origin,
208
+ createdAt: Date.now(),
209
+ lastUsedAt: Date.now(),
210
+ requestCount: 0,
211
+ state: 'active',
212
+ controller: new AbortController(),
213
+ };
214
+ }
215
+ /**
216
+ * Get origin from URL
217
+ */
218
+ getOrigin(url) {
219
+ try {
220
+ const parsed = new URL(url);
221
+ return `${parsed.protocol}//${parsed.host}`;
222
+ }
223
+ catch (error) {
224
+ throw new Error(`Invalid URL: ${url}`);
225
+ }
226
+ }
227
+ /**
228
+ * Record request metrics
229
+ */
230
+ recordMetrics(origin, duration) {
231
+ this.metrics.requestDurations.push(duration);
232
+ // Limit stored durations to last 1000 requests
233
+ if (this.metrics.requestDurations.length > 1000) {
234
+ this.metrics.requestDurations.shift();
235
+ }
236
+ // Record origin-specific metrics
237
+ let originMetrics = this.metrics.byOrigin.get(origin);
238
+ if (!originMetrics) {
239
+ originMetrics = { requests: 0, durations: [] };
240
+ this.metrics.byOrigin.set(origin, originMetrics);
241
+ }
242
+ originMetrics.requests++;
243
+ originMetrics.durations.push(duration);
244
+ // Limit origin durations
245
+ if (originMetrics.durations.length > 100) {
246
+ originMetrics.durations.shift();
247
+ }
248
+ }
249
+ /**
250
+ * Get connection pool metrics
251
+ *
252
+ * @returns Current metrics snapshot
253
+ *
254
+ * @example
255
+ * ```typescript
256
+ * const metrics = pool.getMetrics();
257
+ * console.log(`Hit rate: ${(metrics.hitRate * 100).toFixed(1)}%`);
258
+ * console.log(`Avg duration: ${metrics.avgRequestDuration}ms`);
259
+ * ```
260
+ */
261
+ getMetrics() {
262
+ let totalConnections = 0;
263
+ let activeConnections = 0;
264
+ let idleConnections = 0;
265
+ const byOrigin = new Map();
266
+ for (const [origin, pool] of this.connections.entries()) {
267
+ totalConnections += pool.length;
268
+ activeConnections += pool.filter(c => c.state === 'active').length;
269
+ idleConnections += pool.filter(c => c.state === 'idle').length;
270
+ const originMetrics = this.metrics.byOrigin.get(origin);
271
+ if (originMetrics) {
272
+ const avgDuration = originMetrics.durations.length > 0
273
+ ? originMetrics.durations.reduce((a, b) => a + b, 0) /
274
+ originMetrics.durations.length
275
+ : 0;
276
+ byOrigin.set(origin, {
277
+ connections: pool.length,
278
+ requests: originMetrics.requests,
279
+ avgDuration: Math.round(avgDuration),
280
+ });
281
+ }
282
+ }
283
+ const avgRequestDuration = this.metrics.requestDurations.length > 0
284
+ ? Math.round(this.metrics.requestDurations.reduce((a, b) => a + b, 0) /
285
+ this.metrics.requestDurations.length)
286
+ : 0;
287
+ const hitRate = this.metrics.totalRequests > 0
288
+ ? this.metrics.reuseCount / this.metrics.totalRequests
289
+ : 0;
290
+ return {
291
+ totalConnections,
292
+ activeConnections,
293
+ idleConnections,
294
+ totalRequests: this.metrics.totalRequests,
295
+ reuseCount: this.metrics.reuseCount,
296
+ failedRequests: this.metrics.failedRequests,
297
+ avgRequestDuration,
298
+ hitRate,
299
+ byOrigin,
300
+ };
301
+ }
302
+ /**
303
+ * Reset metrics counters
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * pool.resetMetrics();
308
+ * // Start fresh measurements
309
+ * ```
310
+ */
311
+ resetMetrics() {
312
+ this.metrics = {
313
+ totalRequests: 0,
314
+ reuseCount: 0,
315
+ failedRequests: 0,
316
+ requestDurations: [],
317
+ byOrigin: new Map(),
318
+ };
319
+ }
320
+ /**
321
+ * Start automatic connection cleanup
322
+ */
323
+ startCleanup() {
324
+ // Run cleanup every 10 seconds
325
+ this.cleanupInterval = setInterval(() => {
326
+ this.cleanup();
327
+ }, 10000);
328
+ }
329
+ /**
330
+ * Clean up idle and expired connections
331
+ */
332
+ cleanup() {
333
+ const now = Date.now();
334
+ for (const [origin, pool] of this.connections.entries()) {
335
+ // Remove expired connections
336
+ const activePool = pool.filter(conn => {
337
+ const isExpired = now - conn.lastUsedAt > this.options.idleTimeout &&
338
+ conn.state === 'idle';
339
+ if (isExpired) {
340
+ conn.controller.abort();
341
+ conn.state = 'closed';
342
+ return false;
343
+ }
344
+ return conn.state !== 'closed';
345
+ });
346
+ // Ensure minimum connections are maintained
347
+ while (activePool.length < this.options.minConnections &&
348
+ activePool.length < this.options.maxConnections) {
349
+ const conn = this.createConnection(origin);
350
+ conn.state = 'idle';
351
+ activePool.push(conn);
352
+ }
353
+ if (activePool.length > 0) {
354
+ this.connections.set(origin, activePool);
355
+ }
356
+ else {
357
+ this.connections.delete(origin);
358
+ }
359
+ }
360
+ }
361
+ /**
362
+ * Manually trigger connection cleanup
363
+ *
364
+ * @example
365
+ * ```typescript
366
+ * // Force cleanup of idle connections
367
+ * pool.cleanupNow();
368
+ * ```
369
+ */
370
+ cleanupNow() {
371
+ this.cleanup();
372
+ }
373
+ /**
374
+ * Close all connections and shutdown the pool
375
+ *
376
+ * @example
377
+ * ```typescript
378
+ * // Cleanup on application shutdown
379
+ * await pool.close();
380
+ * ```
381
+ */
382
+ async close() {
383
+ // Stop cleanup interval
384
+ if (this.cleanupInterval) {
385
+ clearInterval(this.cleanupInterval);
386
+ this.cleanupInterval = null;
387
+ }
388
+ // Close all connections
389
+ for (const [_origin, pool] of this.connections.entries()) {
390
+ for (const conn of pool) {
391
+ conn.controller.abort();
392
+ conn.state = 'closed';
393
+ }
394
+ }
395
+ this.connections.clear();
396
+ }
397
+ /**
398
+ * Get the number of connections for a specific origin
399
+ *
400
+ * @param origin - Target origin (e.g., "https://api.example.com")
401
+ * @returns Number of connections
402
+ *
403
+ * @example
404
+ * ```typescript
405
+ * const count = pool.getConnectionCount('https://api.example.com');
406
+ * console.log(`Active connections: ${count}`);
407
+ * ```
408
+ */
409
+ getConnectionCount(origin) {
410
+ const pool = this.connections.get(origin);
411
+ return pool ? pool.length : 0;
412
+ }
413
+ /**
414
+ * Get all origins with active connections
415
+ *
416
+ * @returns Array of origins
417
+ *
418
+ * @example
419
+ * ```typescript
420
+ * const origins = pool.getOrigins();
421
+ * console.log(`Connected to ${origins.length} origins`);
422
+ * ```
423
+ */
424
+ getOrigins() {
425
+ return Array.from(this.connections.keys());
426
+ }
427
+ /**
428
+ * Check if the pool is healthy
429
+ *
430
+ * @returns True if pool is operating normally
431
+ *
432
+ * @example
433
+ * ```typescript
434
+ * if (!pool.isHealthy()) {
435
+ * console.warn('Connection pool degraded');
436
+ * }
437
+ * ```
438
+ */
439
+ isHealthy() {
440
+ const metrics = this.getMetrics();
441
+ // Check if failure rate is too high
442
+ const failureRate = metrics.totalRequests > 0
443
+ ? metrics.failedRequests / metrics.totalRequests
444
+ : 0;
445
+ if (failureRate > 0.1) {
446
+ // More than 10% failure rate
447
+ return false;
448
+ }
449
+ // Check if we have reasonable response times
450
+ if (metrics.avgRequestDuration > 5000) {
451
+ // Average over 5 seconds
452
+ return false;
453
+ }
454
+ return true;
455
+ }
456
+ }
457
+ /**
458
+ * Create a global connection pool instance
459
+ *
460
+ * Useful for sharing a single pool across your application.
461
+ *
462
+ * @param options - Pool configuration
463
+ * @returns Singleton connection pool instance
464
+ *
465
+ * @example
466
+ * ```typescript
467
+ * import { getGlobalPool } from '@private.me/xbind/connection-pool';
468
+ *
469
+ * const pool = getGlobalPool({
470
+ * maxConnections: 50,
471
+ * keepAliveTimeout: 30000,
472
+ * });
473
+ *
474
+ * const response = await pool.fetch('https://api.example.com/data');
475
+ * ```
476
+ */
477
+ let globalPool = null;
478
+ export function getGlobalPool(options) {
479
+ if (!globalPool) {
480
+ globalPool = new ConnectionPool(options);
481
+ }
482
+ return globalPool;
483
+ }
484
+ /**
485
+ * Reset the global connection pool
486
+ *
487
+ * Closes the current pool and allows a new one to be created.
488
+ *
489
+ * @example
490
+ * ```typescript
491
+ * await resetGlobalPool();
492
+ * // Next getGlobalPool() call will create a fresh instance
493
+ * ```
494
+ */
495
+ export async function resetGlobalPool() {
496
+ if (globalPool) {
497
+ await globalPool.close();
498
+ globalPool = null;
499
+ }
500
+ }