@bernierllc/backoff-retry 0.1.1
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/CHANGELOG.md +7 -0
- package/LICENSE +7 -0
- package/README.md +345 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-protection.d.ts +69 -0
- package/dist/memory-protection.d.ts.map +1 -0
- package/dist/memory-protection.js +183 -0
- package/dist/memory-protection.js.map +1 -0
- package/dist/retry-manager.d.ts +74 -0
- package/dist/retry-manager.d.ts.map +1 -0
- package/dist/retry-manager.js +258 -0
- package/dist/retry-manager.js.map +1 -0
- package/dist/storage.d.ts +38 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +197 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
package/README.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# @bernierllc/backoff-retry
|
|
2
|
+
|
|
3
|
+
Portable async retry utility with exponential backoff, jitter, and error filtering for API and webhook management.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Exponential Backoff**: Configurable backoff strategies with jitter
|
|
8
|
+
- **Persistent State**: Track retry attempts across application restarts
|
|
9
|
+
- **Multiple Storage Backends**: Memory, Redis with automatic fallback
|
|
10
|
+
- **Memory Protection**: Built-in memory usage monitoring and protection
|
|
11
|
+
- **Flexible Error Handling**: Custom retry conditions and error filtering
|
|
12
|
+
- **Metrics & Monitoring**: Built-in retry metrics and performance tracking
|
|
13
|
+
- **TypeScript Support**: Full TypeScript definitions included
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @bernierllc/backoff-retry
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Basic Usage
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { createRetryManager } from '@bernierllc/backoff-retry';
|
|
27
|
+
|
|
28
|
+
const retryManager = createRetryManager();
|
|
29
|
+
|
|
30
|
+
// Simple retry with backoff
|
|
31
|
+
const result = await retryManager.retryWithBackoff(
|
|
32
|
+
async () => {
|
|
33
|
+
const response = await fetch('https://api.example.com/data');
|
|
34
|
+
if (!response.ok) throw new Error('API request failed');
|
|
35
|
+
return response.json();
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
maxRetries: 5,
|
|
39
|
+
initialDelayMs: 1000,
|
|
40
|
+
maxDelayMs: 30000,
|
|
41
|
+
backoffFactor: 2,
|
|
42
|
+
jitter: true
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Persistent Retry State
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Retry with persistent state (survives app restarts)
|
|
51
|
+
const result = await retryManager.executeWithRetry(
|
|
52
|
+
'api:user:123', // Unique ID for this operation
|
|
53
|
+
async () => {
|
|
54
|
+
return await updateUserProfile(userId, data);
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
maxRetries: 3,
|
|
58
|
+
initialDelayMs: 2000
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (result.success) {
|
|
63
|
+
console.log('Operation completed:', result.data);
|
|
64
|
+
} else {
|
|
65
|
+
console.log('Operation failed after', result.attempts, 'attempts');
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
### Environment Variables
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Storage type: 'memory' or 'redis'
|
|
75
|
+
RETRY_STORAGE_TYPE=redis
|
|
76
|
+
|
|
77
|
+
# Redis connection URL (if using Redis storage)
|
|
78
|
+
REDIS_URL=redis://localhost:6379
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Custom Configuration
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { createRetryManager } from '@bernierllc/backoff-retry';
|
|
85
|
+
import { MemoryStorageAdapter } from '@bernierllc/backoff-retry';
|
|
86
|
+
|
|
87
|
+
const retryManager = createRetryManager({
|
|
88
|
+
storage: new MemoryStorageAdapter(1000), // Max 1000 retry states
|
|
89
|
+
memoryProtection: {
|
|
90
|
+
maxMemoryUsage: 50 * 1024 * 1024, // 50MB
|
|
91
|
+
maxRetryStates: 500,
|
|
92
|
+
cleanupInterval: 60000, // 1 minute
|
|
93
|
+
evictionPolicy: 'lru'
|
|
94
|
+
},
|
|
95
|
+
defaultOptions: {
|
|
96
|
+
maxRetries: 3,
|
|
97
|
+
initialDelayMs: 1000,
|
|
98
|
+
maxDelayMs: 10000,
|
|
99
|
+
backoffFactor: 2,
|
|
100
|
+
jitter: true
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
### RetryManager
|
|
108
|
+
|
|
109
|
+
#### `retryWithBackoff<T>(fn, options): Promise<T>`
|
|
110
|
+
|
|
111
|
+
Execute a function with retry logic.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const result = await retryManager.retryWithBackoff(
|
|
115
|
+
async () => fetch('/api/data'),
|
|
116
|
+
{
|
|
117
|
+
maxRetries: 5,
|
|
118
|
+
initialDelayMs: 1000,
|
|
119
|
+
maxDelayMs: 30000,
|
|
120
|
+
backoffFactor: 2,
|
|
121
|
+
jitter: true,
|
|
122
|
+
shouldRetry: (error) => error.status !== 404,
|
|
123
|
+
onRetry: (attempt, delay, error) => {
|
|
124
|
+
console.log(`Retry ${attempt} in ${delay}ms`);
|
|
125
|
+
},
|
|
126
|
+
onFailure: (error) => {
|
|
127
|
+
console.error('Final failure:', error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `executeWithRetry<T>(id, fn, options): Promise<RetryResult<T>>`
|
|
134
|
+
|
|
135
|
+
Execute a function with persistent retry state.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const result = await retryManager.executeWithRetry(
|
|
139
|
+
'unique-operation-id',
|
|
140
|
+
async () => processData(),
|
|
141
|
+
{ maxRetries: 3 }
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (result.success) {
|
|
145
|
+
console.log('Success:', result.data);
|
|
146
|
+
console.log('Attempts:', result.attempts);
|
|
147
|
+
console.log('Total time:', result.totalTime);
|
|
148
|
+
} else {
|
|
149
|
+
console.log('Failed:', result.error);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### `getRetryState(id): Promise<RetryState | null>`
|
|
154
|
+
|
|
155
|
+
Get the current state of a retry operation.
|
|
156
|
+
|
|
157
|
+
#### `listRetryStates(prefix?): Promise<RetryState[]>`
|
|
158
|
+
|
|
159
|
+
List all retry states, optionally filtered by prefix.
|
|
160
|
+
|
|
161
|
+
#### `cancelRetry(id): Promise<void>`
|
|
162
|
+
|
|
163
|
+
Cancel a retry operation.
|
|
164
|
+
|
|
165
|
+
#### `clearAll(): Promise<void>`
|
|
166
|
+
|
|
167
|
+
Clear all retry states.
|
|
168
|
+
|
|
169
|
+
#### `getMetrics(): RetryMetrics`
|
|
170
|
+
|
|
171
|
+
Get retry performance metrics.
|
|
172
|
+
|
|
173
|
+
#### `getMemoryStats(): MemoryStats`
|
|
174
|
+
|
|
175
|
+
Get memory protection statistics.
|
|
176
|
+
|
|
177
|
+
## Storage Adapters
|
|
178
|
+
|
|
179
|
+
### Memory Storage
|
|
180
|
+
|
|
181
|
+
Default in-memory storage with LRU eviction:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { MemoryStorageAdapter } from '@bernierllc/backoff-retry';
|
|
185
|
+
|
|
186
|
+
const storage = new MemoryStorageAdapter(1000); // Max 1000 items
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Redis Storage
|
|
190
|
+
|
|
191
|
+
Redis storage with automatic fallback to memory:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { RedisStorageAdapter } from '@bernierllc/backoff-retry';
|
|
195
|
+
import { createClient } from 'redis';
|
|
196
|
+
|
|
197
|
+
const redisClient = createClient({ url: 'redis://localhost:6379' });
|
|
198
|
+
const storage = new RedisStorageAdapter(redisClient, 'retry:');
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Memory Protection
|
|
202
|
+
|
|
203
|
+
The package includes built-in memory protection to prevent runaway memory usage:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
const retryManager = createRetryManager({
|
|
207
|
+
memoryProtection: {
|
|
208
|
+
maxMemoryUsage: 100 * 1024 * 1024, // 100MB
|
|
209
|
+
maxRetryStates: 1000,
|
|
210
|
+
cleanupInterval: 30000, // 30 seconds
|
|
211
|
+
evictionPolicy: 'lru' // 'lru', 'fifo', or 'random'
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Error Handling
|
|
217
|
+
|
|
218
|
+
### Custom Retry Conditions
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const shouldRetry = (error: any) => {
|
|
222
|
+
// Retry on network errors, but not on validation errors
|
|
223
|
+
if (error.code === 'ECONNRESET') return true;
|
|
224
|
+
if (error.status === 400) return false;
|
|
225
|
+
if (error.status >= 500) return true;
|
|
226
|
+
return false;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
await retryManager.retryWithBackoff(fn, {
|
|
230
|
+
maxRetries: 5,
|
|
231
|
+
shouldRetry
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Retry Callbacks
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
await retryManager.retryWithBackoff(fn, {
|
|
239
|
+
maxRetries: 3,
|
|
240
|
+
onRetry: (attempt, delay, error) => {
|
|
241
|
+
console.log(`Retry ${attempt} after ${delay}ms delay`);
|
|
242
|
+
// Log to monitoring service
|
|
243
|
+
analytics.track('retry_attempt', { attempt, delay, error: error.message });
|
|
244
|
+
},
|
|
245
|
+
onFailure: (error) => {
|
|
246
|
+
console.error('Operation failed permanently:', error);
|
|
247
|
+
// Alert operations team
|
|
248
|
+
alerting.sendAlert('retry_failure', { error: error.message });
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Examples
|
|
254
|
+
|
|
255
|
+
### API Request Retry
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
const apiClient = {
|
|
259
|
+
async request(endpoint: string, options: RequestInit = {}) {
|
|
260
|
+
return retryManager.retryWithBackoff(
|
|
261
|
+
async () => {
|
|
262
|
+
const response = await fetch(endpoint, {
|
|
263
|
+
...options,
|
|
264
|
+
headers: {
|
|
265
|
+
'Content-Type': 'application/json',
|
|
266
|
+
...options.headers
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return response.json();
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
maxRetries: 3,
|
|
278
|
+
initialDelayMs: 1000,
|
|
279
|
+
shouldRetry: (error) => {
|
|
280
|
+
// Retry on server errors and network issues
|
|
281
|
+
return error.message.includes('500') ||
|
|
282
|
+
error.message.includes('502') ||
|
|
283
|
+
error.message.includes('503') ||
|
|
284
|
+
error.message.includes('504');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Webhook Delivery
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
const webhookManager = {
|
|
296
|
+
async deliverWebhook(webhookId: string, payload: any) {
|
|
297
|
+
return retryManager.executeWithRetry(
|
|
298
|
+
`webhook:${webhookId}`,
|
|
299
|
+
async () => {
|
|
300
|
+
const response = await fetch(webhookUrl, {
|
|
301
|
+
method: 'POST',
|
|
302
|
+
headers: { 'Content-Type': 'application/json' },
|
|
303
|
+
body: JSON.stringify(payload)
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (!response.ok) {
|
|
307
|
+
throw new Error(`Webhook delivery failed: ${response.status}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return response.json();
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
maxRetries: 5,
|
|
314
|
+
initialDelayMs: 5000, // Start with 5 second delay
|
|
315
|
+
maxDelayMs: 300000, // Max 5 minute delay
|
|
316
|
+
backoffFactor: 2,
|
|
317
|
+
jitter: true
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Monitoring and Metrics
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// Get retry metrics
|
|
328
|
+
const metrics = retryManager.getMetrics();
|
|
329
|
+
console.log('Total retries:', metrics.totalRetries);
|
|
330
|
+
console.log('Success rate:', metrics.successfulRetries / metrics.totalRetries);
|
|
331
|
+
console.log('Average retry time:', metrics.averageRetryTime);
|
|
332
|
+
|
|
333
|
+
// Get memory protection stats
|
|
334
|
+
const memoryStats = retryManager.getMemoryStats();
|
|
335
|
+
console.log('Memory usage:', memoryStats.usagePercentage.toFixed(2) + '%');
|
|
336
|
+
console.log('Memory trend:', memoryStats.trend > 0 ? 'increasing' : 'decreasing');
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## License
|
|
340
|
+
|
|
341
|
+
ISC - See LICENSE file for details.
|
|
342
|
+
|
|
343
|
+
## Contributing
|
|
344
|
+
|
|
345
|
+
This package is part of the Bernier LLC tools monorepo. Please refer to the main repository for contribution guidelines.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
21
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
__exportStar(require("./types"), exports);
|
|
25
|
+
__exportStar(require("./retry-manager"), exports);
|
|
26
|
+
__exportStar(require("./storage"), exports);
|
|
27
|
+
__exportStar(require("./memory-protection"), exports);
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;EAME;;;;;;;;;;;;;;;;AAEF,0CAAwB;AACxB,kDAAgC;AAChC,4CAA0B;AAC1B,sDAAoC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { MemoryProtectionConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Memory protection manager to prevent runaway memory usage
|
|
4
|
+
*/
|
|
5
|
+
export declare class MemoryProtectionManager {
|
|
6
|
+
private config;
|
|
7
|
+
private cleanupInterval?;
|
|
8
|
+
private memoryUsageHistory;
|
|
9
|
+
private maxHistorySize;
|
|
10
|
+
constructor(config: MemoryProtectionConfig);
|
|
11
|
+
/**
|
|
12
|
+
* Check if current memory usage is within safe limits
|
|
13
|
+
*/
|
|
14
|
+
checkMemoryUsage(): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Get current memory usage in bytes
|
|
17
|
+
*/
|
|
18
|
+
private getCurrentMemoryUsage;
|
|
19
|
+
/**
|
|
20
|
+
* Rough memory usage estimation
|
|
21
|
+
*/
|
|
22
|
+
private estimateMemoryUsage;
|
|
23
|
+
/**
|
|
24
|
+
* Update memory usage history for trend analysis
|
|
25
|
+
*/
|
|
26
|
+
private updateMemoryHistory;
|
|
27
|
+
/**
|
|
28
|
+
* Get memory usage trend (positive = increasing, negative = decreasing)
|
|
29
|
+
*/
|
|
30
|
+
getMemoryTrend(): number;
|
|
31
|
+
/**
|
|
32
|
+
* Start periodic cleanup interval
|
|
33
|
+
*/
|
|
34
|
+
private startCleanupInterval;
|
|
35
|
+
/**
|
|
36
|
+
* Perform memory cleanup based on eviction policy
|
|
37
|
+
*/
|
|
38
|
+
private performCleanup;
|
|
39
|
+
/**
|
|
40
|
+
* Emergency cleanup when memory usage is critical
|
|
41
|
+
*/
|
|
42
|
+
private emergencyCleanup;
|
|
43
|
+
/**
|
|
44
|
+
* Evict items based on configured policy
|
|
45
|
+
*/
|
|
46
|
+
evictItems<T>(items: T[], maxCount: number): T[];
|
|
47
|
+
private evictLRU;
|
|
48
|
+
private evictFIFO;
|
|
49
|
+
private evictRandom;
|
|
50
|
+
/**
|
|
51
|
+
* Get memory protection statistics
|
|
52
|
+
*/
|
|
53
|
+
getStats(): {
|
|
54
|
+
currentUsage: number;
|
|
55
|
+
maxUsage: number;
|
|
56
|
+
usagePercentage: number;
|
|
57
|
+
trend: number;
|
|
58
|
+
historySize: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Cleanup resources
|
|
62
|
+
*/
|
|
63
|
+
destroy(): void;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Default memory protection configuration
|
|
67
|
+
*/
|
|
68
|
+
export declare const DEFAULT_MEMORY_PROTECTION_CONFIG: MemoryProtectionConfig;
|
|
69
|
+
//# sourceMappingURL=memory-protection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-protection.d.ts","sourceRoot":"","sources":["../src/memory-protection.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,sBAAsB,EAAc,MAAM,SAAS,CAAC;AAE7D;;GAEG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,kBAAkB,CAAgB;IAC1C,OAAO,CAAC,cAAc,CAAO;gBAEjB,MAAM,EAAE,sBAAsB;IAK1C;;OAEG;IACH,gBAAgB,IAAI,OAAO;IAY3B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAe7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAe3B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;OAEG;IACH,cAAc,IAAI,MAAM;IAcxB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAU5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAexB;;OAEG;IACH,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,EAAE;IAehD,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,WAAW;IAKnB;;OAEG;IACH,QAAQ;;;;;;;IAUR;;OAEG;IACH,OAAO,IAAI,IAAI;CAMhB;AAED;;GAEG;AACH,eAAO,MAAM,gCAAgC,EAAE,sBAK9C,CAAC"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.DEFAULT_MEMORY_PROTECTION_CONFIG = exports.MemoryProtectionManager = void 0;
|
|
11
|
+
/**
|
|
12
|
+
* Memory protection manager to prevent runaway memory usage
|
|
13
|
+
*/
|
|
14
|
+
class MemoryProtectionManager {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.memoryUsageHistory = [];
|
|
17
|
+
this.maxHistorySize = 100;
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.startCleanupInterval();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Check if current memory usage is within safe limits
|
|
23
|
+
*/
|
|
24
|
+
checkMemoryUsage() {
|
|
25
|
+
const currentUsage = this.getCurrentMemoryUsage();
|
|
26
|
+
this.updateMemoryHistory(currentUsage);
|
|
27
|
+
if (currentUsage > this.config.maxMemoryUsage) {
|
|
28
|
+
console.warn(`Memory usage (${currentUsage} bytes) exceeds limit (${this.config.maxMemoryUsage} bytes)`);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get current memory usage in bytes
|
|
35
|
+
*/
|
|
36
|
+
getCurrentMemoryUsage() {
|
|
37
|
+
if (typeof process !== 'undefined' && process.memoryUsage) {
|
|
38
|
+
const memUsage = process.memoryUsage();
|
|
39
|
+
return memUsage.heapUsed + memUsage.external;
|
|
40
|
+
}
|
|
41
|
+
// Fallback for environments without process.memoryUsage
|
|
42
|
+
if (typeof performance !== 'undefined' && performance.memory) {
|
|
43
|
+
return performance.memory.usedJSHeapSize;
|
|
44
|
+
}
|
|
45
|
+
// Very rough estimate based on global object size
|
|
46
|
+
return this.estimateMemoryUsage();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Rough memory usage estimation
|
|
50
|
+
*/
|
|
51
|
+
estimateMemoryUsage() {
|
|
52
|
+
let size = 0;
|
|
53
|
+
const objects = [global, globalThis, window].filter(Boolean);
|
|
54
|
+
for (const obj of objects) {
|
|
55
|
+
try {
|
|
56
|
+
size += JSON.stringify(obj).length;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Ignore circular references
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return size;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Update memory usage history for trend analysis
|
|
66
|
+
*/
|
|
67
|
+
updateMemoryHistory(usage) {
|
|
68
|
+
this.memoryUsageHistory.push(usage);
|
|
69
|
+
if (this.memoryUsageHistory.length > this.maxHistorySize) {
|
|
70
|
+
this.memoryUsageHistory.shift();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get memory usage trend (positive = increasing, negative = decreasing)
|
|
75
|
+
*/
|
|
76
|
+
getMemoryTrend() {
|
|
77
|
+
if (this.memoryUsageHistory.length < 2)
|
|
78
|
+
return 0;
|
|
79
|
+
const recent = this.memoryUsageHistory.slice(-10);
|
|
80
|
+
const older = this.memoryUsageHistory.slice(-20, -10);
|
|
81
|
+
if (older.length === 0)
|
|
82
|
+
return 0;
|
|
83
|
+
const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;
|
|
84
|
+
const olderAvg = older.reduce((a, b) => a + b, 0) / older.length;
|
|
85
|
+
return recentAvg - olderAvg;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Start periodic cleanup interval
|
|
89
|
+
*/
|
|
90
|
+
startCleanupInterval() {
|
|
91
|
+
if (this.cleanupInterval) {
|
|
92
|
+
clearInterval(this.cleanupInterval);
|
|
93
|
+
}
|
|
94
|
+
this.cleanupInterval = setInterval(() => {
|
|
95
|
+
this.performCleanup();
|
|
96
|
+
}, this.config.cleanupInterval);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Perform memory cleanup based on eviction policy
|
|
100
|
+
*/
|
|
101
|
+
performCleanup() {
|
|
102
|
+
if (!this.checkMemoryUsage()) {
|
|
103
|
+
console.warn('Memory protection: Performing emergency cleanup');
|
|
104
|
+
this.emergencyCleanup();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Emergency cleanup when memory usage is critical
|
|
109
|
+
*/
|
|
110
|
+
emergencyCleanup() {
|
|
111
|
+
// Force garbage collection if available
|
|
112
|
+
if (typeof global !== 'undefined' && global.gc) {
|
|
113
|
+
global.gc();
|
|
114
|
+
}
|
|
115
|
+
// Clear memory usage history
|
|
116
|
+
this.memoryUsageHistory = [];
|
|
117
|
+
// Emit warning event
|
|
118
|
+
if (typeof process !== 'undefined' && process.emitWarning) {
|
|
119
|
+
process.emitWarning('Memory usage exceeded limits, emergency cleanup performed', 'MemoryProtection');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Evict items based on configured policy
|
|
124
|
+
*/
|
|
125
|
+
evictItems(items, maxCount) {
|
|
126
|
+
if (items.length <= maxCount)
|
|
127
|
+
return items;
|
|
128
|
+
switch (this.config.evictionPolicy) {
|
|
129
|
+
case 'lru':
|
|
130
|
+
return this.evictLRU(items, maxCount);
|
|
131
|
+
case 'fifo':
|
|
132
|
+
return this.evictFIFO(items, maxCount);
|
|
133
|
+
case 'random':
|
|
134
|
+
return this.evictRandom(items, maxCount);
|
|
135
|
+
default:
|
|
136
|
+
return this.evictLRU(items, maxCount);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
evictLRU(items, maxCount) {
|
|
140
|
+
// For LRU, we assume items are ordered by access time
|
|
141
|
+
return items.slice(-maxCount);
|
|
142
|
+
}
|
|
143
|
+
evictFIFO(items, maxCount) {
|
|
144
|
+
// For FIFO, we keep the most recent items
|
|
145
|
+
return items.slice(-maxCount);
|
|
146
|
+
}
|
|
147
|
+
evictRandom(items, maxCount) {
|
|
148
|
+
const shuffled = [...items].sort(() => Math.random() - 0.5);
|
|
149
|
+
return shuffled.slice(0, maxCount);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get memory protection statistics
|
|
153
|
+
*/
|
|
154
|
+
getStats() {
|
|
155
|
+
return {
|
|
156
|
+
currentUsage: this.getCurrentMemoryUsage(),
|
|
157
|
+
maxUsage: this.config.maxMemoryUsage,
|
|
158
|
+
usagePercentage: (this.getCurrentMemoryUsage() / this.config.maxMemoryUsage) * 100,
|
|
159
|
+
trend: this.getMemoryTrend(),
|
|
160
|
+
historySize: this.memoryUsageHistory.length
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Cleanup resources
|
|
165
|
+
*/
|
|
166
|
+
destroy() {
|
|
167
|
+
if (this.cleanupInterval) {
|
|
168
|
+
clearInterval(this.cleanupInterval);
|
|
169
|
+
this.cleanupInterval = undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.MemoryProtectionManager = MemoryProtectionManager;
|
|
174
|
+
/**
|
|
175
|
+
* Default memory protection configuration
|
|
176
|
+
*/
|
|
177
|
+
exports.DEFAULT_MEMORY_PROTECTION_CONFIG = {
|
|
178
|
+
maxMemoryUsage: 100 * 1024 * 1024, // 100MB
|
|
179
|
+
maxRetryStates: 1000,
|
|
180
|
+
cleanupInterval: 30000, // 30 seconds
|
|
181
|
+
evictionPolicy: 'lru'
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=memory-protection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-protection.js","sourceRoot":"","sources":["../src/memory-protection.ts"],"names":[],"mappings":"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"}
|