@mhmdhammoud/meritt-utils 1.5.9 → 1.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.
@@ -33,24 +33,12 @@ function getIndexName(index, time) {
33
33
  }
34
34
  return index.replace('%{DATE}', time.substring(0, 10));
35
35
  }
36
- function initializeBulkHandler(opts, client, splitter) {
36
+ function initializeBulkHandler(opts, client, splitter, onFatalError) {
37
37
  var _a, _b, _c, _d, _e, _f, _g;
38
38
  const esVersion = Number((_b = (_a = opts.esVersion) !== null && _a !== void 0 ? _a : opts['es-version']) !== null && _b !== void 0 ? _b : 7);
39
39
  const index = (_c = opts.index) !== null && _c !== void 0 ? _c : 'pino';
40
40
  const buildIndexName = typeof index === 'function' ? index : null;
41
41
  const opType = esVersion >= 7 ? undefined : undefined;
42
- // CRITICAL FIX (issue #140): When bulk helper destroys stream after retries exhausted,
43
- // we must BOTH resurrect the pool AND reinitialize the bulk handler so logging continues.
44
- // connectionPool.resurrect exists at runtime (elastic-transport) but may not be in types
45
- const pool = client.connectionPool;
46
- const splitterWithDestroy = splitter;
47
- splitterWithDestroy.destroy = function () {
48
- if (typeof pool.resurrect === 'function') {
49
- pool.resurrect({ name: 'elasticsearch-js' });
50
- }
51
- // Reinitialize bulk handler - without this, logging stops permanently until restart
52
- initializeBulkHandler(opts, client, splitter);
53
- };
54
42
  const indexName = (time = new Date().toISOString()) => buildIndexName ? buildIndexName(time) : getIndexName(index, time);
55
43
  const bulkInsert = client.helpers.bulk({
56
44
  datasource: splitter,
@@ -77,7 +65,10 @@ function initializeBulkHandler(opts, client, splitter) {
77
65
  splitter.emit('insertError', error);
78
66
  },
79
67
  });
80
- bulkInsert.then((stats) => splitter.emit('insert', stats), (err) => splitter.emit('error', err));
68
+ bulkInsert.then((stats) => splitter.emit('insert', stats), (err) => {
69
+ splitter.emit('error', err);
70
+ onFatalError(err);
71
+ });
81
72
  }
82
73
  const createElasticTransport = (opts = {}) => {
83
74
  const splitter = split(function (line) {
@@ -128,10 +119,56 @@ const createElasticTransport = (opts = {}) => {
128
119
  clientOpts.ConnectionPool = opts.ConnectionPool;
129
120
  }
130
121
  const client = new elasticsearch_1.Client(clientOpts);
122
+ // CRITICAL FIX (pino-elasticsearch issues #140/#72): after retries are
123
+ // exhausted the bulk helper can stop consuming the stream while the process
124
+ // stays alive. Keep exactly one helper active and replace it after fatal
125
+ // helper failures instead of waiting for a server restart.
126
+ let isBulkHandlerActive = false;
127
+ let isRestartScheduled = false;
128
+ let isTransportClosed = false;
129
+ const pool = client.connectionPool;
130
+ const splitterWithDestroy = splitter;
131
+ const originalDestroy = splitterWithDestroy.destroy.bind(splitterWithDestroy);
132
+ const startBulkHandler = () => {
133
+ if (isTransportClosed || isBulkHandlerActive) {
134
+ return;
135
+ }
136
+ isBulkHandlerActive = true;
137
+ initializeBulkHandler(opts, client, splitter, () => {
138
+ isBulkHandlerActive = false;
139
+ scheduleBulkHandlerRestart();
140
+ });
141
+ };
142
+ const scheduleBulkHandlerRestart = () => {
143
+ var _a, _b, _c;
144
+ if (isTransportClosed || isRestartScheduled) {
145
+ return;
146
+ }
147
+ isRestartScheduled = true;
148
+ if (typeof pool.resurrect === 'function') {
149
+ pool.resurrect({ name: 'elasticsearch-js' });
150
+ }
151
+ const retryDelayMs = Math.min(Number((_b = (_a = opts.flushInterval) !== null && _a !== void 0 ? _a : opts['flush-interval']) !== null && _b !== void 0 ? _b : 3000), 5000);
152
+ const timer = setTimeout(() => {
153
+ isRestartScheduled = false;
154
+ startBulkHandler();
155
+ }, retryDelayMs);
156
+ (_c = timer.unref) === null || _c === void 0 ? void 0 : _c.call(timer);
157
+ };
158
+ splitterWithDestroy.destroy = function (err) {
159
+ if (err && !isTransportClosed) {
160
+ scheduleBulkHandlerRestart();
161
+ return;
162
+ }
163
+ isTransportClosed = true;
164
+ originalDestroy(err);
165
+ };
131
166
  client.diagnostic.on('resurrect', () => {
132
- initializeBulkHandler(opts, client, splitter);
167
+ if (!isBulkHandlerActive) {
168
+ scheduleBulkHandlerRestart();
169
+ }
133
170
  });
134
- initializeBulkHandler(opts, client, splitter);
171
+ startBulkHandler();
135
172
  return splitter;
136
173
  };
137
174
  exports.createElasticTransport = createElasticTransport;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhmdhammoud/meritt-utils",
3
- "version": "1.5.9",
3
+ "version": "1.6.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
@@ -76,30 +76,14 @@ function getIndexName(
76
76
  function initializeBulkHandler(
77
77
  opts: ElasticTransportOptions,
78
78
  client: Client,
79
- splitter: NodeJS.ReadWriteStream
79
+ splitter: NodeJS.ReadWriteStream,
80
+ onFatalError: (err: Error) => void
80
81
  ): void {
81
82
  const esVersion = Number(opts.esVersion ?? opts['es-version'] ?? 7)
82
83
  const index = opts.index ?? 'pino'
83
84
  const buildIndexName = typeof index === 'function' ? index : null
84
85
  const opType = esVersion >= 7 ? undefined : undefined
85
86
 
86
- // CRITICAL FIX (issue #140): When bulk helper destroys stream after retries exhausted,
87
- // we must BOTH resurrect the pool AND reinitialize the bulk handler so logging continues.
88
- // connectionPool.resurrect exists at runtime (elastic-transport) but may not be in types
89
- const pool = client.connectionPool as {
90
- resurrect?: (opts: { name: string }) => void
91
- }
92
- const splitterWithDestroy = splitter as NodeJS.ReadWriteStream & {
93
- destroy: (err?: Error) => void
94
- }
95
- splitterWithDestroy.destroy = function () {
96
- if (typeof pool.resurrect === 'function') {
97
- pool.resurrect({ name: 'elasticsearch-js' })
98
- }
99
- // Reinitialize bulk handler - without this, logging stops permanently until restart
100
- initializeBulkHandler(opts, client, splitter)
101
- }
102
-
103
87
  const indexName = (time = new Date().toISOString()) =>
104
88
  buildIndexName ? buildIndexName(time) : getIndexName(index as string, time)
105
89
 
@@ -132,7 +116,10 @@ function initializeBulkHandler(
132
116
 
133
117
  bulkInsert.then(
134
118
  (stats) => splitter.emit('insert', stats),
135
- (err) => splitter.emit('error', err)
119
+ (err) => {
120
+ splitter.emit('error', err)
121
+ onFatalError(err)
122
+ }
136
123
  )
137
124
  }
138
125
 
@@ -193,11 +180,70 @@ export const createElasticTransport = (
193
180
 
194
181
  const client = new Client(clientOpts)
195
182
 
183
+ // CRITICAL FIX (pino-elasticsearch issues #140/#72): after retries are
184
+ // exhausted the bulk helper can stop consuming the stream while the process
185
+ // stays alive. Keep exactly one helper active and replace it after fatal
186
+ // helper failures instead of waiting for a server restart.
187
+ let isBulkHandlerActive = false
188
+ let isRestartScheduled = false
189
+ let isTransportClosed = false
190
+
191
+ const pool = client.connectionPool as {
192
+ resurrect?: (opts: { name: string }) => void
193
+ }
194
+ const splitterWithDestroy = splitter as NodeJS.ReadWriteStream & {
195
+ destroy: (err?: Error) => void
196
+ }
197
+ const originalDestroy = splitterWithDestroy.destroy.bind(splitterWithDestroy)
198
+
199
+ const startBulkHandler = () => {
200
+ if (isTransportClosed || isBulkHandlerActive) {
201
+ return
202
+ }
203
+ isBulkHandlerActive = true
204
+ initializeBulkHandler(opts, client, splitter, () => {
205
+ isBulkHandlerActive = false
206
+ scheduleBulkHandlerRestart()
207
+ })
208
+ }
209
+
210
+ const scheduleBulkHandlerRestart = () => {
211
+ if (isTransportClosed || isRestartScheduled) {
212
+ return
213
+ }
214
+ isRestartScheduled = true
215
+
216
+ if (typeof pool.resurrect === 'function') {
217
+ pool.resurrect({ name: 'elasticsearch-js' })
218
+ }
219
+
220
+ const retryDelayMs = Math.min(
221
+ Number(opts.flushInterval ?? opts['flush-interval'] ?? 3000),
222
+ 5000
223
+ )
224
+ const timer = setTimeout(() => {
225
+ isRestartScheduled = false
226
+ startBulkHandler()
227
+ }, retryDelayMs)
228
+ timer.unref?.()
229
+ }
230
+
231
+ splitterWithDestroy.destroy = function (err?: Error) {
232
+ if (err && !isTransportClosed) {
233
+ scheduleBulkHandlerRestart()
234
+ return
235
+ }
236
+ isTransportClosed = true
237
+ originalDestroy(err)
238
+ }
239
+
196
240
  client.diagnostic.on('resurrect', () => {
197
- initializeBulkHandler(opts, client, splitter)
241
+ if (!isBulkHandlerActive) {
242
+ scheduleBulkHandlerRestart()
243
+ }
198
244
  })
199
245
 
200
- initializeBulkHandler(opts, client, splitter)
246
+ startBulkHandler()
201
247
 
202
248
  return splitter
203
249
  }