@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.
- package/README.md +55 -14
- 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.js +163 -1
- package/dist-standalone/plugins/metrics.js +176 -1
- package/dist-standalone/plugins/validation.js +297 -1
- 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.js +516 -1
- package/dist-standalone/runtime/edge.js +511 -1
- package/dist-standalone/runtime/react-native.js +383 -1
- 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.js +52 -1
- 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 -13
- 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,500 @@
|
|
|
1
|
-
|
|
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
|
+
}
|