zario 0.3.6 → 0.4.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.
Files changed (33) hide show
  1. package/README.md +102 -359
  2. package/dist/cjs/aggregation/LogAggregator.js +14 -6
  3. package/dist/cjs/core/Logger.js +36 -11
  4. package/dist/cjs/index.js +4 -1
  5. package/dist/cjs/transports/CircuitBreakerTransport.js +171 -0
  6. package/dist/cjs/transports/DeadLetterQueue.js +129 -0
  7. package/dist/cjs/transports/FileTransport.js +78 -7
  8. package/dist/cjs/transports/HttpTransport.js +15 -3
  9. package/dist/cjs/transports/RetryTransport.js +199 -0
  10. package/dist/cjs/transports/index.js +3 -0
  11. package/dist/cjs/types/TypeInterfaces.js +2 -0
  12. package/dist/esm/aggregation/LogAggregator.d.ts +4 -2
  13. package/dist/esm/aggregation/LogAggregator.js +14 -6
  14. package/dist/esm/core/Logger.d.ts +11 -2
  15. package/dist/esm/core/Logger.js +36 -11
  16. package/dist/esm/index.d.ts +2 -2
  17. package/dist/esm/index.js +2 -2
  18. package/dist/esm/transports/CircuitBreakerTransport.d.ts +43 -0
  19. package/dist/esm/transports/CircuitBreakerTransport.js +167 -0
  20. package/dist/esm/transports/DeadLetterQueue.d.ts +34 -0
  21. package/dist/esm/transports/DeadLetterQueue.js +92 -0
  22. package/dist/esm/transports/FileTransport.d.ts +4 -0
  23. package/dist/esm/transports/FileTransport.js +78 -7
  24. package/dist/esm/transports/HttpTransport.d.ts +2 -0
  25. package/dist/esm/transports/HttpTransport.js +15 -3
  26. package/dist/esm/transports/RetryTransport.d.ts +67 -0
  27. package/dist/esm/transports/RetryTransport.js +195 -0
  28. package/dist/esm/transports/Transport.d.ts +1 -0
  29. package/dist/esm/transports/index.d.ts +3 -0
  30. package/dist/esm/transports/index.js +3 -0
  31. package/dist/esm/types/TypeInterfaces.d.ts +7 -0
  32. package/dist/esm/types/TypeInterfaces.js +1 -0
  33. package/package.json +87 -72
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CircuitBreakerTransport = void 0;
4
+ class CircuitBreakerTransport {
5
+ constructor(baseTransport, options = {}) {
6
+ this.baseTransport = this.createTransport(baseTransport);
7
+ this.options = options;
8
+ this.state = {
9
+ failureCount: 0,
10
+ lastFailureTime: 0,
11
+ state: 'open'
12
+ };
13
+ this.metrics = {
14
+ totalRequests: 0,
15
+ failedRequests: 0,
16
+ successfulRequests: 0,
17
+ currentState: 'open',
18
+ averageResponseTime: 0
19
+ };
20
+ }
21
+ createTransport(config) {
22
+ if (typeof config === 'function') {
23
+ const TransportClass = config;
24
+ return new TransportClass();
25
+ }
26
+ else if (typeof config === 'object' && config !== null) {
27
+ return config;
28
+ }
29
+ else {
30
+ throw new Error('Invalid transport configuration');
31
+ }
32
+ }
33
+ write(data, formatter) {
34
+ if (!this.canWrite()) {
35
+ throw new Error('Circuit breaker is open');
36
+ }
37
+ this.metrics.totalRequests++;
38
+ const startTime = Date.now();
39
+ try {
40
+ this.baseTransport.write(data, formatter);
41
+ this.metrics.successfulRequests++;
42
+ this.resetFailureCount();
43
+ }
44
+ catch (error) {
45
+ this.metrics.failedRequests++;
46
+ this.recordFailure();
47
+ throw error;
48
+ }
49
+ const duration = Date.now() - startTime;
50
+ this.updateAverageResponseTime(duration);
51
+ }
52
+ async writeAsync(data, formatter) {
53
+ if (!this.canWrite()) {
54
+ throw new Error('Circuit breaker is open');
55
+ }
56
+ this.metrics.totalRequests++;
57
+ const startTime = Date.now();
58
+ try {
59
+ if (this.baseTransport.writeAsync) {
60
+ await this.baseTransport.writeAsync(data, formatter);
61
+ }
62
+ else {
63
+ this.baseTransport.write(data, formatter);
64
+ }
65
+ this.metrics.successfulRequests++;
66
+ this.resetFailureCount();
67
+ }
68
+ catch (error) {
69
+ this.metrics.failedRequests++;
70
+ this.recordFailure();
71
+ throw error;
72
+ }
73
+ const duration = Date.now() - startTime;
74
+ this.updateAverageResponseTime(duration);
75
+ }
76
+ canWrite() {
77
+ const threshold = this.options.threshold || 5;
78
+ const timeout = this.options.timeout || 60000;
79
+ if (this.state.state === 'closed') {
80
+ const timeSinceOpen = Date.now() - this.state.lastFailureTime;
81
+ if (timeSinceOpen >= timeout) {
82
+ this.setState('open');
83
+ return true;
84
+ }
85
+ return false;
86
+ }
87
+ if (this.state.failureCount >= threshold) {
88
+ this.setState('closed');
89
+ return false;
90
+ }
91
+ if (this.state.state === 'half-open' && this.state.lastFailureTime > 0 && Date.now() - this.state.lastFailureTime < timeout) {
92
+ this.setState('open');
93
+ return true;
94
+ }
95
+ return this.state.state === 'open' || this.state.state === 'half-open';
96
+ }
97
+ setState(newState) {
98
+ const oldState = this.state.state;
99
+ if (oldState !== newState) {
100
+ this.state.state = newState;
101
+ this.options.onStateChange?.(oldState, newState);
102
+ if (newState === 'closed') {
103
+ this.options.onTrip?.(this.state.failureCount);
104
+ }
105
+ else if (newState === 'open' && oldState === 'closed') {
106
+ this.options.onReset?.();
107
+ }
108
+ }
109
+ }
110
+ resetFailureCount() {
111
+ if (this.state.failureCount === 0)
112
+ return;
113
+ this.state.failureCount = Math.max(0, Math.floor(this.state.failureCount * 0.9));
114
+ }
115
+ updateAverageResponseTime(duration) {
116
+ if (this.metrics.totalRequests > 0) {
117
+ // Use exponential moving average: newAvg = oldAvg * 0.9 + duration * 0.1
118
+ this.metrics.averageResponseTime =
119
+ this.metrics.averageResponseTime * 0.9 + duration * 0.1;
120
+ }
121
+ }
122
+ recordFailure() {
123
+ this.state.failureCount++;
124
+ this.state.lastFailureTime = Date.now();
125
+ if (this.state.failureCount === 1) {
126
+ this.state.state = 'half-open';
127
+ }
128
+ if (this.state.failureCount >= (this.options.threshold || 5)) {
129
+ this.setState('closed');
130
+ if (this.options.resetTimeout) {
131
+ this.resetTimer = setTimeout(() => {
132
+ this.reset();
133
+ }, this.options.resetTimeout);
134
+ }
135
+ }
136
+ }
137
+ getMetrics() {
138
+ return {
139
+ ...this.metrics,
140
+ currentState: this.state.state
141
+ };
142
+ }
143
+ reset() {
144
+ this.state = {
145
+ failureCount: 0,
146
+ lastFailureTime: 0,
147
+ state: 'open'
148
+ };
149
+ this.metrics = {
150
+ totalRequests: 0,
151
+ failedRequests: 0,
152
+ successfulRequests: 0,
153
+ currentState: 'open',
154
+ averageResponseTime: 0
155
+ };
156
+ if (this.resetTimer) {
157
+ clearTimeout(this.resetTimer);
158
+ this.resetTimer = undefined;
159
+ }
160
+ }
161
+ destroy() {
162
+ if (this.resetTimer) {
163
+ clearTimeout(this.resetTimer);
164
+ this.resetTimer = undefined;
165
+ }
166
+ }
167
+ isAsyncSupported() {
168
+ return this.baseTransport.isAsyncSupported?.() || false;
169
+ }
170
+ }
171
+ exports.CircuitBreakerTransport = CircuitBreakerTransport;
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DeadLetterQueue = void 0;
37
+ class DeadLetterQueue {
38
+ constructor(options) {
39
+ this.deadLetters = [];
40
+ const { transport, maxRetries = 3, retryableErrorCodes = ['ECONNREFUSED', 'ETIMEDOUT', 'ECONNRESET', 'ENOTFOUND'], deadLetterFile, onDeadLetter, } = options;
41
+ // Initialize the wrapped transport
42
+ if (typeof transport === 'function') {
43
+ this.transport = transport();
44
+ }
45
+ else {
46
+ this.transport = transport;
47
+ }
48
+ this.maxRetries = maxRetries;
49
+ this.retryableErrorCodes = new Set(retryableErrorCodes);
50
+ if (deadLetterFile)
51
+ this.deadLetterFile = deadLetterFile;
52
+ if (onDeadLetter)
53
+ this.onDeadLetter = onDeadLetter;
54
+ }
55
+ async write(data, formatter) {
56
+ return this.writeWithRetry(data, formatter, 0);
57
+ }
58
+ async writeAsync(data, formatter) {
59
+ return this.writeWithRetry(data, formatter, 0);
60
+ }
61
+ async writeWithRetry(data, formatter, attempt) {
62
+ try {
63
+ if (this.transport.writeAsync) {
64
+ await this.transport.writeAsync(data, formatter);
65
+ }
66
+ else {
67
+ this.transport.write(data, formatter);
68
+ }
69
+ }
70
+ catch (error) {
71
+ const errorCode = error?.code || 'UNKNOWN';
72
+ // Check if this error is retryable
73
+ if (this.retryableErrorCodes.has(errorCode)) {
74
+ if (attempt < this.maxRetries) {
75
+ // Exponential backoff with jitter
76
+ const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
77
+ const jitter = Math.random() * 0.1 * delay;
78
+ await new Promise(resolve => setTimeout(resolve, delay + jitter));
79
+ return this.writeWithRetry(data, formatter, attempt + 1);
80
+ }
81
+ }
82
+ // This is a permanent failure - create dead letter
83
+ const deadLetter = {
84
+ ...data,
85
+ deadLetterReason: error instanceof Error ? error.message : String(error),
86
+ originalError: errorCode,
87
+ retryCount: attempt,
88
+ failedAt: new Date(),
89
+ };
90
+ this.deadLetters.push(deadLetter);
91
+ if (this.deadLetterFile) {
92
+ await this.writeDeadLetterToFile(deadLetter);
93
+ }
94
+ if (this.onDeadLetter) {
95
+ this.onDeadLetter(deadLetter);
96
+ }
97
+ // Re-throw the error for upstream handling
98
+ throw error;
99
+ }
100
+ }
101
+ async writeDeadLetterToFile(deadLetter) {
102
+ if (!this.deadLetterFile)
103
+ return;
104
+ try {
105
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
106
+ const deadLetterLine = JSON.stringify(deadLetter) + '\n';
107
+ await fs.promises.appendFile(this.deadLetterFile, deadLetterLine);
108
+ }
109
+ catch (error) {
110
+ console.error('Failed to write dead letter:', error);
111
+ }
112
+ }
113
+ getDeadLetters() {
114
+ return [...this.deadLetters];
115
+ }
116
+ clearDeadLetters() {
117
+ this.deadLetters = [];
118
+ }
119
+ // Clean up resources
120
+ async destroy() {
121
+ if (this.transport && typeof this.transport.destroy === 'function') {
122
+ await this.transport.destroy();
123
+ }
124
+ }
125
+ isAsyncSupported() {
126
+ return true;
127
+ }
128
+ }
129
+ exports.DeadLetterQueue = DeadLetterQueue;
@@ -42,17 +42,18 @@ const compressGzip = (0, util_1.promisify)(zlib.gzip);
42
42
  const compressDeflate = (0, util_1.promisify)(zlib.deflate);
43
43
  class FileTransport {
44
44
  constructor(options) {
45
- // batching funct
46
45
  this.batchQueue = [];
47
46
  this.batchTimer = null;
48
47
  const { path: filePath, maxSize = 10 * 1024 * 1024, maxFiles = 5, compression = "none", batchInterval = 0, // no batching
49
- compressOldFiles = true, } = options;
48
+ compressOldFiles = true, maxQueueSize = 10000, // default maximum queue size
49
+ } = options;
50
50
  this.filePath = filePath;
51
51
  this.maxSize = maxSize;
52
52
  this.maxFiles = maxFiles;
53
53
  this.compression = compression;
54
54
  this.batchInterval = batchInterval;
55
55
  this.compressOldFiles = compressOldFiles;
56
+ this.maxQueueSize = maxQueueSize;
56
57
  const dir = path.dirname(this.filePath);
57
58
  if (!fs.existsSync(dir)) {
58
59
  fs.mkdirSync(dir, { recursive: true });
@@ -69,7 +70,11 @@ class FileTransport {
69
70
  const output = formatter.format(data);
70
71
  const formattedOutput = output + "\n";
71
72
  if (this.batchInterval > 0) {
72
- // Queue entry if batching is enabled
73
+ // Queue entry if batching is enabled, with queue size limit
74
+ if (this.batchQueue.length >= this.maxQueueSize) {
75
+ // Drop oldest entry to maintain queue limit
76
+ this.batchQueue.shift();
77
+ }
73
78
  this.batchQueue.push({
74
79
  data: formattedOutput,
75
80
  timestamp: new Date(),
@@ -86,6 +91,9 @@ class FileTransport {
86
91
  async writeAsync(data, formatter) {
87
92
  const formattedOutput = formatter.format(data) + "\n";
88
93
  if (this.batchInterval > 0) {
94
+ if (this.batchQueue.length >= this.maxQueueSize) {
95
+ this.batchQueue.shift();
96
+ }
89
97
  this.batchQueue.push({
90
98
  data: formattedOutput,
91
99
  timestamp: new Date(),
@@ -117,8 +125,7 @@ class FileTransport {
117
125
  try {
118
126
  if (!fs.existsSync(this.filePath))
119
127
  return;
120
- const currentContent = fs.readFileSync(this.filePath, "utf8");
121
- this.performRotation(currentContent, fs.writeFileSync);
128
+ this.performRotationWithStreams();
122
129
  this.cleanupOldFiles();
123
130
  }
124
131
  catch (error) {
@@ -129,8 +136,7 @@ class FileTransport {
129
136
  try {
130
137
  if (!fs.existsSync(this.filePath))
131
138
  return;
132
- const currentContent = await fs.promises.readFile(this.filePath, "utf8");
133
- await this.performRotationAsync(currentContent);
139
+ await this.performRotationWithStreamsAsync();
134
140
  await this.cleanupOldFilesAsync();
135
141
  }
136
142
  catch (error) {
@@ -161,6 +167,71 @@ class FileTransport {
161
167
  }
162
168
  await fs.promises.writeFile(this.filePath, "", "utf8");
163
169
  }
170
+ performRotationWithStreams() {
171
+ const rotatedFilePath = this.getRotatedFilePath();
172
+ const readStream = fs.createReadStream(this.filePath);
173
+ if (this.compression !== "none" && this.compressOldFiles) {
174
+ const compressedFilePath = `${rotatedFilePath}.${this.compression === "gzip" ? "gz" : "zz"}`;
175
+ const writeStream = fs.createWriteStream(compressedFilePath);
176
+ const compressStream = this.compression === "gzip" ? zlib.createGzip() : zlib.createDeflate();
177
+ readStream.pipe(compressStream).pipe(writeStream);
178
+ writeStream.on('finish', () => {
179
+ fs.writeFileSync(this.filePath, "", "utf8");
180
+ });
181
+ writeStream.on('error', (error) => {
182
+ console.error("Error during stream compression:", error);
183
+ });
184
+ }
185
+ else {
186
+ const writeStream = fs.createWriteStream(rotatedFilePath);
187
+ readStream.pipe(writeStream);
188
+ writeStream.on('finish', () => {
189
+ fs.writeFileSync(this.filePath, "", "utf8");
190
+ });
191
+ writeStream.on('error', (error) => {
192
+ console.error("Error during stream rotation:", error);
193
+ });
194
+ }
195
+ }
196
+ async performRotationWithStreamsAsync() {
197
+ return new Promise((resolve, reject) => {
198
+ const rotatedFilePath = this.getRotatedFilePath();
199
+ const readStream = fs.createReadStream(this.filePath);
200
+ if (this.compression !== "none" && this.compressOldFiles) {
201
+ const compressedFilePath = `${rotatedFilePath}.${this.compression === "gzip" ? "gz" : "zz"}`;
202
+ const writeStream = fs.createWriteStream(compressedFilePath);
203
+ const compressStream = this.compression === "gzip" ? zlib.createGzip() : zlib.createDeflate();
204
+ readStream.pipe(compressStream).pipe(writeStream);
205
+ writeStream.on('finish', async () => {
206
+ try {
207
+ await fs.promises.writeFile(this.filePath, "", "utf8");
208
+ resolve();
209
+ }
210
+ catch (error) {
211
+ reject(error);
212
+ }
213
+ });
214
+ writeStream.on('error', reject);
215
+ readStream.on('error', reject);
216
+ compressStream.on('error', reject);
217
+ }
218
+ else {
219
+ const writeStream = fs.createWriteStream(rotatedFilePath);
220
+ readStream.pipe(writeStream);
221
+ writeStream.on('finish', async () => {
222
+ try {
223
+ await fs.promises.writeFile(this.filePath, "", "utf8");
224
+ resolve();
225
+ }
226
+ catch (error) {
227
+ reject(error);
228
+ }
229
+ });
230
+ writeStream.on('error', reject);
231
+ readStream.on('error', reject);
232
+ }
233
+ });
234
+ }
164
235
  getRotatedFilePath() {
165
236
  const dir = path.dirname(this.filePath);
166
237
  return path.join(dir, `${path.basename(this.filePath)}.${Date.now()}`);
@@ -39,7 +39,8 @@ const https = __importStar(require("https"));
39
39
  const url = __importStar(require("url"));
40
40
  class HttpTransport {
41
41
  constructor(options) {
42
- const { url, method = 'POST', headers = {}, timeout = 5000, retries = 3 // defaults
42
+ const { url, method = 'POST', headers = {}, timeout = 5000, retries = 3, // defaults
43
+ forceAsync = false // Force async mode even in write() method
43
44
  } = options;
44
45
  if (!url) {
45
46
  throw new Error('HttpTransport requires a URL option');
@@ -49,6 +50,7 @@ class HttpTransport {
49
50
  this.headers = { ...headers };
50
51
  this.timeout = timeout;
51
52
  this.retries = retries;
53
+ this.forceAsync = forceAsync;
52
54
  // Set default Content-Type if not provided
53
55
  if (!this.headers['Content-Type'] && !this.headers['content-type']) {
54
56
  this.headers['Content-Type'] = 'application/json';
@@ -58,12 +60,22 @@ class HttpTransport {
58
60
  // Format the data as JSON for HTTP transport
59
61
  const logObject = this.parseFormattedData(data);
60
62
  const body = JSON.stringify(logObject);
61
- setImmediate(() => {
63
+ if (this.forceAsync) {
64
+ // Force async mode using setImmediate
65
+ setImmediate(() => {
66
+ this.sendHttpRequestWithRetry(body, 0)
67
+ .catch((error) => {
68
+ console.error('HttpTransport error (forced async mode):', error.message);
69
+ });
70
+ });
71
+ }
72
+ else {
73
+ // Best-effort synchronous mode - note: actual network I/O is still async
62
74
  this.sendHttpRequestWithRetry(body, 0)
63
75
  .catch((error) => {
64
76
  console.error('HttpTransport error (sync mode):', error.message);
65
77
  });
66
- });
78
+ }
67
79
  }
68
80
  async writeAsync(data, formatter) {
69
81
  // json formating for HttpTransport
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RetryTransport = exports.CircuitBreakerState = void 0;
4
+ const events_1 = require("events");
5
+ var CircuitBreakerState;
6
+ (function (CircuitBreakerState) {
7
+ CircuitBreakerState["CLOSED"] = "closed";
8
+ CircuitBreakerState["OPEN"] = "open";
9
+ CircuitBreakerState["HALF_OPEN"] = "half_open";
10
+ })(CircuitBreakerState || (exports.CircuitBreakerState = CircuitBreakerState = {}));
11
+ class RetryTransport extends events_1.EventEmitter {
12
+ constructor(options) {
13
+ super();
14
+ this.circuitBreakerState = CircuitBreakerState.CLOSED;
15
+ this.failureCount = 0;
16
+ this.circuitBreakerOpenTime = 0;
17
+ const { wrappedTransport, maxAttempts = 3, baseDelay = 1000, maxDelay = 30000, backoffMultiplier = 2, jitter = true, retryableErrorCodes = [
18
+ 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND',
19
+ 'EAI_AGAIN', 'EHOSTUNREACH', 'ENETUNREACH', 'ENOENT',
20
+ 'EMFILE', 'ENFILE'
21
+ ], retryableErrorPatterns = [
22
+ /timeout/i,
23
+ /network/i,
24
+ /connection/i,
25
+ /temporary/i,
26
+ /rate limit/i,
27
+ /too many requests/i,
28
+ /service unavailable/i,
29
+ /bad gateway/i
30
+ ], circuitBreakerThreshold = 5, circuitBreakerTimeout = 60000, onRetryAttempt, onRetryExhausted, onCircuitBreakerOpen, onCircuitBreakerClose } = options;
31
+ if (!wrappedTransport) {
32
+ throw new Error('RetryTransport requires a wrappedTransport');
33
+ }
34
+ this.wrappedTransport = wrappedTransport;
35
+ this.maxAttempts = maxAttempts;
36
+ this.baseDelay = baseDelay;
37
+ this.maxDelay = maxDelay;
38
+ this.backoffMultiplier = backoffMultiplier;
39
+ this.jitter = jitter;
40
+ this.retryableErrorCodes = new Set(retryableErrorCodes);
41
+ this.retryableErrorPatterns = retryableErrorPatterns;
42
+ this.circuitBreakerThreshold = circuitBreakerThreshold;
43
+ this.circuitBreakerTimeout = circuitBreakerTimeout;
44
+ this.onRetryAttempt = onRetryAttempt;
45
+ this.onRetryExhausted = onRetryExhausted;
46
+ this.onCircuitBreakerOpen = onCircuitBreakerOpen;
47
+ this.onCircuitBreakerClose = onCircuitBreakerClose;
48
+ }
49
+ write(data, formatter) {
50
+ setImmediate(async () => {
51
+ try {
52
+ await this.writeWithRetry(data, formatter);
53
+ }
54
+ catch (error) {
55
+ this.emit('error', { type: 'retry_transport_exhausted', error });
56
+ }
57
+ });
58
+ }
59
+ async writeAsync(data, formatter) {
60
+ return this.writeWithRetry(data, formatter);
61
+ }
62
+ async writeWithRetry(data, formatter) {
63
+ if (this.isCircuitBreakerOpen()) {
64
+ throw new Error('Circuit breaker is open - rejecting requests');
65
+ }
66
+ let lastError = null;
67
+ const startTime = Date.now();
68
+ for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
69
+ try {
70
+ if (this.wrappedTransport.writeAsync) {
71
+ await this.wrappedTransport.writeAsync(data, formatter);
72
+ }
73
+ else {
74
+ await new Promise((resolve, reject) => {
75
+ try {
76
+ this.wrappedTransport.write(data, formatter);
77
+ resolve();
78
+ }
79
+ catch (error) {
80
+ reject(error);
81
+ }
82
+ });
83
+ }
84
+ this.resetFailureCount();
85
+ this.maybeCloseCircuitBreaker();
86
+ return;
87
+ }
88
+ catch (error) {
89
+ lastError = error;
90
+ if (!this.isRetryableError(lastError) || attempt === this.maxAttempts) {
91
+ break;
92
+ }
93
+ const delay = this.calculateDelay(attempt);
94
+ const retryContext = {
95
+ attempt,
96
+ totalAttempts: this.maxAttempts,
97
+ originalError: lastError,
98
+ delay,
99
+ startTime
100
+ };
101
+ this.emit('retryAttempt', retryContext);
102
+ this.onRetryAttempt?.(attempt, lastError, delay);
103
+ await this.delay(delay);
104
+ }
105
+ }
106
+ this.incrementFailureCount();
107
+ this.maybeOpenCircuitBreaker();
108
+ const errorContext = {
109
+ lastError,
110
+ attempts: this.maxAttempts,
111
+ totalTime: Date.now() - startTime,
112
+ data: {
113
+ level: data.level,
114
+ message: data.message,
115
+ timestamp: data.timestamp
116
+ }
117
+ };
118
+ this.emit('retryExhausted', errorContext);
119
+ this.onRetryExhausted?.(lastError, this.maxAttempts);
120
+ throw lastError;
121
+ }
122
+ isRetryableError(error) {
123
+ const errorCode = error.code;
124
+ const errorMessage = error.message;
125
+ if (errorCode && this.retryableErrorCodes.has(errorCode)) {
126
+ return true;
127
+ }
128
+ for (const pattern of this.retryableErrorPatterns) {
129
+ if (pattern.test(errorMessage)) {
130
+ return true;
131
+ }
132
+ }
133
+ return false;
134
+ }
135
+ calculateDelay(attempt) {
136
+ let delay = this.baseDelay * Math.pow(this.backoffMultiplier, attempt - 1);
137
+ delay = Math.min(delay, this.maxDelay);
138
+ if (this.jitter) {
139
+ const jitterAmount = delay * 0.25;
140
+ const jitter = (Math.random() * 2 - 1) * jitterAmount;
141
+ delay += jitter;
142
+ }
143
+ return Math.max(0, Math.floor(delay));
144
+ }
145
+ delay(ms) {
146
+ return new Promise(resolve => setTimeout(resolve, ms));
147
+ }
148
+ isCircuitBreakerOpen() {
149
+ if (this.circuitBreakerState === CircuitBreakerState.OPEN) {
150
+ if (Date.now() - this.circuitBreakerOpenTime >= this.circuitBreakerTimeout) {
151
+ this.circuitBreakerState = CircuitBreakerState.HALF_OPEN;
152
+ this.emit('circuitBreakerHalfOpen');
153
+ return false;
154
+ }
155
+ return true;
156
+ }
157
+ return false;
158
+ }
159
+ incrementFailureCount() {
160
+ this.failureCount++;
161
+ this.maybeOpenCircuitBreaker();
162
+ }
163
+ resetFailureCount() {
164
+ this.failureCount = 0;
165
+ }
166
+ maybeOpenCircuitBreaker() {
167
+ if (this.circuitBreakerState === CircuitBreakerState.CLOSED &&
168
+ this.failureCount >= this.circuitBreakerThreshold) {
169
+ this.circuitBreakerState = CircuitBreakerState.OPEN;
170
+ this.circuitBreakerOpenTime = Date.now();
171
+ this.emit('circuitBreakerOpen');
172
+ this.onCircuitBreakerOpen?.();
173
+ }
174
+ }
175
+ maybeCloseCircuitBreaker() {
176
+ if (this.circuitBreakerState === CircuitBreakerState.HALF_OPEN) {
177
+ this.circuitBreakerState = CircuitBreakerState.CLOSED;
178
+ this.failureCount = 0;
179
+ this.emit('circuitBreakerClose');
180
+ this.onCircuitBreakerClose?.();
181
+ }
182
+ }
183
+ getCircuitBreakerState() {
184
+ return this.circuitBreakerState;
185
+ }
186
+ getFailureCount() {
187
+ return this.failureCount;
188
+ }
189
+ resetCircuitBreaker() {
190
+ this.circuitBreakerState = CircuitBreakerState.CLOSED;
191
+ this.failureCount = 0;
192
+ this.circuitBreakerOpenTime = 0;
193
+ this.emit('circuitBreakerReset');
194
+ }
195
+ getWrappedTransport() {
196
+ return this.wrappedTransport;
197
+ }
198
+ }
199
+ exports.RetryTransport = RetryTransport;
@@ -19,3 +19,6 @@ __exportStar(require("./ConsoleTransport.js"), exports);
19
19
  __exportStar(require("./FileTransport.js"), exports);
20
20
  __exportStar(require("./HttpTransport.js"), exports);
21
21
  __exportStar(require("./FilterableTransport.js"), exports);
22
+ __exportStar(require("./RetryTransport.js"), exports);
23
+ __exportStar(require("./CircuitBreakerTransport.js"), exports);
24
+ __exportStar(require("./DeadLetterQueue.js"), exports);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });