@naman_deep_singh/utils 2.3.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +228 -129
- package/dist/cjs/array/arrayExtensions.js +23 -23
- package/dist/cjs/core/index.js +14 -16
- package/dist/cjs/errors/CompressionError.js +19 -0
- package/dist/cjs/errors/ConnectionError.js +21 -0
- package/dist/cjs/errors/PoolError.js +20 -0
- package/dist/cjs/errors/TimeoutError.js +24 -0
- package/dist/cjs/errors/ValidationError.js +22 -0
- package/dist/cjs/errors/index.js +13 -0
- package/dist/cjs/extensions/index.js +9 -18
- package/dist/cjs/init/index.js +7 -16
- package/dist/cjs/init/initializer.js +10 -10
- package/dist/cjs/number/numberExtensions.js +23 -23
- package/dist/cjs/object/objectExtensions.js +14 -14
- package/dist/cjs/string/stringExtensions.js +22 -22
- package/dist/cjs/types/index.js +0 -17
- package/dist/cjs/utils/compression.js +455 -0
- package/dist/cjs/utils/index.js +35 -18
- package/dist/cjs/utils/pool.js +375 -0
- package/dist/cjs/utils/timeout.js +133 -0
- package/dist/esm/array/arrayExtensions.js +1 -1
- package/dist/esm/core/index.js +3 -2
- package/dist/esm/errors/CompressionError.js +15 -0
- package/dist/esm/errors/ConnectionError.js +17 -0
- package/dist/esm/errors/PoolError.js +16 -0
- package/dist/esm/errors/TimeoutError.js +20 -0
- package/dist/esm/errors/ValidationError.js +18 -0
- package/dist/esm/errors/index.js +5 -0
- package/dist/esm/extensions/index.js +4 -4
- package/dist/esm/init/index.js +2 -2
- package/dist/esm/init/initializer.js +5 -5
- package/dist/esm/number/numberExtensions.js +2 -2
- package/dist/esm/object/objectExtensions.js +1 -1
- package/dist/esm/string/stringExtensions.js +1 -1
- package/dist/esm/types/index.js +1 -3
- package/dist/esm/utils/compression.js +415 -0
- package/dist/esm/utils/index.js +16 -4
- package/dist/esm/utils/pool.js +370 -0
- package/dist/esm/utils/timeout.js +128 -0
- package/dist/types/core/index.d.ts +3 -2
- package/dist/types/errors/CompressionError.d.ts +8 -0
- package/dist/types/errors/ConnectionError.d.ts +9 -0
- package/dist/types/errors/PoolError.d.ts +8 -0
- package/dist/types/errors/TimeoutError.d.ts +12 -0
- package/dist/types/errors/ValidationError.d.ts +9 -0
- package/dist/types/errors/index.d.ts +5 -0
- package/dist/types/extensions/index.d.ts +4 -4
- package/dist/types/init/index.d.ts +2 -2
- package/dist/types/init/initializer.d.ts +1 -1
- package/dist/types/init/options.d.ts +1 -1
- package/dist/types/types/extensionTypes.d.ts +1 -1
- package/dist/types/types/index.d.ts +1 -2
- package/dist/types/utils/compression.d.ts +164 -0
- package/dist/types/utils/config.d.ts +1 -1
- package/dist/types/utils/index.d.ts +11 -3
- package/dist/types/utils/pool.d.ts +157 -0
- package/dist/types/utils/timeout.d.ts +64 -0
- package/package.json +2 -1
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Generic connection pooling utilities
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.PoolManager = exports.GenericPool = void 0;
|
|
8
|
+
const ConnectionError_js_1 = require("../errors/ConnectionError.js");
|
|
9
|
+
const PoolError_js_1 = require("../errors/PoolError.js");
|
|
10
|
+
/**
|
|
11
|
+
* Pool connection wrapper with metadata
|
|
12
|
+
*/
|
|
13
|
+
class PooledConnection {
|
|
14
|
+
constructor(connection, poolName) {
|
|
15
|
+
this.connection = connection;
|
|
16
|
+
this.poolName = poolName;
|
|
17
|
+
this.id = `${poolName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
18
|
+
this.createdAt = new Date();
|
|
19
|
+
this.lastUsedAt = new Date();
|
|
20
|
+
this.isAcquired = false;
|
|
21
|
+
this.useCount = 0;
|
|
22
|
+
this.isMarkedForDestruction = false;
|
|
23
|
+
}
|
|
24
|
+
/** Mark connection as used */
|
|
25
|
+
markUsed() {
|
|
26
|
+
this.lastUsedAt = new Date();
|
|
27
|
+
this.useCount++;
|
|
28
|
+
}
|
|
29
|
+
/** Check if connection is idle beyond timeout */
|
|
30
|
+
isIdle(idleTimeoutMs) {
|
|
31
|
+
const idleTime = Date.now() - this.lastUsedAt.getTime();
|
|
32
|
+
return idleTime > idleTimeoutMs;
|
|
33
|
+
}
|
|
34
|
+
/** Check if connection has exceeded max lifetime */
|
|
35
|
+
hasExceededLifetime(maxLifetimeMs) {
|
|
36
|
+
const lifetime = Date.now() - this.createdAt.getTime();
|
|
37
|
+
return lifetime > maxLifetimeMs;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generic connection pool manager
|
|
42
|
+
*/
|
|
43
|
+
class GenericPool {
|
|
44
|
+
constructor(config) {
|
|
45
|
+
this.connections = new Map();
|
|
46
|
+
this.pendingAcquires = [];
|
|
47
|
+
this.isShuttingDown = false;
|
|
48
|
+
this.stats = {
|
|
49
|
+
totalConnectionsCreated: 0,
|
|
50
|
+
totalConnectionsDestroyed: 0,
|
|
51
|
+
totalAcquireRequests: 0,
|
|
52
|
+
totalAcquireTimeouts: 0,
|
|
53
|
+
totalAcquireErrors: 0,
|
|
54
|
+
totalReleaseRequests: 0,
|
|
55
|
+
};
|
|
56
|
+
this.config = {
|
|
57
|
+
name: config.name || 'default',
|
|
58
|
+
minConnections: config.minConnections ?? 1,
|
|
59
|
+
maxConnections: config.maxConnections ?? 10,
|
|
60
|
+
acquireTimeoutMs: config.acquireTimeoutMs ?? 30000,
|
|
61
|
+
releaseTimeoutMs: config.releaseTimeoutMs ?? 5000,
|
|
62
|
+
idleTimeoutMs: config.idleTimeoutMs ?? 60000,
|
|
63
|
+
maxLifetimeMs: config.maxLifetimeMs ?? 3600000,
|
|
64
|
+
healthCheckIntervalMs: config.healthCheckIntervalMs ?? 30000,
|
|
65
|
+
createConnection: config.createConnection,
|
|
66
|
+
validateConnection: config.validateConnection ?? ((conn) => conn.isHealthy()),
|
|
67
|
+
destroyConnection: config.destroyConnection ?? ((conn) => conn.close()),
|
|
68
|
+
};
|
|
69
|
+
this.validateConfig();
|
|
70
|
+
this.startHealthChecks();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validate pool configuration
|
|
74
|
+
*/
|
|
75
|
+
validateConfig() {
|
|
76
|
+
if (this.config.minConnections < 0) {
|
|
77
|
+
throw new RangeError('minConnections must be non-negative');
|
|
78
|
+
}
|
|
79
|
+
if (this.config.maxConnections <= 0) {
|
|
80
|
+
throw new RangeError('maxConnections must be positive');
|
|
81
|
+
}
|
|
82
|
+
if (this.config.minConnections > this.config.maxConnections) {
|
|
83
|
+
throw new RangeError('minConnections cannot exceed maxConnections');
|
|
84
|
+
}
|
|
85
|
+
if (this.config.acquireTimeoutMs <= 0) {
|
|
86
|
+
throw new RangeError('acquireTimeoutMs must be positive');
|
|
87
|
+
}
|
|
88
|
+
if (this.config.idleTimeoutMs < 0) {
|
|
89
|
+
throw new RangeError('idleTimeoutMs must be non-negative');
|
|
90
|
+
}
|
|
91
|
+
if (this.config.maxLifetimeMs < 0) {
|
|
92
|
+
throw new RangeError('maxLifetimeMs must be non-negative');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Start periodic health checks
|
|
97
|
+
*/
|
|
98
|
+
startHealthChecks() {
|
|
99
|
+
if (this.config.healthCheckIntervalMs > 0) {
|
|
100
|
+
this.healthCheckInterval = setInterval(() => {
|
|
101
|
+
this.cleanupIdleConnections();
|
|
102
|
+
this.cleanupExpiredConnections();
|
|
103
|
+
this.maintainMinConnections();
|
|
104
|
+
}, this.config.healthCheckIntervalMs);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Stop health checks
|
|
109
|
+
*/
|
|
110
|
+
stopHealthChecks() {
|
|
111
|
+
if (this.healthCheckInterval) {
|
|
112
|
+
clearInterval(this.healthCheckInterval);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Acquire a connection from the pool
|
|
117
|
+
* @returns Promise resolving to connection
|
|
118
|
+
*/
|
|
119
|
+
async acquire() {
|
|
120
|
+
if (this.isShuttingDown) {
|
|
121
|
+
throw new PoolError_js_1.PoolError('Cannot acquire connection: pool is shutting down', this.config.name);
|
|
122
|
+
}
|
|
123
|
+
this.stats.totalAcquireRequests++;
|
|
124
|
+
// Try to get available connection
|
|
125
|
+
for (const pooledConn of this.connections.values()) {
|
|
126
|
+
if (!pooledConn.isAcquired &&
|
|
127
|
+
this.config.validateConnection(pooledConn.connection)) {
|
|
128
|
+
pooledConn.isAcquired = true;
|
|
129
|
+
pooledConn.markUsed();
|
|
130
|
+
return pooledConn.connection;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Create new connection if under max
|
|
134
|
+
if (this.connections.size < this.config.maxConnections) {
|
|
135
|
+
try {
|
|
136
|
+
const connection = await this.createAndRegisterConnection();
|
|
137
|
+
connection.isAcquired = true;
|
|
138
|
+
connection.markUsed();
|
|
139
|
+
return connection.connection;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
this.stats.totalAcquireErrors++;
|
|
143
|
+
throw new ConnectionError_js_1.ConnectionError('Failed to create connection', undefined, { poolName: this.config.name }, error instanceof Error ? error : undefined);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Wait for connection to be released
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const request = {
|
|
149
|
+
resolve,
|
|
150
|
+
reject,
|
|
151
|
+
timestamp: Date.now(),
|
|
152
|
+
};
|
|
153
|
+
this.pendingAcquires.push(request);
|
|
154
|
+
// Set timeout for acquire request
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
const index = this.pendingAcquires.indexOf(request);
|
|
157
|
+
if (index !== -1) {
|
|
158
|
+
this.pendingAcquires.splice(index, 1);
|
|
159
|
+
this.stats.totalAcquireTimeouts++;
|
|
160
|
+
reject(new PoolError_js_1.PoolError(`Acquire timeout after ${this.config.acquireTimeoutMs}ms`, this.config.name, { pendingRequests: this.pendingAcquires.length }));
|
|
161
|
+
}
|
|
162
|
+
}, this.config.acquireTimeoutMs);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Release a connection back to the pool
|
|
167
|
+
* @param connection Connection to release
|
|
168
|
+
*/
|
|
169
|
+
async release(connection) {
|
|
170
|
+
this.stats.totalReleaseRequests++;
|
|
171
|
+
const pooledConn = Array.from(this.connections.values()).find((pc) => pc.connection === connection);
|
|
172
|
+
if (!pooledConn) {
|
|
173
|
+
throw new PoolError_js_1.PoolError('Connection not found in pool', this.config.name, {
|
|
174
|
+
connectionId: connection.id,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (!pooledConn.isAcquired) {
|
|
178
|
+
throw new PoolError_js_1.PoolError('Connection is not acquired', this.config.name, {
|
|
179
|
+
connectionId: connection.id,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// Validate connection before returning to pool
|
|
183
|
+
if (!this.config.validateConnection(connection)) {
|
|
184
|
+
await this.destroyConnection(pooledConn);
|
|
185
|
+
this.tryFulfillPendingAcquires();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
pooledConn.isAcquired = false;
|
|
189
|
+
pooledConn.markUsed();
|
|
190
|
+
// Fulfill pending acquire requests
|
|
191
|
+
this.tryFulfillPendingAcquires();
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Try to fulfill pending acquire requests
|
|
195
|
+
*/
|
|
196
|
+
tryFulfillPendingAcquires() {
|
|
197
|
+
while (this.pendingAcquires.length > 0) {
|
|
198
|
+
const availableConn = Array.from(this.connections.values()).find((pc) => !pc.isAcquired && this.config.validateConnection(pc.connection));
|
|
199
|
+
if (!availableConn) {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
const request = this.pendingAcquires.shift();
|
|
203
|
+
if (request) {
|
|
204
|
+
availableConn.isAcquired = true;
|
|
205
|
+
availableConn.markUsed();
|
|
206
|
+
request.resolve(availableConn.connection);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Create and register a new connection
|
|
212
|
+
*/
|
|
213
|
+
async createAndRegisterConnection() {
|
|
214
|
+
const connection = await this.config.createConnection();
|
|
215
|
+
const pooledConn = new PooledConnection(connection, this.config.name);
|
|
216
|
+
this.connections.set(pooledConn.id, pooledConn);
|
|
217
|
+
this.stats.totalConnectionsCreated++;
|
|
218
|
+
return pooledConn;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Destroy a connection
|
|
222
|
+
*/
|
|
223
|
+
async destroyConnection(pooledConn) {
|
|
224
|
+
try {
|
|
225
|
+
await this.config.destroyConnection(pooledConn.connection);
|
|
226
|
+
this.connections.delete(pooledConn.id);
|
|
227
|
+
this.stats.totalConnectionsDestroyed++;
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error(`Failed to destroy connection ${pooledConn.id}:`, error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Clean up idle connections
|
|
235
|
+
*/
|
|
236
|
+
cleanupIdleConnections() {
|
|
237
|
+
if (this.config.idleTimeoutMs <= 0)
|
|
238
|
+
return;
|
|
239
|
+
for (const pooledConn of this.connections.values()) {
|
|
240
|
+
if (!pooledConn.isAcquired &&
|
|
241
|
+
!pooledConn.isMarkedForDestruction &&
|
|
242
|
+
pooledConn.isIdle(this.config.idleTimeoutMs) &&
|
|
243
|
+
this.connections.size > this.config.minConnections) {
|
|
244
|
+
pooledConn.isMarkedForDestruction = true;
|
|
245
|
+
this.destroyConnection(pooledConn);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Clean up expired connections
|
|
251
|
+
*/
|
|
252
|
+
cleanupExpiredConnections() {
|
|
253
|
+
if (this.config.maxLifetimeMs <= 0)
|
|
254
|
+
return;
|
|
255
|
+
for (const pooledConn of this.connections.values()) {
|
|
256
|
+
if (!pooledConn.isMarkedForDestruction &&
|
|
257
|
+
pooledConn.hasExceededLifetime(this.config.maxLifetimeMs)) {
|
|
258
|
+
pooledConn.isMarkedForDestruction = true;
|
|
259
|
+
this.destroyConnection(pooledConn);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Maintain minimum connections
|
|
265
|
+
*/
|
|
266
|
+
async maintainMinConnections() {
|
|
267
|
+
const needed = this.config.minConnections - this.getAvailableCount();
|
|
268
|
+
for (let i = 0; i < needed && this.connections.size < this.config.maxConnections; i++) {
|
|
269
|
+
try {
|
|
270
|
+
await this.createAndRegisterConnection();
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
console.error('Failed to maintain min connections:', error);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get pool statistics
|
|
280
|
+
*/
|
|
281
|
+
getStats() {
|
|
282
|
+
return {
|
|
283
|
+
...this.stats,
|
|
284
|
+
totalConnections: this.connections.size,
|
|
285
|
+
acquiredConnections: Array.from(this.connections.values()).filter((c) => c.isAcquired).length,
|
|
286
|
+
availableConnections: this.getAvailableCount(),
|
|
287
|
+
pendingAcquires: this.pendingAcquires.length,
|
|
288
|
+
isShuttingDown: this.isShuttingDown,
|
|
289
|
+
config: {
|
|
290
|
+
name: this.config.name,
|
|
291
|
+
minConnections: this.config.minConnections,
|
|
292
|
+
maxConnections: this.config.maxConnections,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get number of available connections
|
|
298
|
+
*/
|
|
299
|
+
getAvailableCount() {
|
|
300
|
+
return Array.from(this.connections.values()).filter((pc) => !pc.isAcquired && this.config.validateConnection(pc.connection)).length;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Drain the pool (graceful shutdown)
|
|
304
|
+
*/
|
|
305
|
+
async drain() {
|
|
306
|
+
this.isShuttingDown = true;
|
|
307
|
+
this.stopHealthChecks();
|
|
308
|
+
// Reject all pending acquire requests
|
|
309
|
+
for (const request of this.pendingAcquires) {
|
|
310
|
+
request.reject(new PoolError_js_1.PoolError('Pool is draining', this.config.name));
|
|
311
|
+
}
|
|
312
|
+
this.pendingAcquires.length = 0;
|
|
313
|
+
// Close all connections
|
|
314
|
+
const destroyPromises = Array.from(this.connections.values()).map((pooledConn) => this.destroyConnection(pooledConn));
|
|
315
|
+
await Promise.allSettled(destroyPromises);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Execute a function with a connection from the pool
|
|
319
|
+
*/
|
|
320
|
+
async withConnection(fn) {
|
|
321
|
+
const connection = await this.acquire();
|
|
322
|
+
try {
|
|
323
|
+
return await fn(connection);
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
await this.release(connection);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
exports.GenericPool = GenericPool;
|
|
331
|
+
/**
|
|
332
|
+
* Pool manager for multiple pools
|
|
333
|
+
*/
|
|
334
|
+
class PoolManager {
|
|
335
|
+
constructor() {
|
|
336
|
+
this.pools = new Map();
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Create or get a pool
|
|
340
|
+
*/
|
|
341
|
+
createPool(config) {
|
|
342
|
+
const name = config.name || 'default';
|
|
343
|
+
if (this.pools.has(name)) {
|
|
344
|
+
throw new PoolError_js_1.PoolError(`Pool with name '${name}' already exists`, name);
|
|
345
|
+
}
|
|
346
|
+
const pool = new GenericPool(config);
|
|
347
|
+
this.pools.set(name, pool);
|
|
348
|
+
return pool;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Get a pool by name
|
|
352
|
+
*/
|
|
353
|
+
getPool(name) {
|
|
354
|
+
return this.pools.get(name);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Drain all pools
|
|
358
|
+
*/
|
|
359
|
+
async drainAll() {
|
|
360
|
+
const drainPromises = Array.from(this.pools.values()).map((pool) => pool.drain());
|
|
361
|
+
await Promise.all(drainPromises);
|
|
362
|
+
this.pools.clear();
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Get statistics for all pools
|
|
366
|
+
*/
|
|
367
|
+
getAllStats() {
|
|
368
|
+
const stats = {};
|
|
369
|
+
for (const [name, pool] of this.pools.entries()) {
|
|
370
|
+
stats[name] = pool.getStats();
|
|
371
|
+
}
|
|
372
|
+
return stats;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
exports.PoolManager = PoolManager;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Timeout utilities for async operations
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.TimeoutManager = void 0;
|
|
8
|
+
exports.withTimeout = withTimeout;
|
|
9
|
+
const TimeoutError_js_1 = require("../errors/TimeoutError.js");
|
|
10
|
+
/**
|
|
11
|
+
* Timeout manager for async operations
|
|
12
|
+
*/
|
|
13
|
+
class TimeoutManager {
|
|
14
|
+
/**
|
|
15
|
+
* Execute a promise with a timeout
|
|
16
|
+
* @param promise Promise to execute
|
|
17
|
+
* @param timeoutMs Timeout duration in milliseconds
|
|
18
|
+
* @param errorMessage Optional custom error message
|
|
19
|
+
* @returns Promise result or throws TimeoutError
|
|
20
|
+
* @throws {TimeoutError} If operation times out
|
|
21
|
+
* @throws Any error thrown by the original promise
|
|
22
|
+
*/
|
|
23
|
+
static async withTimeout(promise, timeoutMs, errorMessage) {
|
|
24
|
+
if (timeoutMs <= 0) {
|
|
25
|
+
throw new RangeError(`Timeout must be positive, got ${timeoutMs}`);
|
|
26
|
+
}
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const timeoutId = setTimeout(() => {
|
|
29
|
+
reject(new TimeoutError_js_1.TimeoutError(errorMessage || 'Operation timed out', timeoutMs));
|
|
30
|
+
}, timeoutMs);
|
|
31
|
+
promise
|
|
32
|
+
.then((result) => {
|
|
33
|
+
clearTimeout(timeoutId);
|
|
34
|
+
resolve(result);
|
|
35
|
+
})
|
|
36
|
+
.catch((error) => {
|
|
37
|
+
clearTimeout(timeoutId);
|
|
38
|
+
reject(error);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create an AbortSignal that aborts after specified timeout
|
|
44
|
+
* @param timeoutMs Timeout duration in milliseconds
|
|
45
|
+
* @returns AbortSignal that aborts after timeout
|
|
46
|
+
*/
|
|
47
|
+
static createTimeoutSignal(timeoutMs) {
|
|
48
|
+
if (timeoutMs <= 0) {
|
|
49
|
+
throw new RangeError(`Timeout must be positive, got ${timeoutMs}`);
|
|
50
|
+
}
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
setTimeout(() => controller.abort(), timeoutMs);
|
|
53
|
+
return controller.signal;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a promise that resolves after specified delay
|
|
57
|
+
* @param delayMs Delay in milliseconds
|
|
58
|
+
* @returns Promise that resolves after delay
|
|
59
|
+
*/
|
|
60
|
+
static delay(delayMs) {
|
|
61
|
+
if (delayMs < 0) {
|
|
62
|
+
throw new RangeError(`Delay must be non-negative, got ${delayMs}`);
|
|
63
|
+
}
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
setTimeout(resolve, delayMs);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create a promise that rejects after specified timeout
|
|
70
|
+
* @param timeoutMs Timeout in milliseconds
|
|
71
|
+
* @param errorMessage Optional error message
|
|
72
|
+
* @returns Promise that rejects with TimeoutError after timeout
|
|
73
|
+
*/
|
|
74
|
+
static timeoutPromise(timeoutMs, errorMessage) {
|
|
75
|
+
return new Promise((_, reject) => {
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
reject(new TimeoutError_js_1.TimeoutError(errorMessage || 'Timeout reached', timeoutMs));
|
|
78
|
+
}, timeoutMs);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Race multiple promises with individual timeouts
|
|
83
|
+
* @param promises Array of promises to race
|
|
84
|
+
* @param timeoutMs Timeout for each promise
|
|
85
|
+
* @returns First promise to resolve, or rejects if all timeout
|
|
86
|
+
*/
|
|
87
|
+
static async raceWithTimeout(promises, timeoutMs) {
|
|
88
|
+
const timeoutPromises = promises.map((promise) => this.withTimeout(promise, timeoutMs, 'Race participant timed out'));
|
|
89
|
+
return Promise.race(timeoutPromises);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Execute function with retry and timeout for each attempt
|
|
93
|
+
* @param fn Function to execute
|
|
94
|
+
* @param options Retry and timeout options
|
|
95
|
+
* @returns Function result
|
|
96
|
+
*/
|
|
97
|
+
static async retryWithTimeout(fn, options = {}) {
|
|
98
|
+
const { maxAttempts = 3, timeoutPerAttempt = 5000, delayBetweenAttempts = 1000, backoffMultiplier = 2, } = options;
|
|
99
|
+
if (maxAttempts < 1) {
|
|
100
|
+
throw new RangeError(`maxAttempts must be at least 1, got ${maxAttempts}`);
|
|
101
|
+
}
|
|
102
|
+
let lastError;
|
|
103
|
+
let currentDelay = delayBetweenAttempts;
|
|
104
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
105
|
+
try {
|
|
106
|
+
return await this.withTimeout(fn(), timeoutPerAttempt, `Attempt ${attempt} timed out`);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
lastError = error;
|
|
110
|
+
// Don't delay after last attempt
|
|
111
|
+
if (attempt < maxAttempts) {
|
|
112
|
+
await this.delay(currentDelay);
|
|
113
|
+
currentDelay *= backoffMultiplier;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
throw lastError || new Error('All retry attempts failed');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.TimeoutManager = TimeoutManager;
|
|
121
|
+
/**
|
|
122
|
+
* Create a timeout wrapper for async functions
|
|
123
|
+
* @param timeoutMs Timeout in milliseconds
|
|
124
|
+
* @param errorMessage Optional error message
|
|
125
|
+
* @returns Function wrapper that adds timeout
|
|
126
|
+
*/
|
|
127
|
+
function withTimeout(timeoutMs, errorMessage) {
|
|
128
|
+
return (target, _context) => {
|
|
129
|
+
return function (...args) {
|
|
130
|
+
return TimeoutManager.withTimeout(target.apply(this, args), timeoutMs, errorMessage);
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
}
|
package/dist/esm/core/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
// Re-export core utilities
|
|
2
|
-
export
|
|
3
|
-
export
|
|
2
|
+
export { validateExtensionInput, validateArrayInput, validateNumberRange, validatePositiveInteger, } from './validation.js';
|
|
3
|
+
export { setPerformanceConfig, getPerformanceConfig, LRUCache, makeInternalCacheKey, withCache, } from './performance.js';
|
|
4
|
+
export { getPackageVersion } from './version.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AppError, ERROR_CODES } from '@naman_deep_singh/errors';
|
|
2
|
+
/**
|
|
3
|
+
* Compression error - when compression/decompression fails
|
|
4
|
+
*/
|
|
5
|
+
export class CompressionError extends AppError {
|
|
6
|
+
constructor(message, algorithm, details, cause) {
|
|
7
|
+
super(ERROR_CODES.FILE_ERROR, 500, {
|
|
8
|
+
message,
|
|
9
|
+
algorithm,
|
|
10
|
+
...(details ? { details } : {}),
|
|
11
|
+
}, cause);
|
|
12
|
+
this.algorithm = algorithm;
|
|
13
|
+
this.name = 'CompressionError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection error - when connection fails
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import { AppError, ERROR_CODES } from '@naman_deep_singh/errors';
|
|
6
|
+
export class ConnectionError extends AppError {
|
|
7
|
+
constructor(message, endpoint, details, cause) {
|
|
8
|
+
super(ERROR_CODES.DEPENDENCY_FAILURE, 502, // 502 = Bad Gateway
|
|
9
|
+
{
|
|
10
|
+
message,
|
|
11
|
+
endpoint,
|
|
12
|
+
...(details ? { details } : {}),
|
|
13
|
+
}, cause);
|
|
14
|
+
this.endpoint = endpoint;
|
|
15
|
+
this.name = 'ConnectionError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AppError, ERROR_CODES } from '@naman_deep_singh/errors';
|
|
2
|
+
/**
|
|
3
|
+
* Pool error - when pool operations fail
|
|
4
|
+
*/
|
|
5
|
+
export class PoolError extends AppError {
|
|
6
|
+
constructor(message, poolName, details, cause) {
|
|
7
|
+
super(ERROR_CODES.RESOURCE_EXHAUSTED, 503, // 503 = Service Unavailable
|
|
8
|
+
{
|
|
9
|
+
message,
|
|
10
|
+
poolName,
|
|
11
|
+
...(details ? { details } : {}),
|
|
12
|
+
}, cause);
|
|
13
|
+
this.poolName = poolName;
|
|
14
|
+
this.name = 'PoolError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility-specific error classes
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import { AppError, ERROR_CODES } from '@naman_deep_singh/errors';
|
|
6
|
+
/**
|
|
7
|
+
* Timeout error - when operation times out
|
|
8
|
+
*/
|
|
9
|
+
export class TimeoutError extends AppError {
|
|
10
|
+
constructor(message = 'Operation timed out', timeoutMs, details, cause) {
|
|
11
|
+
super(ERROR_CODES.TIMEOUT_ERROR, 408, // 408 = Request Timeout
|
|
12
|
+
{
|
|
13
|
+
message,
|
|
14
|
+
timeoutMs,
|
|
15
|
+
...(details ? { details } : {}),
|
|
16
|
+
}, cause);
|
|
17
|
+
this.timeoutMs = timeoutMs;
|
|
18
|
+
this.name = 'TimeoutError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AppError, ERROR_CODES } from '@naman_deep_singh/errors';
|
|
2
|
+
/**
|
|
3
|
+
* Validation error - when utility input validation fails
|
|
4
|
+
*/
|
|
5
|
+
export class ValidationError extends AppError {
|
|
6
|
+
constructor(message, field, value, details, cause) {
|
|
7
|
+
super(ERROR_CODES.VALIDATION_FAILED, 400, // 400 = Bad Request
|
|
8
|
+
{
|
|
9
|
+
message,
|
|
10
|
+
field,
|
|
11
|
+
value,
|
|
12
|
+
...(details ? { details } : {}),
|
|
13
|
+
}, cause);
|
|
14
|
+
this.field = field;
|
|
15
|
+
this.value = value;
|
|
16
|
+
this.name = 'ValidationError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Re-export all extensions
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
2
|
+
export { extendString } from '../string/index.js';
|
|
3
|
+
export { extendArray } from '../array/index.js';
|
|
4
|
+
export { extendObject } from '../object/index.js';
|
|
5
|
+
export { extendNumber } from '../number/index.js';
|
package/dist/esm/init/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Re-export initialization logic
|
|
2
|
-
export
|
|
3
|
-
export
|
|
2
|
+
export { initExtensions, extendAll } from './initializer.js';
|
|
3
|
+
export { validateExtensionOptions, createExtensionOptions } from './options.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { extendArray } from '../array/
|
|
2
|
-
import { setPerformanceConfig } from '../core/
|
|
3
|
-
import { extendNumber } from '../number/
|
|
4
|
-
import { extendObject } from '../object/
|
|
5
|
-
import { extendString } from '../string/
|
|
1
|
+
import { extendArray } from '../array/arrayExtensions.js';
|
|
2
|
+
import { setPerformanceConfig } from '../core/performance.js';
|
|
3
|
+
import { extendNumber } from '../number/numberExtensions.js';
|
|
4
|
+
import { extendObject } from '../object/objectExtensions.js';
|
|
5
|
+
import { extendString } from '../string/stringExtensions.js';
|
|
6
6
|
export function initExtensions(options = {}) {
|
|
7
7
|
const { string = true, array = true, object = true, number = true, performance, } = options;
|
|
8
8
|
if (performance) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { makeInternalCacheKey, withCache } from '../core/
|
|
2
|
-
import { defineExtension } from '../utils/
|
|
1
|
+
import { makeInternalCacheKey, withCache } from '../core/performance.js';
|
|
2
|
+
import { defineExtension } from '../utils/defineExtension.js';
|
|
3
3
|
let numberExtended = false;
|
|
4
4
|
export function extendNumber() {
|
|
5
5
|
if (numberExtended)
|
package/dist/esm/types/index.js
CHANGED