@getstrata/core 2.2.23 → 2.2.25

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 2.2.25
4
+
5
+ Queue worker run loops recover native Redis connection closure with bounded backoff and a fresh connection. Uncertain Redis mutations are not replayed; retained reservations and retries use normal recovery. Explicit closure remains terminal and non-connection errors remain fatal. Native poll/ack closure regressions cover Streams and list transports.
6
+
7
+ ## 2.2.24
8
+
9
+ Fix Redis cache recovery after transport failures and command deadlines: retire failed connections, abort active fills, and lazily reconnect for later operations without replaying uncertain commands; preserve permanent explicit closure and shared invalidation fencing.
10
+
3
11
  ## 2.2.23
4
12
 
5
13
  Fix native SMTP hangs with bounded whole-send deadlines, cancellation, buffered multiline replies, complete writes and verified implicit TLS. Preserve known final DATA acceptance through cleanup failures. Correct DATA transparency, CRLF, UTF-8 MIME/header encoding and long-line framing; reject incomplete AUTH and unsupported SMTPUTF8 envelopes explicitly. Generate documented SMTP timeout and TLS defaults. Real socket/TLS/protocol regressions cover outages, cancellation, accepted sends and framing; SMTP delivery before final acknowledgement remains uncertain.
@@ -9,14 +9,16 @@ type RedisCacheOptions = {
9
9
  metadataLimit?: number;
10
10
  };
11
11
  declare class RedisCacheStore implements CacheStore {
12
+ private readonly redisUrl;
12
13
  private readonly ttlMs;
13
14
  private readonly maxEntries;
14
- private readonly client;
15
+ private client;
15
16
  private readonly keys;
16
17
  private readonly options;
17
18
  private readonly fills;
18
19
  private closed;
19
20
  constructor(redisUrl: string, ttlMs: number, maxEntries: number, options?: RedisCacheOptions);
21
+ private createClient;
20
22
  close(): void;
21
23
  get<T>(key: string): Promise<T | undefined>;
22
24
  set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
@@ -27,13 +27,14 @@ declare class RedisQueue implements Queue {
27
27
  dispatch<TPayload extends object>(job: Job<TPayload>, payload: TPayload): Promise<void>;
28
28
  }
29
29
  declare class QueueWorker {
30
+ private readonly redisUrl;
30
31
  private readonly failedJobs;
31
32
  private readonly timeoutSeconds;
32
33
  private readonly queueKeys;
33
34
  private readonly owner;
34
35
  private running;
35
36
  private stopping;
36
- private readonly client;
37
+ private client;
37
38
  constructor(redisUrl: string, failedJobs: FailedJobService, timeoutSeconds?: number, queueKeys?: readonly string[]);
38
39
  requestStop(): void;
39
40
  isRunning(): boolean;
@@ -49,10 +49,11 @@ declare class RedisStreamsQueue implements Queue {
49
49
  cancel(jobId: string): Promise<boolean>;
50
50
  }
51
51
  declare class RedisStreamsWorker {
52
+ private readonly redisUrl;
52
53
  private readonly failedJobs;
53
54
  private readonly timeoutSeconds;
54
55
  private readonly queueKeys;
55
- private readonly client;
56
+ private client;
56
57
  private readonly owner;
57
58
  private readonly cursors;
58
59
  private processing;
@@ -289,6 +289,7 @@ return redis.error_reply('Unknown cache operation')
289
289
  `;
290
290
 
291
291
  class RedisCacheStore {
292
+ redisUrl;
292
293
  ttlMs;
293
294
  maxEntries;
294
295
  client;
@@ -297,6 +298,7 @@ class RedisCacheStore {
297
298
  fills = new Set;
298
299
  closed = false;
299
300
  constructor(redisUrl, ttlMs, maxEntries, options = {}) {
301
+ this.redisUrl = redisUrl;
300
302
  this.ttlMs = ttlMs;
301
303
  this.maxEntries = maxEntries;
302
304
  if (!Number.isSafeInteger(ttlMs) || ttlMs < 0)
@@ -321,7 +323,10 @@ class RedisCacheStore {
321
323
  const namespace = namespacedRedisKey("cache:v3:");
322
324
  const slot = new Bun.CryptoHasher("sha256").update(namespace).digest("hex");
323
325
  this.keys = ["entries", "expiry", "order", "metadata", "metadata-expiry"].map((name) => `${namespace}{${slot}}:${name}`);
324
- this.client = new RedisClient(redisUrl, {
326
+ this.client = this.createClient();
327
+ }
328
+ createClient() {
329
+ return new RedisClient(this.redisUrl, {
325
330
  connectionTimeout: this.options.commandTimeoutMs,
326
331
  maxRetries: 1
327
332
  });
@@ -330,7 +335,8 @@ class RedisCacheStore {
330
335
  this.closed = true;
331
336
  for (const fill of this.fills)
332
337
  fill.abort(new Error("Redis cache store is closed."));
333
- this.client.close();
338
+ this.client?.close();
339
+ this.client = undefined;
334
340
  }
335
341
  async get(key) {
336
342
  const result = await this.execute("get", key);
@@ -409,6 +415,7 @@ class RedisCacheStore {
409
415
  Promise.resolve().then(() => loader(controller.signal)),
410
416
  aborted
411
417
  ]);
418
+ controller.signal.throwIfAborted();
412
419
  done = true;
413
420
  clearInterval(renew);
414
421
  await this.execute("publish", key, {
@@ -450,16 +457,28 @@ class RedisCacheStore {
450
457
  const ttlMs = extra.ttlMs ?? this.ttlMs;
451
458
  if (typeof ttlMs !== "number" || !Number.isSafeInteger(ttlMs) || ttlMs < 0)
452
459
  throw new RangeError("ttlMs must be a non-negative safe integer.");
460
+ if (!this.client)
461
+ this.client = this.createClient();
462
+ const client = this.client;
463
+ const retire = (reason) => {
464
+ if (this.client !== client)
465
+ return;
466
+ this.client = undefined;
467
+ for (const fill of this.fills)
468
+ fill.abort(reason);
469
+ client.close();
470
+ };
453
471
  let timer;
454
472
  const timeout = new Promise((_, reject) => {
455
473
  timer = setTimeout(() => {
456
- reject(new Error("Redis cache command deadline exceeded; recreate the store."));
457
- this.close();
474
+ const error = new Error("Redis cache command deadline exceeded.");
475
+ reject(error);
476
+ retire(error);
458
477
  }, this.options.commandTimeoutMs);
459
478
  });
460
479
  try {
461
480
  const raw = await Promise.race([
462
- this.client.send("EVAL", [
481
+ client.send("EVAL", [
463
482
  CACHE_SCRIPT,
464
483
  String(this.keys.length),
465
484
  ...this.keys,
@@ -477,7 +496,13 @@ class RedisCacheStore {
477
496
  ]),
478
497
  timeout
479
498
  ]);
499
+ if (this.closed || this.client !== client)
500
+ throw new Error("Redis cache connection was retired.");
480
501
  return JSON.parse(String(raw));
502
+ } catch (error) {
503
+ if (error && typeof error === "object" && "code" in error && typeof error.code === "string" && error.code.startsWith("ERR_REDIS_CONNECTION"))
504
+ retire(error);
505
+ throw error;
481
506
  } finally {
482
507
  clearTimeout(timer);
483
508
  }
@@ -690,6 +690,7 @@ class RedisStreamsQueue {
690
690
  }
691
691
 
692
692
  class RedisStreamsWorker {
693
+ redisUrl;
693
694
  failedJobs;
694
695
  timeoutSeconds;
695
696
  queueKeys;
@@ -702,6 +703,7 @@ class RedisStreamsWorker {
702
703
  stopping = false;
703
704
  active;
704
705
  constructor(redisUrl, failedJobs, timeoutSeconds = 5, queueKeys = defaultQueueKeys()) {
706
+ this.redisUrl = redisUrl;
705
707
  this.failedJobs = failedJobs;
706
708
  this.timeoutSeconds = timeoutSeconds;
707
709
  this.queueKeys = queueKeys;
@@ -891,8 +893,21 @@ class RedisStreamsWorker {
891
893
  async run() {
892
894
  this.running = true;
893
895
  try {
894
- while (!this.stopping)
895
- await this.processNext();
896
+ while (!this.stopping) {
897
+ try {
898
+ await this.processNext();
899
+ } catch (error) {
900
+ if (!(error && typeof error === "object" && ("code" in error) && error.code === "ERR_REDIS_CONNECTION_CLOSED"))
901
+ throw error;
902
+ this.client.close();
903
+ await Bun.sleep(250);
904
+ if (this.stopping)
905
+ return;
906
+ this.client = new RedisClient(this.redisUrl);
907
+ this.initialized = false;
908
+ this.cursors.clear();
909
+ }
910
+ }
896
911
  } finally {
897
912
  this.running = false;
898
913
  }
@@ -1195,6 +1210,7 @@ class RedisQueue {
1195
1210
  }
1196
1211
 
1197
1212
  class QueueWorker {
1213
+ redisUrl;
1198
1214
  failedJobs;
1199
1215
  timeoutSeconds;
1200
1216
  queueKeys;
@@ -1203,6 +1219,7 @@ class QueueWorker {
1203
1219
  stopping = false;
1204
1220
  client;
1205
1221
  constructor(redisUrl, failedJobs, timeoutSeconds = 5, queueKeys = QUEUE_KEYS) {
1222
+ this.redisUrl = redisUrl;
1206
1223
  this.failedJobs = failedJobs;
1207
1224
  this.timeoutSeconds = timeoutSeconds;
1208
1225
  this.queueKeys = queueKeys;
@@ -1283,13 +1300,24 @@ class QueueWorker {
1283
1300
  this.running = true;
1284
1301
  try {
1285
1302
  while (!this.stopping) {
1286
- await this.processNext();
1303
+ try {
1304
+ await this.processNext();
1305
+ } catch (error) {
1306
+ if (!(error && typeof error === "object" && ("code" in error) && error.code === "ERR_REDIS_CONNECTION_CLOSED"))
1307
+ throw error;
1308
+ this.client.close();
1309
+ await Bun.sleep(250);
1310
+ if (this.stopping)
1311
+ return;
1312
+ this.client = new RedisClient2(this.redisUrl);
1313
+ }
1287
1314
  }
1288
1315
  } finally {
1289
1316
  this.running = false;
1290
1317
  }
1291
1318
  }
1292
1319
  close() {
1320
+ this.stopping = true;
1293
1321
  this.client.close();
1294
1322
  }
1295
1323
  }
@@ -690,6 +690,7 @@ class RedisStreamsQueue {
690
690
  }
691
691
 
692
692
  class RedisStreamsWorker {
693
+ redisUrl;
693
694
  failedJobs;
694
695
  timeoutSeconds;
695
696
  queueKeys;
@@ -702,6 +703,7 @@ class RedisStreamsWorker {
702
703
  stopping = false;
703
704
  active;
704
705
  constructor(redisUrl, failedJobs, timeoutSeconds = 5, queueKeys = defaultQueueKeys()) {
706
+ this.redisUrl = redisUrl;
705
707
  this.failedJobs = failedJobs;
706
708
  this.timeoutSeconds = timeoutSeconds;
707
709
  this.queueKeys = queueKeys;
@@ -891,8 +893,21 @@ class RedisStreamsWorker {
891
893
  async run() {
892
894
  this.running = true;
893
895
  try {
894
- while (!this.stopping)
895
- await this.processNext();
896
+ while (!this.stopping) {
897
+ try {
898
+ await this.processNext();
899
+ } catch (error) {
900
+ if (!(error && typeof error === "object" && ("code" in error) && error.code === "ERR_REDIS_CONNECTION_CLOSED"))
901
+ throw error;
902
+ this.client.close();
903
+ await Bun.sleep(250);
904
+ if (this.stopping)
905
+ return;
906
+ this.client = new RedisClient(this.redisUrl);
907
+ this.initialized = false;
908
+ this.cursors.clear();
909
+ }
910
+ }
896
911
  } finally {
897
912
  this.running = false;
898
913
  }
@@ -1195,6 +1210,7 @@ class RedisQueue {
1195
1210
  }
1196
1211
 
1197
1212
  class QueueWorker {
1213
+ redisUrl;
1198
1214
  failedJobs;
1199
1215
  timeoutSeconds;
1200
1216
  queueKeys;
@@ -1203,6 +1219,7 @@ class QueueWorker {
1203
1219
  stopping = false;
1204
1220
  client;
1205
1221
  constructor(redisUrl, failedJobs, timeoutSeconds = 5, queueKeys = QUEUE_KEYS) {
1222
+ this.redisUrl = redisUrl;
1206
1223
  this.failedJobs = failedJobs;
1207
1224
  this.timeoutSeconds = timeoutSeconds;
1208
1225
  this.queueKeys = queueKeys;
@@ -1283,13 +1300,24 @@ class QueueWorker {
1283
1300
  this.running = true;
1284
1301
  try {
1285
1302
  while (!this.stopping) {
1286
- await this.processNext();
1303
+ try {
1304
+ await this.processNext();
1305
+ } catch (error) {
1306
+ if (!(error && typeof error === "object" && ("code" in error) && error.code === "ERR_REDIS_CONNECTION_CLOSED"))
1307
+ throw error;
1308
+ this.client.close();
1309
+ await Bun.sleep(250);
1310
+ if (this.stopping)
1311
+ return;
1312
+ this.client = new RedisClient2(this.redisUrl);
1313
+ }
1287
1314
  }
1288
1315
  } finally {
1289
1316
  this.running = false;
1290
1317
  }
1291
1318
  }
1292
1319
  close() {
1320
+ this.stopping = true;
1293
1321
  this.client.close();
1294
1322
  }
1295
1323
  }
@@ -1569,6 +1569,7 @@ class RedisStreamsQueue {
1569
1569
  }
1570
1570
 
1571
1571
  class RedisStreamsWorker {
1572
+ redisUrl;
1572
1573
  failedJobs;
1573
1574
  timeoutSeconds;
1574
1575
  queueKeys;
@@ -1581,6 +1582,7 @@ class RedisStreamsWorker {
1581
1582
  stopping = false;
1582
1583
  active;
1583
1584
  constructor(redisUrl, failedJobs, timeoutSeconds = 5, queueKeys = defaultQueueKeys()) {
1585
+ this.redisUrl = redisUrl;
1584
1586
  this.failedJobs = failedJobs;
1585
1587
  this.timeoutSeconds = timeoutSeconds;
1586
1588
  this.queueKeys = queueKeys;
@@ -1770,8 +1772,21 @@ class RedisStreamsWorker {
1770
1772
  async run() {
1771
1773
  this.running = true;
1772
1774
  try {
1773
- while (!this.stopping)
1774
- await this.processNext();
1775
+ while (!this.stopping) {
1776
+ try {
1777
+ await this.processNext();
1778
+ } catch (error) {
1779
+ if (!(error && typeof error === "object" && ("code" in error) && error.code === "ERR_REDIS_CONNECTION_CLOSED"))
1780
+ throw error;
1781
+ this.client.close();
1782
+ await Bun.sleep(250);
1783
+ if (this.stopping)
1784
+ return;
1785
+ this.client = new RedisClient(this.redisUrl);
1786
+ this.initialized = false;
1787
+ this.cursors.clear();
1788
+ }
1789
+ }
1775
1790
  } finally {
1776
1791
  this.running = false;
1777
1792
  }
@@ -2074,6 +2089,7 @@ class RedisQueue {
2074
2089
  }
2075
2090
 
2076
2091
  class QueueWorker {
2092
+ redisUrl;
2077
2093
  failedJobs;
2078
2094
  timeoutSeconds;
2079
2095
  queueKeys;
@@ -2082,6 +2098,7 @@ class QueueWorker {
2082
2098
  stopping = false;
2083
2099
  client;
2084
2100
  constructor(redisUrl, failedJobs, timeoutSeconds = 5, queueKeys = QUEUE_KEYS) {
2101
+ this.redisUrl = redisUrl;
2085
2102
  this.failedJobs = failedJobs;
2086
2103
  this.timeoutSeconds = timeoutSeconds;
2087
2104
  this.queueKeys = queueKeys;
@@ -2162,13 +2179,24 @@ class QueueWorker {
2162
2179
  this.running = true;
2163
2180
  try {
2164
2181
  while (!this.stopping) {
2165
- await this.processNext();
2182
+ try {
2183
+ await this.processNext();
2184
+ } catch (error) {
2185
+ if (!(error && typeof error === "object" && ("code" in error) && error.code === "ERR_REDIS_CONNECTION_CLOSED"))
2186
+ throw error;
2187
+ this.client.close();
2188
+ await Bun.sleep(250);
2189
+ if (this.stopping)
2190
+ return;
2191
+ this.client = new RedisClient2(this.redisUrl);
2192
+ }
2166
2193
  }
2167
2194
  } finally {
2168
2195
  this.running = false;
2169
2196
  }
2170
2197
  }
2171
2198
  close() {
2199
+ this.stopping = true;
2172
2200
  this.client.close();
2173
2201
  }
2174
2202
  }
@@ -622,6 +622,7 @@ class RedisStreamsQueue {
622
622
  }
623
623
 
624
624
  class RedisStreamsWorker {
625
+ redisUrl;
625
626
  failedJobs;
626
627
  timeoutSeconds;
627
628
  queueKeys;
@@ -634,6 +635,7 @@ class RedisStreamsWorker {
634
635
  stopping = false;
635
636
  active;
636
637
  constructor(redisUrl, failedJobs, timeoutSeconds = 5, queueKeys = defaultQueueKeys()) {
638
+ this.redisUrl = redisUrl;
637
639
  this.failedJobs = failedJobs;
638
640
  this.timeoutSeconds = timeoutSeconds;
639
641
  this.queueKeys = queueKeys;
@@ -823,8 +825,21 @@ class RedisStreamsWorker {
823
825
  async run() {
824
826
  this.running = true;
825
827
  try {
826
- while (!this.stopping)
827
- await this.processNext();
828
+ while (!this.stopping) {
829
+ try {
830
+ await this.processNext();
831
+ } catch (error) {
832
+ if (!(error && typeof error === "object" && ("code" in error) && error.code === "ERR_REDIS_CONNECTION_CLOSED"))
833
+ throw error;
834
+ this.client.close();
835
+ await Bun.sleep(250);
836
+ if (this.stopping)
837
+ return;
838
+ this.client = new RedisClient(this.redisUrl);
839
+ this.initialized = false;
840
+ this.cursors.clear();
841
+ }
842
+ }
828
843
  } finally {
829
844
  this.running = false;
830
845
  }
@@ -1127,6 +1142,7 @@ class RedisQueue {
1127
1142
  }
1128
1143
 
1129
1144
  class QueueWorker {
1145
+ redisUrl;
1130
1146
  failedJobs;
1131
1147
  timeoutSeconds;
1132
1148
  queueKeys;
@@ -1135,6 +1151,7 @@ class QueueWorker {
1135
1151
  stopping = false;
1136
1152
  client;
1137
1153
  constructor(redisUrl, failedJobs, timeoutSeconds = 5, queueKeys = QUEUE_KEYS) {
1154
+ this.redisUrl = redisUrl;
1138
1155
  this.failedJobs = failedJobs;
1139
1156
  this.timeoutSeconds = timeoutSeconds;
1140
1157
  this.queueKeys = queueKeys;
@@ -1215,13 +1232,24 @@ class QueueWorker {
1215
1232
  this.running = true;
1216
1233
  try {
1217
1234
  while (!this.stopping) {
1218
- await this.processNext();
1235
+ try {
1236
+ await this.processNext();
1237
+ } catch (error) {
1238
+ if (!(error && typeof error === "object" && ("code" in error) && error.code === "ERR_REDIS_CONNECTION_CLOSED"))
1239
+ throw error;
1240
+ this.client.close();
1241
+ await Bun.sleep(250);
1242
+ if (this.stopping)
1243
+ return;
1244
+ this.client = new RedisClient2(this.redisUrl);
1245
+ }
1219
1246
  }
1220
1247
  } finally {
1221
1248
  this.running = false;
1222
1249
  }
1223
1250
  }
1224
1251
  close() {
1252
+ this.stopping = true;
1225
1253
  this.client.close();
1226
1254
  }
1227
1255
  }
package/dist/index.js CHANGED
@@ -3893,6 +3893,7 @@ return redis.error_reply('Unknown cache operation')
3893
3893
  `;
3894
3894
 
3895
3895
  class RedisCacheStore {
3896
+ redisUrl;
3896
3897
  ttlMs;
3897
3898
  maxEntries;
3898
3899
  client;
@@ -3901,6 +3902,7 @@ class RedisCacheStore {
3901
3902
  fills = new Set;
3902
3903
  closed = false;
3903
3904
  constructor(redisUrl, ttlMs, maxEntries, options = {}) {
3905
+ this.redisUrl = redisUrl;
3904
3906
  this.ttlMs = ttlMs;
3905
3907
  this.maxEntries = maxEntries;
3906
3908
  if (!Number.isSafeInteger(ttlMs) || ttlMs < 0)
@@ -3925,7 +3927,10 @@ class RedisCacheStore {
3925
3927
  const namespace = namespacedRedisKey("cache:v3:");
3926
3928
  const slot = new Bun.CryptoHasher("sha256").update(namespace).digest("hex");
3927
3929
  this.keys = ["entries", "expiry", "order", "metadata", "metadata-expiry"].map((name) => `${namespace}{${slot}}:${name}`);
3928
- this.client = new RedisClient(redisUrl, {
3930
+ this.client = this.createClient();
3931
+ }
3932
+ createClient() {
3933
+ return new RedisClient(this.redisUrl, {
3929
3934
  connectionTimeout: this.options.commandTimeoutMs,
3930
3935
  maxRetries: 1
3931
3936
  });
@@ -3934,7 +3939,8 @@ class RedisCacheStore {
3934
3939
  this.closed = true;
3935
3940
  for (const fill of this.fills)
3936
3941
  fill.abort(new Error("Redis cache store is closed."));
3937
- this.client.close();
3942
+ this.client?.close();
3943
+ this.client = undefined;
3938
3944
  }
3939
3945
  async get(key) {
3940
3946
  const result = await this.execute("get", key);
@@ -4013,6 +4019,7 @@ class RedisCacheStore {
4013
4019
  Promise.resolve().then(() => loader(controller.signal)),
4014
4020
  aborted
4015
4021
  ]);
4022
+ controller.signal.throwIfAborted();
4016
4023
  done = true;
4017
4024
  clearInterval(renew);
4018
4025
  await this.execute("publish", key, {
@@ -4054,16 +4061,28 @@ class RedisCacheStore {
4054
4061
  const ttlMs = extra.ttlMs ?? this.ttlMs;
4055
4062
  if (typeof ttlMs !== "number" || !Number.isSafeInteger(ttlMs) || ttlMs < 0)
4056
4063
  throw new RangeError("ttlMs must be a non-negative safe integer.");
4064
+ if (!this.client)
4065
+ this.client = this.createClient();
4066
+ const client = this.client;
4067
+ const retire = (reason) => {
4068
+ if (this.client !== client)
4069
+ return;
4070
+ this.client = undefined;
4071
+ for (const fill of this.fills)
4072
+ fill.abort(reason);
4073
+ client.close();
4074
+ };
4057
4075
  let timer;
4058
4076
  const timeout = new Promise((_, reject) => {
4059
4077
  timer = setTimeout(() => {
4060
- reject(new Error("Redis cache command deadline exceeded; recreate the store."));
4061
- this.close();
4078
+ const error = new Error("Redis cache command deadline exceeded.");
4079
+ reject(error);
4080
+ retire(error);
4062
4081
  }, this.options.commandTimeoutMs);
4063
4082
  });
4064
4083
  try {
4065
4084
  const raw = await Promise.race([
4066
- this.client.send("EVAL", [
4085
+ client.send("EVAL", [
4067
4086
  CACHE_SCRIPT,
4068
4087
  String(this.keys.length),
4069
4088
  ...this.keys,
@@ -4081,7 +4100,13 @@ class RedisCacheStore {
4081
4100
  ]),
4082
4101
  timeout
4083
4102
  ]);
4103
+ if (this.closed || this.client !== client)
4104
+ throw new Error("Redis cache connection was retired.");
4084
4105
  return JSON.parse(String(raw));
4106
+ } catch (error) {
4107
+ if (error && typeof error === "object" && "code" in error && typeof error.code === "string" && error.code.startsWith("ERR_REDIS_CONNECTION"))
4108
+ retire(error);
4109
+ throw error;
4085
4110
  } finally {
4086
4111
  clearTimeout(timer);
4087
4112
  }
@@ -14796,6 +14821,7 @@ class RedisStreamsQueue {
14796
14821
  }
14797
14822
 
14798
14823
  class RedisStreamsWorker {
14824
+ redisUrl;
14799
14825
  failedJobs;
14800
14826
  timeoutSeconds;
14801
14827
  queueKeys;
@@ -14808,6 +14834,7 @@ class RedisStreamsWorker {
14808
14834
  stopping = false;
14809
14835
  active;
14810
14836
  constructor(redisUrl, failedJobs, timeoutSeconds = 5, queueKeys = defaultQueueKeys()) {
14837
+ this.redisUrl = redisUrl;
14811
14838
  this.failedJobs = failedJobs;
14812
14839
  this.timeoutSeconds = timeoutSeconds;
14813
14840
  this.queueKeys = queueKeys;
@@ -14997,8 +15024,21 @@ class RedisStreamsWorker {
14997
15024
  async run() {
14998
15025
  this.running = true;
14999
15026
  try {
15000
- while (!this.stopping)
15001
- await this.processNext();
15027
+ while (!this.stopping) {
15028
+ try {
15029
+ await this.processNext();
15030
+ } catch (error) {
15031
+ if (!(error && typeof error === "object" && ("code" in error) && error.code === "ERR_REDIS_CONNECTION_CLOSED"))
15032
+ throw error;
15033
+ this.client.close();
15034
+ await Bun.sleep(250);
15035
+ if (this.stopping)
15036
+ return;
15037
+ this.client = new RedisClient3(this.redisUrl);
15038
+ this.initialized = false;
15039
+ this.cursors.clear();
15040
+ }
15041
+ }
15002
15042
  } finally {
15003
15043
  this.running = false;
15004
15044
  }
@@ -15224,6 +15264,7 @@ class RedisQueue {
15224
15264
  }
15225
15265
 
15226
15266
  class QueueWorker {
15267
+ redisUrl;
15227
15268
  failedJobs;
15228
15269
  timeoutSeconds;
15229
15270
  queueKeys;
@@ -15232,6 +15273,7 @@ class QueueWorker {
15232
15273
  stopping = false;
15233
15274
  client;
15234
15275
  constructor(redisUrl, failedJobs, timeoutSeconds = 5, queueKeys = QUEUE_KEYS) {
15276
+ this.redisUrl = redisUrl;
15235
15277
  this.failedJobs = failedJobs;
15236
15278
  this.timeoutSeconds = timeoutSeconds;
15237
15279
  this.queueKeys = queueKeys;
@@ -15312,13 +15354,24 @@ class QueueWorker {
15312
15354
  this.running = true;
15313
15355
  try {
15314
15356
  while (!this.stopping) {
15315
- await this.processNext();
15357
+ try {
15358
+ await this.processNext();
15359
+ } catch (error) {
15360
+ if (!(error && typeof error === "object" && ("code" in error) && error.code === "ERR_REDIS_CONNECTION_CLOSED"))
15361
+ throw error;
15362
+ this.client.close();
15363
+ await Bun.sleep(250);
15364
+ if (this.stopping)
15365
+ return;
15366
+ this.client = new RedisClient4(this.redisUrl);
15367
+ }
15316
15368
  }
15317
15369
  } finally {
15318
15370
  this.running = false;
15319
15371
  }
15320
15372
  }
15321
15373
  close() {
15374
+ this.stopping = true;
15322
15375
  this.client.close();
15323
15376
  }
15324
15377
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "2.2.23",
3
+ "version": "2.2.25",
4
4
  "description": "Strata Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",