@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 +8 -0
- package/dist/core/cache/redisCacheStore.d.ts +3 -1
- package/dist/core/queue/redisQueue.d.ts +2 -1
- package/dist/core/queue/redisStreams.d.ts +2 -1
- package/dist/entries/cache/createCacheStore.js +30 -5
- package/dist/entries/queue/createAppQueue.js +31 -3
- package/dist/entries/queue/publicQueue.js +31 -3
- package/dist/entries/queue/queueMetrics.js +31 -3
- package/dist/entries/queue/redisQueue.js +31 -3
- package/dist/index.js +61 -8
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
457
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
4061
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|