@mastra/redis-streams 0.1.0 → 0.2.0-alpha.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 +37 -0
- package/dist/index.cjs +167 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +36 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +167 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @mastra/redis-streams
|
|
2
2
|
|
|
3
|
+
## 0.2.0-alpha.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Honor the `localOnly` publish option so in-process subscribers can receive events without round-tripping through the broker. ([#17836](https://github.com/mastra-ai/mastra/pull/17836))
|
|
8
|
+
|
|
9
|
+
This matches the contract already implemented by `UnixSocketPubSub` in `@mastra/core`: when `Mastra` tags an internal workflow event as `localOnly`, the payload is delivered by reference to local subscribers and the broker is skipped entirely. Live runtime values like `MastraModelOutput` instances now keep their prototypes when the evented agent loop runs against a Redis Streams or Google Cloud Pub/Sub broker, fixing `output.consumeStream is not a function` style failures.
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`5c4e9a4`](https://github.com/mastra-ai/mastra/commit/5c4e9a4cfb2216bb3ea7f8988ad3727f3b92bb3a), [`25961e3`](https://github.com/mastra-ai/mastra/commit/25961e3260ff3b1464637af8fcdb36210551c39f), [`7b29f33`](https://github.com/mastra-ai/mastra/commit/7b29f332a357a83e555f29e718e5f2fab9979943), [`24912b1`](https://github.com/mastra-ai/mastra/commit/24912b1f855d29ec36af4ef4bde1f7417e20cdf5), [`7686216`](https://github.com/mastra-ai/mastra/commit/7686216f37e74568feddec17cef3c3d24e10e60a), [`975c59a`](https://github.com/mastra-ai/mastra/commit/975c59ae363ee275fc55062392e1ffd2cbccbd53), [`d95f394`](https://github.com/mastra-ai/mastra/commit/d95f394fd24c8411886930d727679c4d5252aa26), [`f3f0c9d`](https://github.com/mastra-ai/mastra/commit/f3f0c9d7c878db5a13177871ce3523a14f14b311)]:
|
|
12
|
+
- @mastra/core@1.46.0-alpha.4
|
|
13
|
+
|
|
14
|
+
## 0.2.0-alpha.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- Collapsed the result of `agent.sendSignal` / `sendMessage` / `queueMessage` / `sendStateSignal` / `sendNotificationSignal` into a single `accepted` promise that resolves at decision-time to a discriminated union: `{ action: 'wake'; runId; output }` when the signal started a run in this process, `{ action: 'deliver'; runId }` when it was forwarded onto an existing run, or `{ action: 'persist' }` / `{ action: 'discard' }` when nothing ran. `runId` is present only on `wake`/`deliver`; for `persist`/`discard` correlate via `result.signal.id`. `accepted` resolves for routing decisions and rejects only when the signal couldn't be routed at all (e.g. misconfigured agent). This replaces the old `accepted: true` boolean and the best-effort top-level `runId`. `result.persisted` stays top-level. `sendNotificationSignal`'s `accepted` is optional (a notification may be dropped by policy with no signal) — read `result.decision` for the policy verdict. ([#17723](https://github.com/mastra-ai/mastra/pull/17723))
|
|
19
|
+
|
|
20
|
+
On serverless platforms, when this process/Lambda is the one that woke the run (`action === 'wake'`), it can use the platform's `waitUntil` to keep itself alive until the run it started completes — otherwise the runtime may be frozen or torn down mid-run:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
const result = agent.sendSignal(signal, { resourceId, threadId });
|
|
24
|
+
ctx.waitUntil(
|
|
25
|
+
result.accepted.then(async accepted => {
|
|
26
|
+
if (accepted.action === 'wake') await accepted.output.consumeStream();
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Added a `LeaseProvider` capability (`acquireLease` / `releaseLease` / `renewLease` / `getLeaseOwner` / `transferLease`) — distributed leasing kept separate from event delivery (`PubSub`) — so processes racing to wake the same thread coordinate a single owner. The winner runs the stream; losers forward their signal to it. `EventEmitterPubSub` leases in-memory; `RedisStreamsPubSub` uses `SET NX PX` with owner-verified Lua scripts for release, renew, and transfer; the default `NoopLeaseProvider` always wins, preserving single-process behavior.
|
|
32
|
+
|
|
33
|
+
Fixed a cross-process race where, after a run finished, the thread's lease briefly went free before its queued follow-up work started — letting another process win the lease and start a competing run for the same thread. The owner now keeps the lease until all queued work is drained. The lease TTL and renewal interval are overridable for tests via `MASTRA_AGENT_THREAD_LEASE_TTL_MS` and `MASTRA_AGENT_THREAD_LEASE_RENEW_INTERVAL_MS` (production defaults unchanged).
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- Updated dependencies [[`5bd72d2`](https://github.com/mastra-ai/mastra/commit/5bd72d255f45b5ea8ab342643bd463814a980a24), [`1cc9ee1`](https://github.com/mastra-ai/mastra/commit/1cc9ee1ba51db53020a735626d33017a60b4b5b3), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`30ebaf0`](https://github.com/mastra-ai/mastra/commit/30ebaf07bed5f4d30f2f257836c15d1bf7e40aae), [`5704634`](https://github.com/mastra-ai/mastra/commit/5704634b22133167dea337a942a34f57aaa3fa14), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`c0eda2b`](https://github.com/mastra-ai/mastra/commit/c0eda2bcd91a228427314b12c91d8b147f3a739f), [`c0eda2b`](https://github.com/mastra-ai/mastra/commit/c0eda2bcd91a228427314b12c91d8b147f3a739f), [`b13925b`](https://github.com/mastra-ai/mastra/commit/b13925bfa91aa8700f56fa54a9ce707ee7e4ba62), [`bf94ec6`](https://github.com/mastra-ai/mastra/commit/bf94ec68192d9f16e46ef7e5ac36370aeeddf35d), [`a29f371`](https://github.com/mastra-ai/mastra/commit/a29f371aef629ac8562661524a497127e93b5131), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`073f910`](https://github.com/mastra-ai/mastra/commit/073f910481e7d94b95ba3830f96531774ae95d33), [`ebbe1d3`](https://github.com/mastra-ai/mastra/commit/ebbe1d31a965a3adb0e728758f326b8122b4b55f), [`1f97ce5`](https://github.com/mastra-ai/mastra/commit/1f97ce5695463bebb4eaacf501da6fb403e20885), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`64f58c0`](https://github.com/mastra-ai/mastra/commit/64f58c04e78b40137497d47f781e897e416f22a5), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`ebbe1d3`](https://github.com/mastra-ai/mastra/commit/ebbe1d31a965a3adb0e728758f326b8122b4b55f), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`8e25a78`](https://github.com/mastra-ai/mastra/commit/8e25a78e0597575f0b0729bae8c5e190c84869b5), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`a5b22d3`](https://github.com/mastra-ai/mastra/commit/a5b22d314d62a68d801886a8d3d0eb6c089473db), [`417baae`](https://github.com/mastra-ai/mastra/commit/417baae40b995db5819c845036947f0c27dc1c00), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858)]:
|
|
38
|
+
- @mastra/core@1.46.0-alpha.0
|
|
39
|
+
|
|
3
40
|
## 0.1.0
|
|
4
41
|
|
|
5
42
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,12 @@ var RedisStreamsPubSub = class extends events.PubSub {
|
|
|
26
26
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
27
27
|
#cbIds = /* @__PURE__ */ new WeakMap();
|
|
28
28
|
#pendingPublishes = /* @__PURE__ */ new Set();
|
|
29
|
+
// `localOnly` publishes bypass Redis entirely so values carrying live
|
|
30
|
+
// methods (e.g. `MastraModelOutput` returned from an evented agent run via
|
|
31
|
+
// `workflows-finish`) survive intact. Mirrors the same contract honored by
|
|
32
|
+
// `UnixSocketPubSub` so consumers like `Mastra.__registerInternalWorkflow`
|
|
33
|
+
// get consistent semantics across transports.
|
|
34
|
+
#localCallbacks = /* @__PURE__ */ new Map();
|
|
29
35
|
#closed = false;
|
|
30
36
|
constructor(options = {}) {
|
|
31
37
|
super();
|
|
@@ -66,8 +72,18 @@ var RedisStreamsPubSub = class extends events.PubSub {
|
|
|
66
72
|
#streamKey(topic) {
|
|
67
73
|
return `${this.#keyPrefix}:${topic}`;
|
|
68
74
|
}
|
|
69
|
-
async publish(topic, event) {
|
|
75
|
+
async publish(topic, event, options) {
|
|
70
76
|
if (this.#closed) throw new Error("RedisStreamsPubSub: cannot publish on closed client");
|
|
77
|
+
if (options?.localOnly) {
|
|
78
|
+
const localEvent = {
|
|
79
|
+
...event,
|
|
80
|
+
id: crypto.randomUUID(),
|
|
81
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
82
|
+
deliveryAttempt: event.deliveryAttempt ?? 1
|
|
83
|
+
};
|
|
84
|
+
this.#deliverLocal(topic, localEvent);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
71
87
|
await this.#ensureWriterConnected();
|
|
72
88
|
const id = crypto.randomUUID();
|
|
73
89
|
const createdAt = /* @__PURE__ */ new Date();
|
|
@@ -102,6 +118,12 @@ var RedisStreamsPubSub = class extends events.PubSub {
|
|
|
102
118
|
if (this.#closed) throw new Error("RedisStreamsPubSub: cannot subscribe on closed client");
|
|
103
119
|
const key = this.#subKey(topic, cb);
|
|
104
120
|
if (this.#subscriptions.has(key)) return;
|
|
121
|
+
let localBucket = this.#localCallbacks.get(topic);
|
|
122
|
+
if (!localBucket) {
|
|
123
|
+
localBucket = /* @__PURE__ */ new Set();
|
|
124
|
+
this.#localCallbacks.set(topic, localBucket);
|
|
125
|
+
}
|
|
126
|
+
localBucket.add(cb);
|
|
105
127
|
await this.#ensureWriterConnected();
|
|
106
128
|
const isGrouped = !!options?.group;
|
|
107
129
|
const group = options?.group ?? `__fanout-${crypto.randomUUID()}`;
|
|
@@ -171,6 +193,11 @@ var RedisStreamsPubSub = class extends events.PubSub {
|
|
|
171
193
|
sub.reclaimTimer = setTimeout(tick, this.#reclaimIntervalMs);
|
|
172
194
|
}
|
|
173
195
|
async unsubscribe(topic, cb) {
|
|
196
|
+
const localBucket = this.#localCallbacks.get(topic);
|
|
197
|
+
if (localBucket) {
|
|
198
|
+
localBucket.delete(cb);
|
|
199
|
+
if (localBucket.size === 0) this.#localCallbacks.delete(topic);
|
|
200
|
+
}
|
|
174
201
|
const key = this.#subKey(topic, cb);
|
|
175
202
|
const sub = this.#subscriptions.get(key);
|
|
176
203
|
if (!sub) return;
|
|
@@ -215,6 +242,111 @@ var RedisStreamsPubSub = class extends events.PubSub {
|
|
|
215
242
|
await Promise.allSettled([...this.#pendingPublishes]);
|
|
216
243
|
}
|
|
217
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Lease key used in Redis. Distinct prefix from streams so leases and
|
|
247
|
+
* streams can't collide on key namespace.
|
|
248
|
+
*/
|
|
249
|
+
#leaseKey(key) {
|
|
250
|
+
return `${this.#keyPrefix}:lease:${key}`;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Atomic claim via SET NX PX. Idempotent for the same owner: if the
|
|
254
|
+
* current value is already this owner, we refresh the TTL instead of
|
|
255
|
+
* failing. Cross-process callers race here; Redis serializes them.
|
|
256
|
+
*/
|
|
257
|
+
async acquireLease(key, owner, ttlMs) {
|
|
258
|
+
if (this.#closed) return { acquired: false };
|
|
259
|
+
await this.#ensureWriterConnected();
|
|
260
|
+
const redisKey = this.#leaseKey(key);
|
|
261
|
+
const result = await this.#writeClient.set(redisKey, owner, { NX: true, PX: ttlMs });
|
|
262
|
+
if (result === "OK") return { acquired: true, owner };
|
|
263
|
+
const script = `
|
|
264
|
+
local current = redis.call("GET", KEYS[1])
|
|
265
|
+
if current == ARGV[1] then
|
|
266
|
+
redis.call("PEXPIRE", KEYS[1], ARGV[2])
|
|
267
|
+
return 1
|
|
268
|
+
end
|
|
269
|
+
return 0
|
|
270
|
+
`;
|
|
271
|
+
const refreshed = await this.#writeClient.eval(script, {
|
|
272
|
+
keys: [redisKey],
|
|
273
|
+
arguments: [owner, String(ttlMs)]
|
|
274
|
+
});
|
|
275
|
+
if (refreshed === 1) return { acquired: true, owner };
|
|
276
|
+
return { acquired: false, owner: await this.#writeClient.get(redisKey) ?? void 0 };
|
|
277
|
+
}
|
|
278
|
+
async getLeaseOwner(key) {
|
|
279
|
+
if (this.#closed) return void 0;
|
|
280
|
+
await this.#ensureWriterConnected();
|
|
281
|
+
const current = await this.#writeClient.get(this.#leaseKey(key));
|
|
282
|
+
return current ?? void 0;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Release only if we still own it. Implemented as GET+DEL with a Lua
|
|
286
|
+
* script so the check-and-delete is atomic against concurrent renewals
|
|
287
|
+
* from other processes.
|
|
288
|
+
*/
|
|
289
|
+
async releaseLease(key, owner) {
|
|
290
|
+
if (this.#closed) return;
|
|
291
|
+
await this.#ensureWriterConnected();
|
|
292
|
+
const script = `
|
|
293
|
+
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
|
294
|
+
return redis.call("DEL", KEYS[1])
|
|
295
|
+
else
|
|
296
|
+
return 0
|
|
297
|
+
end
|
|
298
|
+
`;
|
|
299
|
+
await this.#writeClient.eval(script, {
|
|
300
|
+
keys: [this.#leaseKey(key)],
|
|
301
|
+
arguments: [owner]
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Extend the TTL only if we still own the lease. Returns false if the
|
|
306
|
+
* lease was lost (expired or another owner took it).
|
|
307
|
+
*/
|
|
308
|
+
async renewLease(key, owner, ttlMs) {
|
|
309
|
+
if (this.#closed) return false;
|
|
310
|
+
await this.#ensureWriterConnected();
|
|
311
|
+
const script = `
|
|
312
|
+
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
|
313
|
+
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
|
|
314
|
+
else
|
|
315
|
+
return 0
|
|
316
|
+
end
|
|
317
|
+
`;
|
|
318
|
+
const result = await this.#writeClient.eval(script, {
|
|
319
|
+
keys: [this.#leaseKey(key)],
|
|
320
|
+
arguments: [owner, String(ttlMs)]
|
|
321
|
+
});
|
|
322
|
+
return result === 1;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Atomically hand the lease from `fromOwner` to `toOwner`, refreshing the
|
|
326
|
+
* TTL to the full `ttlMs`, without the key ever going empty.
|
|
327
|
+
*
|
|
328
|
+
* Implemented as a single Lua script (GET == fromOwner -> SET toOwner PX)
|
|
329
|
+
* so a racing process cannot win the key between a release and a re-acquire.
|
|
330
|
+
* Returns false if `fromOwner` no longer holds the lease (expired or taken),
|
|
331
|
+
* in which case the caller should fall back to a fresh `acquireLease`.
|
|
332
|
+
*/
|
|
333
|
+
async transferLease(key, fromOwner, toOwner, ttlMs) {
|
|
334
|
+
if (this.#closed) return false;
|
|
335
|
+
await this.#ensureWriterConnected();
|
|
336
|
+
const script = `
|
|
337
|
+
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
|
338
|
+
redis.call("SET", KEYS[1], ARGV[2], "PX", ARGV[3])
|
|
339
|
+
return 1
|
|
340
|
+
else
|
|
341
|
+
return 0
|
|
342
|
+
end
|
|
343
|
+
`;
|
|
344
|
+
const result = await this.#writeClient.eval(script, {
|
|
345
|
+
keys: [this.#leaseKey(key)],
|
|
346
|
+
arguments: [fromOwner, toOwner, String(ttlMs)]
|
|
347
|
+
});
|
|
348
|
+
return result === 1;
|
|
349
|
+
}
|
|
218
350
|
/**
|
|
219
351
|
* Disconnect all clients and stop all subscription loops.
|
|
220
352
|
*/
|
|
@@ -223,6 +355,7 @@ var RedisStreamsPubSub = class extends events.PubSub {
|
|
|
223
355
|
this.#closed = true;
|
|
224
356
|
const subs = [...this.#subscriptions.values()];
|
|
225
357
|
await Promise.all(subs.map((sub) => this.unsubscribe(sub.topic, sub.cb)));
|
|
358
|
+
this.#localCallbacks.clear();
|
|
226
359
|
if (this.#writeClient.isOpen) {
|
|
227
360
|
try {
|
|
228
361
|
await this.#writeClient.quit();
|
|
@@ -353,6 +486,39 @@ var RedisStreamsPubSub = class extends events.PubSub {
|
|
|
353
486
|
await nack();
|
|
354
487
|
}
|
|
355
488
|
}
|
|
489
|
+
#deliverLocal(topic, event) {
|
|
490
|
+
const callbacks = this.#localCallbacks.get(topic);
|
|
491
|
+
if (!callbacks || callbacks.size === 0) return;
|
|
492
|
+
for (const cb of callbacks) {
|
|
493
|
+
this.#invokeLocalCallback(topic, event, cb);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
#invokeLocalCallback(topic, event, cb) {
|
|
497
|
+
const ack = async () => {
|
|
498
|
+
};
|
|
499
|
+
const nack = async () => {
|
|
500
|
+
};
|
|
501
|
+
try {
|
|
502
|
+
const result = cb(
|
|
503
|
+
event,
|
|
504
|
+
ack,
|
|
505
|
+
nack
|
|
506
|
+
);
|
|
507
|
+
if (result && typeof result.catch === "function") {
|
|
508
|
+
result.catch((err) => {
|
|
509
|
+
this.#logger?.debug?.("redis-streams: local subscriber rejected", {
|
|
510
|
+
topic,
|
|
511
|
+
err: err instanceof Error ? err.message : err
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
} catch (err) {
|
|
516
|
+
this.#logger?.debug?.("redis-streams: local subscriber threw", {
|
|
517
|
+
topic,
|
|
518
|
+
err: err instanceof Error ? err.message : err
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
356
522
|
};
|
|
357
523
|
|
|
358
524
|
exports.RedisStreamsPubSub = RedisStreamsPubSub;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["PubSub","createClient","randomUUID"],"mappings":"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","file":"index.cjs","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { PubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';\nimport { createClient } from 'redis';\nimport type { RedisClientOptions, RedisClientType } from 'redis';\n\n/**\n * Mastra PubSub backed by Redis Streams.\n *\n * - Each topic maps to a Redis stream key `<prefix>:<topic>`.\n * - Subscriptions with `options.group` use a real Redis consumer group, so\n * competing subscribers in the same group share the work (round-robin).\n * - Subscriptions without a group create a private per-subscriber consumer\n * group, so they get fan-out semantics (every subscriber sees every event).\n * - Nack triggers redelivery by re-publishing the event with an incremented\n * `deliveryAttempt` field, then XACK-ing the original. This trades strict\n * FIFO ordering on retry for a simple, reliable redelivery path.\n */\nexport interface RedisStreamsPubSubConfig {\n url?: string;\n keyPrefix?: string;\n blockMs?: number;\n redisOptions?: RedisClientOptions;\n /**\n * Approximate maximum number of entries kept per stream. On every publish we\n * issue MAXLEN ~ N which lets Redis trim opportunistically. Defaults to\n * 10_000 — set to 0 to disable trimming.\n */\n maxStreamLength?: number;\n /**\n * How often (in ms) each subscription runs XAUTOCLAIM to recover messages\n * that an earlier consumer in the group read but never acked. Defaults to\n * 30_000 ms. Set to 0 to disable.\n */\n reclaimIntervalMs?: number;\n /**\n * Minimum idle time (in ms) before a pending message is eligible for\n * reclaim. Should be much larger than typical in-flight processing time to\n * avoid double-delivery. Defaults to 60_000 ms.\n */\n reclaimIdleMs?: number;\n /**\n * Maximum number of times a single event will be redelivered via nack\n * before it is dropped (acked without republish). Defaults to 5. Set to\n * `Infinity` to disable the cap (events redeliver forever on every nack).\n *\n * `0` is treated as `Infinity` with a one-time warn for back-compat;\n * prefer `Infinity` to disable the cap explicitly.\n */\n maxDeliveryAttempts?: number;\n /**\n * Optional logger for diagnostics. When omitted, suppressed errors\n * (BUSYGROUP, malformed payloads, connection-close races) are swallowed\n * silently. When provided, those paths emit `debug`/`warn` entries so\n * operators can see what's happening without noise on the happy path.\n */\n logger?: { debug?: (...args: unknown[]) => void; warn?: (...args: unknown[]) => void };\n}\n\nexport class RedisStreamsPubSub extends PubSub {\n // Redis Streams is a pull transport: consumers issue XREADGROUP to read\n // events. Mastra reads this to know an OrchestrationWorker is required.\n override get supportedModes(): ReadonlyArray<PubSubDeliveryMode> {\n return ['pull'];\n }\n\n #writeClient: RedisClientType;\n #connectOptions: RedisClientOptions;\n #keyPrefix: string;\n #blockMs: number;\n #maxStreamLength: number;\n #reclaimIntervalMs: number;\n #reclaimIdleMs: number;\n #maxDeliveryAttempts: number;\n #logger?: RedisStreamsPubSubConfig['logger'];\n // Keyed by `${topic}::${cbId}` so the same callback can be subscribed to\n // multiple topics independently. Without the topic in the key,\n // unsubscribe(otherTopic, cb) would tear down the wrong subscription.\n #subscriptions: Map<string, Subscription> = new Map();\n #cbIds: WeakMap<EventCallback, string> = new WeakMap();\n #pendingPublishes: Set<Promise<unknown>> = new Set();\n #closed = false;\n\n constructor(options: RedisStreamsPubSubConfig = {}) {\n super();\n const url = options.url ?? options.redisOptions?.url ?? 'redis://localhost:6379';\n this.#connectOptions = { ...options.redisOptions, url };\n this.#writeClient = createClient(this.#connectOptions) as RedisClientType;\n this.#keyPrefix = options.keyPrefix ?? 'mastra:topic';\n this.#blockMs = options.blockMs ?? 1000;\n this.#maxStreamLength = options.maxStreamLength ?? 10_000;\n this.#reclaimIntervalMs = options.reclaimIntervalMs ?? 30_000;\n this.#reclaimIdleMs = options.reclaimIdleMs ?? 60_000;\n const cap = options.maxDeliveryAttempts ?? 5;\n if (cap === 0) {\n options.logger?.warn?.(\n 'redis-streams: maxDeliveryAttempts=0 is treated as Infinity for back-compat; pass Infinity to disable the cap explicitly.',\n );\n this.#maxDeliveryAttempts = Infinity;\n } else if (cap < 0 || Number.isNaN(cap)) {\n throw new Error(`redis-streams: maxDeliveryAttempts must be >= 1 or Infinity, got ${cap}`);\n } else {\n this.#maxDeliveryAttempts = cap;\n }\n this.#logger = options.logger;\n }\n\n #subKey(topic: string, cb: EventCallback): string {\n let cbId = this.#cbIds.get(cb);\n if (!cbId) {\n cbId = randomUUID();\n this.#cbIds.set(cb, cbId);\n }\n return `${topic}::${cbId}`;\n }\n\n /** Lazily connect the shared writer client. Idempotent. */\n async #ensureWriterConnected(): Promise<void> {\n if (this.#writeClient.isOpen) return;\n await this.#writeClient.connect();\n }\n\n #streamKey(topic: string): string {\n return `${this.#keyPrefix}:${topic}`;\n }\n\n async publish(topic: string, event: Omit<Event, 'id' | 'createdAt'>): Promise<void> {\n if (this.#closed) throw new Error('RedisStreamsPubSub: cannot publish on closed client');\n await this.#ensureWriterConnected();\n\n const id = randomUUID();\n const createdAt = new Date();\n const payload: Event = {\n ...event,\n id,\n createdAt,\n deliveryAttempt: event.deliveryAttempt ?? 1,\n };\n const xaddOptions: { TRIM?: { strategy: 'MAXLEN'; strategyModifier: '~'; threshold: number } } = {};\n if (this.#maxStreamLength > 0) {\n xaddOptions.TRIM = {\n strategy: 'MAXLEN',\n strategyModifier: '~',\n threshold: this.#maxStreamLength,\n };\n }\n const promise = this.#writeClient.xAdd(\n this.#streamKey(topic),\n '*',\n { event: JSON.stringify(payload) },\n xaddOptions,\n );\n this.#pendingPublishes.add(promise);\n try {\n await promise;\n } finally {\n this.#pendingPublishes.delete(promise);\n }\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (this.#closed) throw new Error('RedisStreamsPubSub: cannot subscribe on closed client');\n const key = this.#subKey(topic, cb);\n if (this.#subscriptions.has(key)) return; // idempotent: same (topic, cb) already subscribed\n\n await this.#ensureWriterConnected();\n\n const isGrouped = !!options?.group;\n const group = options?.group ?? `__fanout-${randomUUID()}`;\n const consumer = `${group}-${randomUUID()}`;\n const streamKey = this.#streamKey(topic);\n\n // Create the consumer group if it doesn't exist. MKSTREAM creates the\n // stream if needed. BUSYGROUP means another subscriber raced us — fine.\n //\n // We anchor brand-new groups at '0' (stream start) instead of '$' so that\n // a worker which subscribes after a publish still sees the backlog. This\n // is the \"late join\" case: a server may publish workflow.start before any\n // orchestrator process exists. Without this, that work is silently lost.\n // Existing groups (BUSYGROUP path) keep their own checkpoint, so this\n // doesn't change semantics for already-running clusters. Stream growth is\n // bounded by the MAXLEN ~ trim applied on every publish.\n try {\n await this.#writeClient.xGroupCreate(streamKey, group, '0', { MKSTREAM: true });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes('BUSYGROUP')) throw err;\n this.#logger?.debug?.('redis-streams: consumer group already exists', { topic, group });\n }\n\n // Each subscription gets a dedicated reader connection because XREADGROUP\n // with BLOCK > 0 holds the connection until a message arrives.\n const readClient = createClient(this.#connectOptions) as RedisClientType;\n await readClient.connect();\n\n const sub: Subscription = {\n cb,\n topic,\n streamKey,\n group,\n consumer,\n isGrouped,\n readClient,\n stopped: false,\n loop: undefined,\n reclaimTimer: undefined,\n };\n this.#subscriptions.set(key, sub);\n sub.loop = this.#runReadLoop(sub);\n this.#startReclaimLoop(sub);\n }\n\n /**\n * Periodically run XAUTOCLAIM against this subscription's group so that\n * messages a crashed/stuck consumer read but never acked get redelivered to\n * a live sibling. Runs only for grouped subscriptions — fan-out groups are\n * private to one consumer, so there's no sibling to claim from.\n */\n #startReclaimLoop(sub: Subscription): void {\n if (this.#reclaimIntervalMs <= 0) return;\n if (!sub.isGrouped) return;\n\n const tick = async () => {\n if (sub.stopped || this.#closed) return;\n try {\n const reply = await this.#writeClient.xAutoClaim(\n sub.streamKey,\n sub.group,\n sub.consumer,\n this.#reclaimIdleMs,\n '0-0',\n { COUNT: 100 },\n );\n const messages = (reply?.messages ?? []) as Array<{ id: string; message: Record<string, string> } | null>;\n for (const entry of messages) {\n if (sub.stopped || this.#closed) return;\n if (!entry) continue;\n await this.#deliverMessage(sub, entry.id, entry.message);\n }\n } catch (err) {\n this.#logger?.debug?.('redis-streams: XAUTOCLAIM failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n }\n if (sub.stopped || this.#closed) return;\n sub.reclaimTimer = setTimeout(tick, this.#reclaimIntervalMs);\n };\n\n sub.reclaimTimer = setTimeout(tick, this.#reclaimIntervalMs);\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n const key = this.#subKey(topic, cb);\n const sub = this.#subscriptions.get(key);\n if (!sub) return;\n this.#subscriptions.delete(key);\n sub.stopped = true;\n if (sub.reclaimTimer) {\n clearTimeout(sub.reclaimTimer);\n sub.reclaimTimer = undefined;\n }\n\n // Cancel the in-flight blocking XREADGROUP by closing the reader.\n try {\n await sub.readClient.quit();\n } catch (err) {\n this.#logger?.debug?.('redis-streams: reader quit failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n\n if (sub.loop) {\n try {\n await sub.loop;\n } catch (err) {\n // loop exits naturally when readClient closes; surface only at debug.\n this.#logger?.debug?.('redis-streams: read loop exited with error', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n\n // For fan-out, drop the private group entirely so the stream can be reclaimed.\n if (!sub.isGrouped) {\n try {\n await this.#writeClient.xGroupDestroy(sub.streamKey, sub.group);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: xGroupDestroy failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n }\n\n async flush(): Promise<void> {\n // Wait for any in-flight publishes to settle.\n if (this.#pendingPublishes.size > 0) {\n await Promise.allSettled([...this.#pendingPublishes]);\n }\n }\n\n /**\n * Disconnect all clients and stop all subscription loops.\n */\n async close(): Promise<void> {\n if (this.#closed) return;\n this.#closed = true;\n\n // Walk the actual subscriptions and pass the original topic through so\n // unsubscribe's key lookup works.\n const subs = [...this.#subscriptions.values()];\n await Promise.all(subs.map(sub => this.unsubscribe(sub.topic, sub.cb)));\n\n if (this.#writeClient.isOpen) {\n try {\n await this.#writeClient.quit();\n } catch (err) {\n this.#logger?.debug?.('redis-streams: writer quit failed', {\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n }\n\n async #runReadLoop(sub: Subscription): Promise<void> {\n while (!sub.stopped) {\n let result;\n try {\n result = await sub.readClient.xReadGroup(sub.group, sub.consumer, [{ key: sub.streamKey, id: '>' }], {\n COUNT: 10,\n BLOCK: this.#blockMs,\n });\n } catch (err) {\n if (sub.stopped) return;\n this.#logger?.debug?.('redis-streams: xReadGroup failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n // Connection error or similar — pause briefly then retry.\n await new Promise(r => setTimeout(r, 100));\n continue;\n }\n\n if (!result || result.length === 0) continue;\n\n for (const stream of result) {\n for (const entry of stream.messages) {\n if (sub.stopped) return;\n await this.#deliverMessage(sub, entry.id, entry.message);\n }\n }\n }\n }\n\n async #deliverMessage(sub: Subscription, streamId: string, fields: Record<string, string>): Promise<void> {\n let event: Event;\n try {\n event = JSON.parse(fields.event ?? '{}') as Event;\n // createdAt is serialized as a string; rehydrate.\n if (typeof event.createdAt === 'string') {\n event.createdAt = new Date(event.createdAt);\n }\n } catch (err) {\n this.#logger?.debug?.('redis-streams: malformed payload, dropping', {\n topic: sub.topic,\n streamId,\n err: err instanceof Error ? err.message : err,\n });\n try {\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (ackErr) {\n this.#logger?.debug?.('redis-streams: xAck after malformed payload failed', {\n topic: sub.topic,\n err: ackErr instanceof Error ? ackErr.message : ackErr,\n });\n }\n return;\n }\n\n let settled = false;\n const ack = async () => {\n if (settled) return;\n settled = true;\n try {\n // Only ack against this consumer group. Do NOT xDel: the stream may\n // be consumed by other groups, and xDel removes the entry for all of\n // them. Stream growth is bounded elsewhere via MAXLEN-style trimming.\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: ack cleanup failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n };\n const nack = async () => {\n if (settled) return;\n settled = true;\n const attempt = event.deliveryAttempt ?? 1;\n // Cap redelivery to avoid an infinite poison-pill loop. When the cap\n // is hit we drop the event (xAck without republish) and warn so an\n // operator can find it in logs.\n if (attempt >= this.#maxDeliveryAttempts) {\n this.#logger?.warn?.('redis-streams: dropping event after max delivery attempts', {\n topic: sub.topic,\n eventType: event.type,\n eventId: event.id,\n attempt,\n max: this.#maxDeliveryAttempts,\n });\n try {\n // Group-scoped ack only — see ack() above for why we never xDel.\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: ack on dropped poison message failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n return;\n }\n // Republish with incremented deliveryAttempt FIRST, then ack the\n // original entry. If the republish fails we deliberately leave the\n // original message pending so XAUTOCLAIM (or another consumer) can\n // pick it up on a future tick — acking first would silently drop it.\n const next: Event = {\n ...event,\n deliveryAttempt: attempt + 1,\n };\n try {\n await this.#writeClient.xAdd(sub.streamKey, '*', { event: JSON.stringify(next) });\n } catch (err) {\n this.#logger?.warn?.('redis-streams: nack republish failed; leaving original pending for reclaim', {\n topic: sub.topic,\n eventId: event.id,\n err: err instanceof Error ? err.message : err,\n });\n // Allow this entry to be redelivered: reset settled so the next\n // delivery attempt (via XAUTOCLAIM) can ack/nack it again.\n settled = false;\n return;\n }\n try {\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: xAck after nack failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n };\n\n try {\n // EventCallback is typed `=> void` but handlers commonly return a\n // promise (TS allows Promise<void> to satisfy void). If we get one\n // back, attach a catch handler so async rejections route to nack\n // instead of silently dropping the message. We do NOT await here —\n // serializing messages on a subscription would deadlock orchestration\n // callbacks that await their own future events.\n const result: unknown = sub.cb(event, ack, nack);\n if (result && typeof (result as { then?: unknown; catch?: unknown }).catch === 'function') {\n (result as Promise<unknown>).catch(async () => {\n await nack();\n });\n }\n } catch {\n // Caller threw synchronously — treat as nack.\n await nack();\n }\n }\n}\n\ninterface Subscription {\n cb: EventCallback;\n topic: string;\n streamKey: string;\n group: string;\n consumer: string;\n isGrouped: boolean;\n readClient: RedisClientType;\n stopped: boolean;\n loop: Promise<void> | undefined;\n reclaimTimer: ReturnType<typeof setTimeout> | undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["PubSub","createClient","randomUUID"],"mappings":"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","file":"index.cjs","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { PubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, LeaseProvider, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';\nimport { createClient } from 'redis';\nimport type { RedisClientOptions, RedisClientType } from 'redis';\n\n/**\n * Mastra PubSub backed by Redis Streams.\n *\n * - Each topic maps to a Redis stream key `<prefix>:<topic>`.\n * - Subscriptions with `options.group` use a real Redis consumer group, so\n * competing subscribers in the same group share the work (round-robin).\n * - Subscriptions without a group create a private per-subscriber consumer\n * group, so they get fan-out semantics (every subscriber sees every event).\n * - Nack triggers redelivery by re-publishing the event with an incremented\n * `deliveryAttempt` field, then XACK-ing the original. This trades strict\n * FIFO ordering on retry for a simple, reliable redelivery path.\n */\nexport interface RedisStreamsPubSubConfig {\n url?: string;\n keyPrefix?: string;\n blockMs?: number;\n redisOptions?: RedisClientOptions;\n /**\n * Approximate maximum number of entries kept per stream. On every publish we\n * issue MAXLEN ~ N which lets Redis trim opportunistically. Defaults to\n * 10_000 — set to 0 to disable trimming.\n */\n maxStreamLength?: number;\n /**\n * How often (in ms) each subscription runs XAUTOCLAIM to recover messages\n * that an earlier consumer in the group read but never acked. Defaults to\n * 30_000 ms. Set to 0 to disable.\n */\n reclaimIntervalMs?: number;\n /**\n * Minimum idle time (in ms) before a pending message is eligible for\n * reclaim. Should be much larger than typical in-flight processing time to\n * avoid double-delivery. Defaults to 60_000 ms.\n */\n reclaimIdleMs?: number;\n /**\n * Maximum number of times a single event will be redelivered via nack\n * before it is dropped (acked without republish). Defaults to 5. Set to\n * `Infinity` to disable the cap (events redeliver forever on every nack).\n *\n * `0` is treated as `Infinity` with a one-time warn for back-compat;\n * prefer `Infinity` to disable the cap explicitly.\n */\n maxDeliveryAttempts?: number;\n /**\n * Optional logger for diagnostics. When omitted, suppressed errors\n * (BUSYGROUP, malformed payloads, connection-close races) are swallowed\n * silently. When provided, those paths emit `debug`/`warn` entries so\n * operators can see what's happening without noise on the happy path.\n */\n logger?: { debug?: (...args: unknown[]) => void; warn?: (...args: unknown[]) => void };\n}\n\nexport class RedisStreamsPubSub extends PubSub implements LeaseProvider {\n // Redis Streams is a pull transport: consumers issue XREADGROUP to read\n // events. Mastra reads this to know an OrchestrationWorker is required.\n override get supportedModes(): ReadonlyArray<PubSubDeliveryMode> {\n return ['pull'];\n }\n\n #writeClient: RedisClientType;\n #connectOptions: RedisClientOptions;\n #keyPrefix: string;\n #blockMs: number;\n #maxStreamLength: number;\n #reclaimIntervalMs: number;\n #reclaimIdleMs: number;\n #maxDeliveryAttempts: number;\n #logger?: RedisStreamsPubSubConfig['logger'];\n // Keyed by `${topic}::${cbId}` so the same callback can be subscribed to\n // multiple topics independently. Without the topic in the key,\n // unsubscribe(otherTopic, cb) would tear down the wrong subscription.\n #subscriptions: Map<string, Subscription> = new Map();\n #cbIds: WeakMap<EventCallback, string> = new WeakMap();\n #pendingPublishes: Set<Promise<unknown>> = new Set();\n // `localOnly` publishes bypass Redis entirely so values carrying live\n // methods (e.g. `MastraModelOutput` returned from an evented agent run via\n // `workflows-finish`) survive intact. Mirrors the same contract honored by\n // `UnixSocketPubSub` so consumers like `Mastra.__registerInternalWorkflow`\n // get consistent semantics across transports.\n #localCallbacks: Map<string, Set<EventCallback>> = new Map();\n #closed = false;\n\n constructor(options: RedisStreamsPubSubConfig = {}) {\n super();\n const url = options.url ?? options.redisOptions?.url ?? 'redis://localhost:6379';\n this.#connectOptions = { ...options.redisOptions, url };\n this.#writeClient = createClient(this.#connectOptions) as RedisClientType;\n this.#keyPrefix = options.keyPrefix ?? 'mastra:topic';\n this.#blockMs = options.blockMs ?? 1000;\n this.#maxStreamLength = options.maxStreamLength ?? 10_000;\n this.#reclaimIntervalMs = options.reclaimIntervalMs ?? 30_000;\n this.#reclaimIdleMs = options.reclaimIdleMs ?? 60_000;\n const cap = options.maxDeliveryAttempts ?? 5;\n if (cap === 0) {\n options.logger?.warn?.(\n 'redis-streams: maxDeliveryAttempts=0 is treated as Infinity for back-compat; pass Infinity to disable the cap explicitly.',\n );\n this.#maxDeliveryAttempts = Infinity;\n } else if (cap < 0 || Number.isNaN(cap)) {\n throw new Error(`redis-streams: maxDeliveryAttempts must be >= 1 or Infinity, got ${cap}`);\n } else {\n this.#maxDeliveryAttempts = cap;\n }\n this.#logger = options.logger;\n }\n\n #subKey(topic: string, cb: EventCallback): string {\n let cbId = this.#cbIds.get(cb);\n if (!cbId) {\n cbId = randomUUID();\n this.#cbIds.set(cb, cbId);\n }\n return `${topic}::${cbId}`;\n }\n\n /** Lazily connect the shared writer client. Idempotent. */\n async #ensureWriterConnected(): Promise<void> {\n if (this.#writeClient.isOpen) return;\n await this.#writeClient.connect();\n }\n\n #streamKey(topic: string): string {\n return `${this.#keyPrefix}:${topic}`;\n }\n\n async publish(\n topic: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n if (this.#closed) throw new Error('RedisStreamsPubSub: cannot publish on closed client');\n\n // `localOnly` events stay entirely within the publishing process. They are\n // never serialized through Redis, so live values on the payload (e.g.\n // `MastraModelOutput` returned via `workflows-finish` for an evented\n // agent run) keep their prototype and methods intact.\n if (options?.localOnly) {\n const localEvent: Event = {\n ...event,\n id: randomUUID(),\n createdAt: new Date(),\n deliveryAttempt: event.deliveryAttempt ?? 1,\n };\n this.#deliverLocal(topic, localEvent);\n return;\n }\n\n await this.#ensureWriterConnected();\n\n const id = randomUUID();\n const createdAt = new Date();\n const payload: Event = {\n ...event,\n id,\n createdAt,\n deliveryAttempt: event.deliveryAttempt ?? 1,\n };\n const xaddOptions: { TRIM?: { strategy: 'MAXLEN'; strategyModifier: '~'; threshold: number } } = {};\n if (this.#maxStreamLength > 0) {\n xaddOptions.TRIM = {\n strategy: 'MAXLEN',\n strategyModifier: '~',\n threshold: this.#maxStreamLength,\n };\n }\n const promise = this.#writeClient.xAdd(\n this.#streamKey(topic),\n '*',\n { event: JSON.stringify(payload) },\n xaddOptions,\n );\n this.#pendingPublishes.add(promise);\n try {\n await promise;\n } finally {\n this.#pendingPublishes.delete(promise);\n }\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (this.#closed) throw new Error('RedisStreamsPubSub: cannot subscribe on closed client');\n const key = this.#subKey(topic, cb);\n if (this.#subscriptions.has(key)) return; // idempotent: same (topic, cb) already subscribed\n\n // Register for `localOnly` delivery before wiring up the Redis reader so a\n // racing publisher in the same process never misses this subscriber.\n let localBucket = this.#localCallbacks.get(topic);\n if (!localBucket) {\n localBucket = new Set();\n this.#localCallbacks.set(topic, localBucket);\n }\n localBucket.add(cb);\n\n await this.#ensureWriterConnected();\n\n const isGrouped = !!options?.group;\n const group = options?.group ?? `__fanout-${randomUUID()}`;\n const consumer = `${group}-${randomUUID()}`;\n const streamKey = this.#streamKey(topic);\n\n // Create the consumer group if it doesn't exist. MKSTREAM creates the\n // stream if needed. BUSYGROUP means another subscriber raced us — fine.\n //\n // We anchor brand-new groups at '0' (stream start) instead of '$' so that\n // a worker which subscribes after a publish still sees the backlog. This\n // is the \"late join\" case: a server may publish workflow.start before any\n // orchestrator process exists. Without this, that work is silently lost.\n // Existing groups (BUSYGROUP path) keep their own checkpoint, so this\n // doesn't change semantics for already-running clusters. Stream growth is\n // bounded by the MAXLEN ~ trim applied on every publish.\n try {\n await this.#writeClient.xGroupCreate(streamKey, group, '0', { MKSTREAM: true });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes('BUSYGROUP')) throw err;\n this.#logger?.debug?.('redis-streams: consumer group already exists', { topic, group });\n }\n\n // Each subscription gets a dedicated reader connection because XREADGROUP\n // with BLOCK > 0 holds the connection until a message arrives.\n const readClient = createClient(this.#connectOptions) as RedisClientType;\n await readClient.connect();\n\n const sub: Subscription = {\n cb,\n topic,\n streamKey,\n group,\n consumer,\n isGrouped,\n readClient,\n stopped: false,\n loop: undefined,\n reclaimTimer: undefined,\n };\n this.#subscriptions.set(key, sub);\n sub.loop = this.#runReadLoop(sub);\n this.#startReclaimLoop(sub);\n }\n\n /**\n * Periodically run XAUTOCLAIM against this subscription's group so that\n * messages a crashed/stuck consumer read but never acked get redelivered to\n * a live sibling. Runs only for grouped subscriptions — fan-out groups are\n * private to one consumer, so there's no sibling to claim from.\n */\n #startReclaimLoop(sub: Subscription): void {\n if (this.#reclaimIntervalMs <= 0) return;\n if (!sub.isGrouped) return;\n\n const tick = async () => {\n if (sub.stopped || this.#closed) return;\n try {\n const reply = await this.#writeClient.xAutoClaim(\n sub.streamKey,\n sub.group,\n sub.consumer,\n this.#reclaimIdleMs,\n '0-0',\n { COUNT: 100 },\n );\n const messages = (reply?.messages ?? []) as Array<{ id: string; message: Record<string, string> } | null>;\n for (const entry of messages) {\n if (sub.stopped || this.#closed) return;\n if (!entry) continue;\n await this.#deliverMessage(sub, entry.id, entry.message);\n }\n } catch (err) {\n this.#logger?.debug?.('redis-streams: XAUTOCLAIM failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n }\n if (sub.stopped || this.#closed) return;\n sub.reclaimTimer = setTimeout(tick, this.#reclaimIntervalMs);\n };\n\n sub.reclaimTimer = setTimeout(tick, this.#reclaimIntervalMs);\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n const localBucket = this.#localCallbacks.get(topic);\n if (localBucket) {\n localBucket.delete(cb);\n if (localBucket.size === 0) this.#localCallbacks.delete(topic);\n }\n\n const key = this.#subKey(topic, cb);\n const sub = this.#subscriptions.get(key);\n if (!sub) return;\n this.#subscriptions.delete(key);\n sub.stopped = true;\n if (sub.reclaimTimer) {\n clearTimeout(sub.reclaimTimer);\n sub.reclaimTimer = undefined;\n }\n\n // Cancel the in-flight blocking XREADGROUP by closing the reader.\n try {\n await sub.readClient.quit();\n } catch (err) {\n this.#logger?.debug?.('redis-streams: reader quit failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n\n if (sub.loop) {\n try {\n await sub.loop;\n } catch (err) {\n // loop exits naturally when readClient closes; surface only at debug.\n this.#logger?.debug?.('redis-streams: read loop exited with error', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n\n // For fan-out, drop the private group entirely so the stream can be reclaimed.\n if (!sub.isGrouped) {\n try {\n await this.#writeClient.xGroupDestroy(sub.streamKey, sub.group);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: xGroupDestroy failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n }\n\n async flush(): Promise<void> {\n // Wait for any in-flight publishes to settle.\n if (this.#pendingPublishes.size > 0) {\n await Promise.allSettled([...this.#pendingPublishes]);\n }\n }\n\n /**\n * Lease key used in Redis. Distinct prefix from streams so leases and\n * streams can't collide on key namespace.\n */\n #leaseKey(key: string): string {\n return `${this.#keyPrefix}:lease:${key}`;\n }\n\n /**\n * Atomic claim via SET NX PX. Idempotent for the same owner: if the\n * current value is already this owner, we refresh the TTL instead of\n * failing. Cross-process callers race here; Redis serializes them.\n */\n async acquireLease(key: string, owner: string, ttlMs: number): Promise<{ acquired: boolean; owner?: string }> {\n if (this.#closed) return { acquired: false };\n await this.#ensureWriterConnected();\n const redisKey = this.#leaseKey(key);\n const result = await this.#writeClient.set(redisKey, owner, { NX: true, PX: ttlMs });\n if (result === 'OK') return { acquired: true, owner };\n // Someone holds the key. Re-claim only if we still own it, refreshing the\n // TTL atomically: a bare GET+PEXPIRE would let us extend a *new* owner's\n // lease if the key expired and was re-acquired between the two calls.\n const script = `\n local current = redis.call(\"GET\", KEYS[1])\n if current == ARGV[1] then\n redis.call(\"PEXPIRE\", KEYS[1], ARGV[2])\n return 1\n end\n return 0\n `;\n const refreshed = await this.#writeClient.eval(script, {\n keys: [redisKey],\n arguments: [owner, String(ttlMs)],\n });\n if (refreshed === 1) return { acquired: true, owner };\n return { acquired: false, owner: (await this.#writeClient.get(redisKey)) ?? undefined };\n }\n\n async getLeaseOwner(key: string): Promise<string | undefined> {\n if (this.#closed) return undefined;\n await this.#ensureWriterConnected();\n const current = await this.#writeClient.get(this.#leaseKey(key));\n return current ?? undefined;\n }\n\n /**\n * Release only if we still own it. Implemented as GET+DEL with a Lua\n * script so the check-and-delete is atomic against concurrent renewals\n * from other processes.\n */\n async releaseLease(key: string, owner: string): Promise<void> {\n if (this.#closed) return;\n await this.#ensureWriterConnected();\n const script = `\n if redis.call(\"GET\", KEYS[1]) == ARGV[1] then\n return redis.call(\"DEL\", KEYS[1])\n else\n return 0\n end\n `;\n await this.#writeClient.eval(script, {\n keys: [this.#leaseKey(key)],\n arguments: [owner],\n });\n }\n\n /**\n * Extend the TTL only if we still own the lease. Returns false if the\n * lease was lost (expired or another owner took it).\n */\n async renewLease(key: string, owner: string, ttlMs: number): Promise<boolean> {\n if (this.#closed) return false;\n await this.#ensureWriterConnected();\n const script = `\n if redis.call(\"GET\", KEYS[1]) == ARGV[1] then\n return redis.call(\"PEXPIRE\", KEYS[1], ARGV[2])\n else\n return 0\n end\n `;\n const result = await this.#writeClient.eval(script, {\n keys: [this.#leaseKey(key)],\n arguments: [owner, String(ttlMs)],\n });\n return result === 1;\n }\n\n /**\n * Atomically hand the lease from `fromOwner` to `toOwner`, refreshing the\n * TTL to the full `ttlMs`, without the key ever going empty.\n *\n * Implemented as a single Lua script (GET == fromOwner -> SET toOwner PX)\n * so a racing process cannot win the key between a release and a re-acquire.\n * Returns false if `fromOwner` no longer holds the lease (expired or taken),\n * in which case the caller should fall back to a fresh `acquireLease`.\n */\n async transferLease(key: string, fromOwner: string, toOwner: string, ttlMs: number): Promise<boolean> {\n if (this.#closed) return false;\n await this.#ensureWriterConnected();\n const script = `\n if redis.call(\"GET\", KEYS[1]) == ARGV[1] then\n redis.call(\"SET\", KEYS[1], ARGV[2], \"PX\", ARGV[3])\n return 1\n else\n return 0\n end\n `;\n const result = await this.#writeClient.eval(script, {\n keys: [this.#leaseKey(key)],\n arguments: [fromOwner, toOwner, String(ttlMs)],\n });\n return result === 1;\n }\n\n /**\n * Disconnect all clients and stop all subscription loops.\n */\n async close(): Promise<void> {\n if (this.#closed) return;\n this.#closed = true;\n\n // Walk the actual subscriptions and pass the original topic through so\n // unsubscribe's key lookup works.\n const subs = [...this.#subscriptions.values()];\n await Promise.all(subs.map(sub => this.unsubscribe(sub.topic, sub.cb)));\n this.#localCallbacks.clear();\n\n if (this.#writeClient.isOpen) {\n try {\n await this.#writeClient.quit();\n } catch (err) {\n this.#logger?.debug?.('redis-streams: writer quit failed', {\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n }\n\n async #runReadLoop(sub: Subscription): Promise<void> {\n while (!sub.stopped) {\n let result;\n try {\n result = await sub.readClient.xReadGroup(sub.group, sub.consumer, [{ key: sub.streamKey, id: '>' }], {\n COUNT: 10,\n BLOCK: this.#blockMs,\n });\n } catch (err) {\n if (sub.stopped) return;\n this.#logger?.debug?.('redis-streams: xReadGroup failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n // Connection error or similar — pause briefly then retry.\n await new Promise(r => setTimeout(r, 100));\n continue;\n }\n\n if (!result || result.length === 0) continue;\n\n for (const stream of result) {\n for (const entry of stream.messages) {\n if (sub.stopped) return;\n await this.#deliverMessage(sub, entry.id, entry.message);\n }\n }\n }\n }\n\n async #deliverMessage(sub: Subscription, streamId: string, fields: Record<string, string>): Promise<void> {\n let event: Event;\n try {\n event = JSON.parse(fields.event ?? '{}') as Event;\n // createdAt is serialized as a string; rehydrate.\n if (typeof event.createdAt === 'string') {\n event.createdAt = new Date(event.createdAt);\n }\n } catch (err) {\n this.#logger?.debug?.('redis-streams: malformed payload, dropping', {\n topic: sub.topic,\n streamId,\n err: err instanceof Error ? err.message : err,\n });\n try {\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (ackErr) {\n this.#logger?.debug?.('redis-streams: xAck after malformed payload failed', {\n topic: sub.topic,\n err: ackErr instanceof Error ? ackErr.message : ackErr,\n });\n }\n return;\n }\n\n let settled = false;\n const ack = async () => {\n if (settled) return;\n settled = true;\n try {\n // Only ack against this consumer group. Do NOT xDel: the stream may\n // be consumed by other groups, and xDel removes the entry for all of\n // them. Stream growth is bounded elsewhere via MAXLEN-style trimming.\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: ack cleanup failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n };\n const nack = async () => {\n if (settled) return;\n settled = true;\n const attempt = event.deliveryAttempt ?? 1;\n // Cap redelivery to avoid an infinite poison-pill loop. When the cap\n // is hit we drop the event (xAck without republish) and warn so an\n // operator can find it in logs.\n if (attempt >= this.#maxDeliveryAttempts) {\n this.#logger?.warn?.('redis-streams: dropping event after max delivery attempts', {\n topic: sub.topic,\n eventType: event.type,\n eventId: event.id,\n attempt,\n max: this.#maxDeliveryAttempts,\n });\n try {\n // Group-scoped ack only — see ack() above for why we never xDel.\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: ack on dropped poison message failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n return;\n }\n // Republish with incremented deliveryAttempt FIRST, then ack the\n // original entry. If the republish fails we deliberately leave the\n // original message pending so XAUTOCLAIM (or another consumer) can\n // pick it up on a future tick — acking first would silently drop it.\n const next: Event = {\n ...event,\n deliveryAttempt: attempt + 1,\n };\n try {\n await this.#writeClient.xAdd(sub.streamKey, '*', { event: JSON.stringify(next) });\n } catch (err) {\n this.#logger?.warn?.('redis-streams: nack republish failed; leaving original pending for reclaim', {\n topic: sub.topic,\n eventId: event.id,\n err: err instanceof Error ? err.message : err,\n });\n // Allow this entry to be redelivered: reset settled so the next\n // delivery attempt (via XAUTOCLAIM) can ack/nack it again.\n settled = false;\n return;\n }\n try {\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: xAck after nack failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n };\n\n try {\n // EventCallback is typed `=> void` but handlers commonly return a\n // promise (TS allows Promise<void> to satisfy void). If we get one\n // back, attach a catch handler so async rejections route to nack\n // instead of silently dropping the message. We do NOT await here —\n // serializing messages on a subscription would deadlock orchestration\n // callbacks that await their own future events.\n const result: unknown = sub.cb(event, ack, nack);\n if (result && typeof (result as { then?: unknown; catch?: unknown }).catch === 'function') {\n (result as Promise<unknown>).catch(async () => {\n await nack();\n });\n }\n } catch {\n // Caller threw synchronously — treat as nack.\n await nack();\n }\n }\n\n #deliverLocal(topic: string, event: Event): void {\n const callbacks = this.#localCallbacks.get(topic);\n if (!callbacks || callbacks.size === 0) return;\n for (const cb of callbacks) {\n this.#invokeLocalCallback(topic, event, cb);\n }\n }\n\n #invokeLocalCallback(topic: string, event: Event, cb: EventCallback): void {\n // `localOnly` deliveries don't have an external broker to redeliver from,\n // so ack/nack are no-ops here. The caller still gets a real Event object\n // and can branch on `deliveryAttempt` if it cares.\n const ack = async () => {};\n const nack = async () => {};\n try {\n const result: unknown = (cb as (event: Event, ack: () => Promise<void>, nack: () => Promise<void>) => unknown)(\n event,\n ack,\n nack,\n );\n if (result && typeof (result as { then?: unknown; catch?: unknown }).catch === 'function') {\n (result as Promise<unknown>).catch(err => {\n this.#logger?.debug?.('redis-streams: local subscriber rejected', {\n topic,\n err: err instanceof Error ? err.message : err,\n });\n });\n }\n } catch (err) {\n this.#logger?.debug?.('redis-streams: local subscriber threw', {\n topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n}\n\ninterface Subscription {\n cb: EventCallback;\n topic: string;\n streamKey: string;\n group: string;\n consumer: string;\n isGrouped: boolean;\n readClient: RedisClientType;\n stopped: boolean;\n loop: Promise<void> | undefined;\n reclaimTimer: ReturnType<typeof setTimeout> | undefined;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PubSub } from '@mastra/core/events';
|
|
2
|
-
import type { Event, EventCallback, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';
|
|
2
|
+
import type { Event, EventCallback, LeaseProvider, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';
|
|
3
3
|
import type { RedisClientOptions } from 'redis';
|
|
4
4
|
/**
|
|
5
5
|
* Mastra PubSub backed by Redis Streams.
|
|
@@ -56,14 +56,47 @@ export interface RedisStreamsPubSubConfig {
|
|
|
56
56
|
warn?: (...args: unknown[]) => void;
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
-
export declare class RedisStreamsPubSub extends PubSub {
|
|
59
|
+
export declare class RedisStreamsPubSub extends PubSub implements LeaseProvider {
|
|
60
60
|
#private;
|
|
61
61
|
get supportedModes(): ReadonlyArray<PubSubDeliveryMode>;
|
|
62
62
|
constructor(options?: RedisStreamsPubSubConfig);
|
|
63
|
-
publish(topic: string, event: Omit<Event, 'id' | 'createdAt'
|
|
63
|
+
publish(topic: string, event: Omit<Event, 'id' | 'createdAt'>, options?: {
|
|
64
|
+
localOnly?: boolean;
|
|
65
|
+
}): Promise<void>;
|
|
64
66
|
subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void>;
|
|
65
67
|
unsubscribe(topic: string, cb: EventCallback): Promise<void>;
|
|
66
68
|
flush(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Atomic claim via SET NX PX. Idempotent for the same owner: if the
|
|
71
|
+
* current value is already this owner, we refresh the TTL instead of
|
|
72
|
+
* failing. Cross-process callers race here; Redis serializes them.
|
|
73
|
+
*/
|
|
74
|
+
acquireLease(key: string, owner: string, ttlMs: number): Promise<{
|
|
75
|
+
acquired: boolean;
|
|
76
|
+
owner?: string;
|
|
77
|
+
}>;
|
|
78
|
+
getLeaseOwner(key: string): Promise<string | undefined>;
|
|
79
|
+
/**
|
|
80
|
+
* Release only if we still own it. Implemented as GET+DEL with a Lua
|
|
81
|
+
* script so the check-and-delete is atomic against concurrent renewals
|
|
82
|
+
* from other processes.
|
|
83
|
+
*/
|
|
84
|
+
releaseLease(key: string, owner: string): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Extend the TTL only if we still own the lease. Returns false if the
|
|
87
|
+
* lease was lost (expired or another owner took it).
|
|
88
|
+
*/
|
|
89
|
+
renewLease(key: string, owner: string, ttlMs: number): Promise<boolean>;
|
|
90
|
+
/**
|
|
91
|
+
* Atomically hand the lease from `fromOwner` to `toOwner`, refreshing the
|
|
92
|
+
* TTL to the full `ttlMs`, without the key ever going empty.
|
|
93
|
+
*
|
|
94
|
+
* Implemented as a single Lua script (GET == fromOwner -> SET toOwner PX)
|
|
95
|
+
* so a racing process cannot win the key between a release and a re-acquire.
|
|
96
|
+
* Returns false if `fromOwner` no longer holds the lease (expired or taken),
|
|
97
|
+
* in which case the caller should fall back to a fresh `acquireLease`.
|
|
98
|
+
*/
|
|
99
|
+
transferLease(key: string, fromOwner: string, toOwner: string, ttlMs: number): Promise<boolean>;
|
|
67
100
|
/**
|
|
68
101
|
* Disconnect all clients and stop all subscription loops.
|
|
69
102
|
*/
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAErH,OAAO,KAAK,EAAE,kBAAkB,EAAmB,MAAM,OAAO,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,wBAAwB;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CACxF;AAED,qBAAa,kBAAmB,SAAQ,MAAO,YAAW,aAAa;;IAGrE,IAAa,cAAc,IAAI,aAAa,CAAC,kBAAkB,CAAC,CAE/D;gBAyBW,OAAO,GAAE,wBAA6B;IA2C5C,OAAO,CACX,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,WAAW,CAAC,EACtC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAChC,OAAO,CAAC,IAAI,CAAC;IAkDV,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsGtF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAqD5D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAe5B;;;;OAIG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAyBvG,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAO7D;;;;OAIG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7D;;;OAGG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB7E;;;;;;;;OAQG;IACG,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkBrG;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA4M7B"}
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,12 @@ var RedisStreamsPubSub = class extends PubSub {
|
|
|
24
24
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
25
25
|
#cbIds = /* @__PURE__ */ new WeakMap();
|
|
26
26
|
#pendingPublishes = /* @__PURE__ */ new Set();
|
|
27
|
+
// `localOnly` publishes bypass Redis entirely so values carrying live
|
|
28
|
+
// methods (e.g. `MastraModelOutput` returned from an evented agent run via
|
|
29
|
+
// `workflows-finish`) survive intact. Mirrors the same contract honored by
|
|
30
|
+
// `UnixSocketPubSub` so consumers like `Mastra.__registerInternalWorkflow`
|
|
31
|
+
// get consistent semantics across transports.
|
|
32
|
+
#localCallbacks = /* @__PURE__ */ new Map();
|
|
27
33
|
#closed = false;
|
|
28
34
|
constructor(options = {}) {
|
|
29
35
|
super();
|
|
@@ -64,8 +70,18 @@ var RedisStreamsPubSub = class extends PubSub {
|
|
|
64
70
|
#streamKey(topic) {
|
|
65
71
|
return `${this.#keyPrefix}:${topic}`;
|
|
66
72
|
}
|
|
67
|
-
async publish(topic, event) {
|
|
73
|
+
async publish(topic, event, options) {
|
|
68
74
|
if (this.#closed) throw new Error("RedisStreamsPubSub: cannot publish on closed client");
|
|
75
|
+
if (options?.localOnly) {
|
|
76
|
+
const localEvent = {
|
|
77
|
+
...event,
|
|
78
|
+
id: randomUUID(),
|
|
79
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
80
|
+
deliveryAttempt: event.deliveryAttempt ?? 1
|
|
81
|
+
};
|
|
82
|
+
this.#deliverLocal(topic, localEvent);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
69
85
|
await this.#ensureWriterConnected();
|
|
70
86
|
const id = randomUUID();
|
|
71
87
|
const createdAt = /* @__PURE__ */ new Date();
|
|
@@ -100,6 +116,12 @@ var RedisStreamsPubSub = class extends PubSub {
|
|
|
100
116
|
if (this.#closed) throw new Error("RedisStreamsPubSub: cannot subscribe on closed client");
|
|
101
117
|
const key = this.#subKey(topic, cb);
|
|
102
118
|
if (this.#subscriptions.has(key)) return;
|
|
119
|
+
let localBucket = this.#localCallbacks.get(topic);
|
|
120
|
+
if (!localBucket) {
|
|
121
|
+
localBucket = /* @__PURE__ */ new Set();
|
|
122
|
+
this.#localCallbacks.set(topic, localBucket);
|
|
123
|
+
}
|
|
124
|
+
localBucket.add(cb);
|
|
103
125
|
await this.#ensureWriterConnected();
|
|
104
126
|
const isGrouped = !!options?.group;
|
|
105
127
|
const group = options?.group ?? `__fanout-${randomUUID()}`;
|
|
@@ -169,6 +191,11 @@ var RedisStreamsPubSub = class extends PubSub {
|
|
|
169
191
|
sub.reclaimTimer = setTimeout(tick, this.#reclaimIntervalMs);
|
|
170
192
|
}
|
|
171
193
|
async unsubscribe(topic, cb) {
|
|
194
|
+
const localBucket = this.#localCallbacks.get(topic);
|
|
195
|
+
if (localBucket) {
|
|
196
|
+
localBucket.delete(cb);
|
|
197
|
+
if (localBucket.size === 0) this.#localCallbacks.delete(topic);
|
|
198
|
+
}
|
|
172
199
|
const key = this.#subKey(topic, cb);
|
|
173
200
|
const sub = this.#subscriptions.get(key);
|
|
174
201
|
if (!sub) return;
|
|
@@ -213,6 +240,111 @@ var RedisStreamsPubSub = class extends PubSub {
|
|
|
213
240
|
await Promise.allSettled([...this.#pendingPublishes]);
|
|
214
241
|
}
|
|
215
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Lease key used in Redis. Distinct prefix from streams so leases and
|
|
245
|
+
* streams can't collide on key namespace.
|
|
246
|
+
*/
|
|
247
|
+
#leaseKey(key) {
|
|
248
|
+
return `${this.#keyPrefix}:lease:${key}`;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Atomic claim via SET NX PX. Idempotent for the same owner: if the
|
|
252
|
+
* current value is already this owner, we refresh the TTL instead of
|
|
253
|
+
* failing. Cross-process callers race here; Redis serializes them.
|
|
254
|
+
*/
|
|
255
|
+
async acquireLease(key, owner, ttlMs) {
|
|
256
|
+
if (this.#closed) return { acquired: false };
|
|
257
|
+
await this.#ensureWriterConnected();
|
|
258
|
+
const redisKey = this.#leaseKey(key);
|
|
259
|
+
const result = await this.#writeClient.set(redisKey, owner, { NX: true, PX: ttlMs });
|
|
260
|
+
if (result === "OK") return { acquired: true, owner };
|
|
261
|
+
const script = `
|
|
262
|
+
local current = redis.call("GET", KEYS[1])
|
|
263
|
+
if current == ARGV[1] then
|
|
264
|
+
redis.call("PEXPIRE", KEYS[1], ARGV[2])
|
|
265
|
+
return 1
|
|
266
|
+
end
|
|
267
|
+
return 0
|
|
268
|
+
`;
|
|
269
|
+
const refreshed = await this.#writeClient.eval(script, {
|
|
270
|
+
keys: [redisKey],
|
|
271
|
+
arguments: [owner, String(ttlMs)]
|
|
272
|
+
});
|
|
273
|
+
if (refreshed === 1) return { acquired: true, owner };
|
|
274
|
+
return { acquired: false, owner: await this.#writeClient.get(redisKey) ?? void 0 };
|
|
275
|
+
}
|
|
276
|
+
async getLeaseOwner(key) {
|
|
277
|
+
if (this.#closed) return void 0;
|
|
278
|
+
await this.#ensureWriterConnected();
|
|
279
|
+
const current = await this.#writeClient.get(this.#leaseKey(key));
|
|
280
|
+
return current ?? void 0;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Release only if we still own it. Implemented as GET+DEL with a Lua
|
|
284
|
+
* script so the check-and-delete is atomic against concurrent renewals
|
|
285
|
+
* from other processes.
|
|
286
|
+
*/
|
|
287
|
+
async releaseLease(key, owner) {
|
|
288
|
+
if (this.#closed) return;
|
|
289
|
+
await this.#ensureWriterConnected();
|
|
290
|
+
const script = `
|
|
291
|
+
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
|
292
|
+
return redis.call("DEL", KEYS[1])
|
|
293
|
+
else
|
|
294
|
+
return 0
|
|
295
|
+
end
|
|
296
|
+
`;
|
|
297
|
+
await this.#writeClient.eval(script, {
|
|
298
|
+
keys: [this.#leaseKey(key)],
|
|
299
|
+
arguments: [owner]
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Extend the TTL only if we still own the lease. Returns false if the
|
|
304
|
+
* lease was lost (expired or another owner took it).
|
|
305
|
+
*/
|
|
306
|
+
async renewLease(key, owner, ttlMs) {
|
|
307
|
+
if (this.#closed) return false;
|
|
308
|
+
await this.#ensureWriterConnected();
|
|
309
|
+
const script = `
|
|
310
|
+
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
|
311
|
+
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
|
|
312
|
+
else
|
|
313
|
+
return 0
|
|
314
|
+
end
|
|
315
|
+
`;
|
|
316
|
+
const result = await this.#writeClient.eval(script, {
|
|
317
|
+
keys: [this.#leaseKey(key)],
|
|
318
|
+
arguments: [owner, String(ttlMs)]
|
|
319
|
+
});
|
|
320
|
+
return result === 1;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Atomically hand the lease from `fromOwner` to `toOwner`, refreshing the
|
|
324
|
+
* TTL to the full `ttlMs`, without the key ever going empty.
|
|
325
|
+
*
|
|
326
|
+
* Implemented as a single Lua script (GET == fromOwner -> SET toOwner PX)
|
|
327
|
+
* so a racing process cannot win the key between a release and a re-acquire.
|
|
328
|
+
* Returns false if `fromOwner` no longer holds the lease (expired or taken),
|
|
329
|
+
* in which case the caller should fall back to a fresh `acquireLease`.
|
|
330
|
+
*/
|
|
331
|
+
async transferLease(key, fromOwner, toOwner, ttlMs) {
|
|
332
|
+
if (this.#closed) return false;
|
|
333
|
+
await this.#ensureWriterConnected();
|
|
334
|
+
const script = `
|
|
335
|
+
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
|
336
|
+
redis.call("SET", KEYS[1], ARGV[2], "PX", ARGV[3])
|
|
337
|
+
return 1
|
|
338
|
+
else
|
|
339
|
+
return 0
|
|
340
|
+
end
|
|
341
|
+
`;
|
|
342
|
+
const result = await this.#writeClient.eval(script, {
|
|
343
|
+
keys: [this.#leaseKey(key)],
|
|
344
|
+
arguments: [fromOwner, toOwner, String(ttlMs)]
|
|
345
|
+
});
|
|
346
|
+
return result === 1;
|
|
347
|
+
}
|
|
216
348
|
/**
|
|
217
349
|
* Disconnect all clients and stop all subscription loops.
|
|
218
350
|
*/
|
|
@@ -221,6 +353,7 @@ var RedisStreamsPubSub = class extends PubSub {
|
|
|
221
353
|
this.#closed = true;
|
|
222
354
|
const subs = [...this.#subscriptions.values()];
|
|
223
355
|
await Promise.all(subs.map((sub) => this.unsubscribe(sub.topic, sub.cb)));
|
|
356
|
+
this.#localCallbacks.clear();
|
|
224
357
|
if (this.#writeClient.isOpen) {
|
|
225
358
|
try {
|
|
226
359
|
await this.#writeClient.quit();
|
|
@@ -351,6 +484,39 @@ var RedisStreamsPubSub = class extends PubSub {
|
|
|
351
484
|
await nack();
|
|
352
485
|
}
|
|
353
486
|
}
|
|
487
|
+
#deliverLocal(topic, event) {
|
|
488
|
+
const callbacks = this.#localCallbacks.get(topic);
|
|
489
|
+
if (!callbacks || callbacks.size === 0) return;
|
|
490
|
+
for (const cb of callbacks) {
|
|
491
|
+
this.#invokeLocalCallback(topic, event, cb);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
#invokeLocalCallback(topic, event, cb) {
|
|
495
|
+
const ack = async () => {
|
|
496
|
+
};
|
|
497
|
+
const nack = async () => {
|
|
498
|
+
};
|
|
499
|
+
try {
|
|
500
|
+
const result = cb(
|
|
501
|
+
event,
|
|
502
|
+
ack,
|
|
503
|
+
nack
|
|
504
|
+
);
|
|
505
|
+
if (result && typeof result.catch === "function") {
|
|
506
|
+
result.catch((err) => {
|
|
507
|
+
this.#logger?.debug?.("redis-streams: local subscriber rejected", {
|
|
508
|
+
topic,
|
|
509
|
+
err: err instanceof Error ? err.message : err
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
} catch (err) {
|
|
514
|
+
this.#logger?.debug?.("redis-streams: local subscriber threw", {
|
|
515
|
+
topic,
|
|
516
|
+
err: err instanceof Error ? err.message : err
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
354
520
|
};
|
|
355
521
|
|
|
356
522
|
export { RedisStreamsPubSub };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { PubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';\nimport { createClient } from 'redis';\nimport type { RedisClientOptions, RedisClientType } from 'redis';\n\n/**\n * Mastra PubSub backed by Redis Streams.\n *\n * - Each topic maps to a Redis stream key `<prefix>:<topic>`.\n * - Subscriptions with `options.group` use a real Redis consumer group, so\n * competing subscribers in the same group share the work (round-robin).\n * - Subscriptions without a group create a private per-subscriber consumer\n * group, so they get fan-out semantics (every subscriber sees every event).\n * - Nack triggers redelivery by re-publishing the event with an incremented\n * `deliveryAttempt` field, then XACK-ing the original. This trades strict\n * FIFO ordering on retry for a simple, reliable redelivery path.\n */\nexport interface RedisStreamsPubSubConfig {\n url?: string;\n keyPrefix?: string;\n blockMs?: number;\n redisOptions?: RedisClientOptions;\n /**\n * Approximate maximum number of entries kept per stream. On every publish we\n * issue MAXLEN ~ N which lets Redis trim opportunistically. Defaults to\n * 10_000 — set to 0 to disable trimming.\n */\n maxStreamLength?: number;\n /**\n * How often (in ms) each subscription runs XAUTOCLAIM to recover messages\n * that an earlier consumer in the group read but never acked. Defaults to\n * 30_000 ms. Set to 0 to disable.\n */\n reclaimIntervalMs?: number;\n /**\n * Minimum idle time (in ms) before a pending message is eligible for\n * reclaim. Should be much larger than typical in-flight processing time to\n * avoid double-delivery. Defaults to 60_000 ms.\n */\n reclaimIdleMs?: number;\n /**\n * Maximum number of times a single event will be redelivered via nack\n * before it is dropped (acked without republish). Defaults to 5. Set to\n * `Infinity` to disable the cap (events redeliver forever on every nack).\n *\n * `0` is treated as `Infinity` with a one-time warn for back-compat;\n * prefer `Infinity` to disable the cap explicitly.\n */\n maxDeliveryAttempts?: number;\n /**\n * Optional logger for diagnostics. When omitted, suppressed errors\n * (BUSYGROUP, malformed payloads, connection-close races) are swallowed\n * silently. When provided, those paths emit `debug`/`warn` entries so\n * operators can see what's happening without noise on the happy path.\n */\n logger?: { debug?: (...args: unknown[]) => void; warn?: (...args: unknown[]) => void };\n}\n\nexport class RedisStreamsPubSub extends PubSub {\n // Redis Streams is a pull transport: consumers issue XREADGROUP to read\n // events. Mastra reads this to know an OrchestrationWorker is required.\n override get supportedModes(): ReadonlyArray<PubSubDeliveryMode> {\n return ['pull'];\n }\n\n #writeClient: RedisClientType;\n #connectOptions: RedisClientOptions;\n #keyPrefix: string;\n #blockMs: number;\n #maxStreamLength: number;\n #reclaimIntervalMs: number;\n #reclaimIdleMs: number;\n #maxDeliveryAttempts: number;\n #logger?: RedisStreamsPubSubConfig['logger'];\n // Keyed by `${topic}::${cbId}` so the same callback can be subscribed to\n // multiple topics independently. Without the topic in the key,\n // unsubscribe(otherTopic, cb) would tear down the wrong subscription.\n #subscriptions: Map<string, Subscription> = new Map();\n #cbIds: WeakMap<EventCallback, string> = new WeakMap();\n #pendingPublishes: Set<Promise<unknown>> = new Set();\n #closed = false;\n\n constructor(options: RedisStreamsPubSubConfig = {}) {\n super();\n const url = options.url ?? options.redisOptions?.url ?? 'redis://localhost:6379';\n this.#connectOptions = { ...options.redisOptions, url };\n this.#writeClient = createClient(this.#connectOptions) as RedisClientType;\n this.#keyPrefix = options.keyPrefix ?? 'mastra:topic';\n this.#blockMs = options.blockMs ?? 1000;\n this.#maxStreamLength = options.maxStreamLength ?? 10_000;\n this.#reclaimIntervalMs = options.reclaimIntervalMs ?? 30_000;\n this.#reclaimIdleMs = options.reclaimIdleMs ?? 60_000;\n const cap = options.maxDeliveryAttempts ?? 5;\n if (cap === 0) {\n options.logger?.warn?.(\n 'redis-streams: maxDeliveryAttempts=0 is treated as Infinity for back-compat; pass Infinity to disable the cap explicitly.',\n );\n this.#maxDeliveryAttempts = Infinity;\n } else if (cap < 0 || Number.isNaN(cap)) {\n throw new Error(`redis-streams: maxDeliveryAttempts must be >= 1 or Infinity, got ${cap}`);\n } else {\n this.#maxDeliveryAttempts = cap;\n }\n this.#logger = options.logger;\n }\n\n #subKey(topic: string, cb: EventCallback): string {\n let cbId = this.#cbIds.get(cb);\n if (!cbId) {\n cbId = randomUUID();\n this.#cbIds.set(cb, cbId);\n }\n return `${topic}::${cbId}`;\n }\n\n /** Lazily connect the shared writer client. Idempotent. */\n async #ensureWriterConnected(): Promise<void> {\n if (this.#writeClient.isOpen) return;\n await this.#writeClient.connect();\n }\n\n #streamKey(topic: string): string {\n return `${this.#keyPrefix}:${topic}`;\n }\n\n async publish(topic: string, event: Omit<Event, 'id' | 'createdAt'>): Promise<void> {\n if (this.#closed) throw new Error('RedisStreamsPubSub: cannot publish on closed client');\n await this.#ensureWriterConnected();\n\n const id = randomUUID();\n const createdAt = new Date();\n const payload: Event = {\n ...event,\n id,\n createdAt,\n deliveryAttempt: event.deliveryAttempt ?? 1,\n };\n const xaddOptions: { TRIM?: { strategy: 'MAXLEN'; strategyModifier: '~'; threshold: number } } = {};\n if (this.#maxStreamLength > 0) {\n xaddOptions.TRIM = {\n strategy: 'MAXLEN',\n strategyModifier: '~',\n threshold: this.#maxStreamLength,\n };\n }\n const promise = this.#writeClient.xAdd(\n this.#streamKey(topic),\n '*',\n { event: JSON.stringify(payload) },\n xaddOptions,\n );\n this.#pendingPublishes.add(promise);\n try {\n await promise;\n } finally {\n this.#pendingPublishes.delete(promise);\n }\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (this.#closed) throw new Error('RedisStreamsPubSub: cannot subscribe on closed client');\n const key = this.#subKey(topic, cb);\n if (this.#subscriptions.has(key)) return; // idempotent: same (topic, cb) already subscribed\n\n await this.#ensureWriterConnected();\n\n const isGrouped = !!options?.group;\n const group = options?.group ?? `__fanout-${randomUUID()}`;\n const consumer = `${group}-${randomUUID()}`;\n const streamKey = this.#streamKey(topic);\n\n // Create the consumer group if it doesn't exist. MKSTREAM creates the\n // stream if needed. BUSYGROUP means another subscriber raced us — fine.\n //\n // We anchor brand-new groups at '0' (stream start) instead of '$' so that\n // a worker which subscribes after a publish still sees the backlog. This\n // is the \"late join\" case: a server may publish workflow.start before any\n // orchestrator process exists. Without this, that work is silently lost.\n // Existing groups (BUSYGROUP path) keep their own checkpoint, so this\n // doesn't change semantics for already-running clusters. Stream growth is\n // bounded by the MAXLEN ~ trim applied on every publish.\n try {\n await this.#writeClient.xGroupCreate(streamKey, group, '0', { MKSTREAM: true });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes('BUSYGROUP')) throw err;\n this.#logger?.debug?.('redis-streams: consumer group already exists', { topic, group });\n }\n\n // Each subscription gets a dedicated reader connection because XREADGROUP\n // with BLOCK > 0 holds the connection until a message arrives.\n const readClient = createClient(this.#connectOptions) as RedisClientType;\n await readClient.connect();\n\n const sub: Subscription = {\n cb,\n topic,\n streamKey,\n group,\n consumer,\n isGrouped,\n readClient,\n stopped: false,\n loop: undefined,\n reclaimTimer: undefined,\n };\n this.#subscriptions.set(key, sub);\n sub.loop = this.#runReadLoop(sub);\n this.#startReclaimLoop(sub);\n }\n\n /**\n * Periodically run XAUTOCLAIM against this subscription's group so that\n * messages a crashed/stuck consumer read but never acked get redelivered to\n * a live sibling. Runs only for grouped subscriptions — fan-out groups are\n * private to one consumer, so there's no sibling to claim from.\n */\n #startReclaimLoop(sub: Subscription): void {\n if (this.#reclaimIntervalMs <= 0) return;\n if (!sub.isGrouped) return;\n\n const tick = async () => {\n if (sub.stopped || this.#closed) return;\n try {\n const reply = await this.#writeClient.xAutoClaim(\n sub.streamKey,\n sub.group,\n sub.consumer,\n this.#reclaimIdleMs,\n '0-0',\n { COUNT: 100 },\n );\n const messages = (reply?.messages ?? []) as Array<{ id: string; message: Record<string, string> } | null>;\n for (const entry of messages) {\n if (sub.stopped || this.#closed) return;\n if (!entry) continue;\n await this.#deliverMessage(sub, entry.id, entry.message);\n }\n } catch (err) {\n this.#logger?.debug?.('redis-streams: XAUTOCLAIM failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n }\n if (sub.stopped || this.#closed) return;\n sub.reclaimTimer = setTimeout(tick, this.#reclaimIntervalMs);\n };\n\n sub.reclaimTimer = setTimeout(tick, this.#reclaimIntervalMs);\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n const key = this.#subKey(topic, cb);\n const sub = this.#subscriptions.get(key);\n if (!sub) return;\n this.#subscriptions.delete(key);\n sub.stopped = true;\n if (sub.reclaimTimer) {\n clearTimeout(sub.reclaimTimer);\n sub.reclaimTimer = undefined;\n }\n\n // Cancel the in-flight blocking XREADGROUP by closing the reader.\n try {\n await sub.readClient.quit();\n } catch (err) {\n this.#logger?.debug?.('redis-streams: reader quit failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n\n if (sub.loop) {\n try {\n await sub.loop;\n } catch (err) {\n // loop exits naturally when readClient closes; surface only at debug.\n this.#logger?.debug?.('redis-streams: read loop exited with error', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n\n // For fan-out, drop the private group entirely so the stream can be reclaimed.\n if (!sub.isGrouped) {\n try {\n await this.#writeClient.xGroupDestroy(sub.streamKey, sub.group);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: xGroupDestroy failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n }\n\n async flush(): Promise<void> {\n // Wait for any in-flight publishes to settle.\n if (this.#pendingPublishes.size > 0) {\n await Promise.allSettled([...this.#pendingPublishes]);\n }\n }\n\n /**\n * Disconnect all clients and stop all subscription loops.\n */\n async close(): Promise<void> {\n if (this.#closed) return;\n this.#closed = true;\n\n // Walk the actual subscriptions and pass the original topic through so\n // unsubscribe's key lookup works.\n const subs = [...this.#subscriptions.values()];\n await Promise.all(subs.map(sub => this.unsubscribe(sub.topic, sub.cb)));\n\n if (this.#writeClient.isOpen) {\n try {\n await this.#writeClient.quit();\n } catch (err) {\n this.#logger?.debug?.('redis-streams: writer quit failed', {\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n }\n\n async #runReadLoop(sub: Subscription): Promise<void> {\n while (!sub.stopped) {\n let result;\n try {\n result = await sub.readClient.xReadGroup(sub.group, sub.consumer, [{ key: sub.streamKey, id: '>' }], {\n COUNT: 10,\n BLOCK: this.#blockMs,\n });\n } catch (err) {\n if (sub.stopped) return;\n this.#logger?.debug?.('redis-streams: xReadGroup failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n // Connection error or similar — pause briefly then retry.\n await new Promise(r => setTimeout(r, 100));\n continue;\n }\n\n if (!result || result.length === 0) continue;\n\n for (const stream of result) {\n for (const entry of stream.messages) {\n if (sub.stopped) return;\n await this.#deliverMessage(sub, entry.id, entry.message);\n }\n }\n }\n }\n\n async #deliverMessage(sub: Subscription, streamId: string, fields: Record<string, string>): Promise<void> {\n let event: Event;\n try {\n event = JSON.parse(fields.event ?? '{}') as Event;\n // createdAt is serialized as a string; rehydrate.\n if (typeof event.createdAt === 'string') {\n event.createdAt = new Date(event.createdAt);\n }\n } catch (err) {\n this.#logger?.debug?.('redis-streams: malformed payload, dropping', {\n topic: sub.topic,\n streamId,\n err: err instanceof Error ? err.message : err,\n });\n try {\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (ackErr) {\n this.#logger?.debug?.('redis-streams: xAck after malformed payload failed', {\n topic: sub.topic,\n err: ackErr instanceof Error ? ackErr.message : ackErr,\n });\n }\n return;\n }\n\n let settled = false;\n const ack = async () => {\n if (settled) return;\n settled = true;\n try {\n // Only ack against this consumer group. Do NOT xDel: the stream may\n // be consumed by other groups, and xDel removes the entry for all of\n // them. Stream growth is bounded elsewhere via MAXLEN-style trimming.\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: ack cleanup failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n };\n const nack = async () => {\n if (settled) return;\n settled = true;\n const attempt = event.deliveryAttempt ?? 1;\n // Cap redelivery to avoid an infinite poison-pill loop. When the cap\n // is hit we drop the event (xAck without republish) and warn so an\n // operator can find it in logs.\n if (attempt >= this.#maxDeliveryAttempts) {\n this.#logger?.warn?.('redis-streams: dropping event after max delivery attempts', {\n topic: sub.topic,\n eventType: event.type,\n eventId: event.id,\n attempt,\n max: this.#maxDeliveryAttempts,\n });\n try {\n // Group-scoped ack only — see ack() above for why we never xDel.\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: ack on dropped poison message failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n return;\n }\n // Republish with incremented deliveryAttempt FIRST, then ack the\n // original entry. If the republish fails we deliberately leave the\n // original message pending so XAUTOCLAIM (or another consumer) can\n // pick it up on a future tick — acking first would silently drop it.\n const next: Event = {\n ...event,\n deliveryAttempt: attempt + 1,\n };\n try {\n await this.#writeClient.xAdd(sub.streamKey, '*', { event: JSON.stringify(next) });\n } catch (err) {\n this.#logger?.warn?.('redis-streams: nack republish failed; leaving original pending for reclaim', {\n topic: sub.topic,\n eventId: event.id,\n err: err instanceof Error ? err.message : err,\n });\n // Allow this entry to be redelivered: reset settled so the next\n // delivery attempt (via XAUTOCLAIM) can ack/nack it again.\n settled = false;\n return;\n }\n try {\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: xAck after nack failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n };\n\n try {\n // EventCallback is typed `=> void` but handlers commonly return a\n // promise (TS allows Promise<void> to satisfy void). If we get one\n // back, attach a catch handler so async rejections route to nack\n // instead of silently dropping the message. We do NOT await here —\n // serializing messages on a subscription would deadlock orchestration\n // callbacks that await their own future events.\n const result: unknown = sub.cb(event, ack, nack);\n if (result && typeof (result as { then?: unknown; catch?: unknown }).catch === 'function') {\n (result as Promise<unknown>).catch(async () => {\n await nack();\n });\n }\n } catch {\n // Caller threw synchronously — treat as nack.\n await nack();\n }\n }\n}\n\ninterface Subscription {\n cb: EventCallback;\n topic: string;\n streamKey: string;\n group: string;\n consumer: string;\n isGrouped: boolean;\n readClient: RedisClientType;\n stopped: boolean;\n loop: Promise<void> | undefined;\n reclaimTimer: ReturnType<typeof setTimeout> | undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { PubSub } from '@mastra/core/events';\nimport type { Event, EventCallback, LeaseProvider, PubSubDeliveryMode, SubscribeOptions } from '@mastra/core/events';\nimport { createClient } from 'redis';\nimport type { RedisClientOptions, RedisClientType } from 'redis';\n\n/**\n * Mastra PubSub backed by Redis Streams.\n *\n * - Each topic maps to a Redis stream key `<prefix>:<topic>`.\n * - Subscriptions with `options.group` use a real Redis consumer group, so\n * competing subscribers in the same group share the work (round-robin).\n * - Subscriptions without a group create a private per-subscriber consumer\n * group, so they get fan-out semantics (every subscriber sees every event).\n * - Nack triggers redelivery by re-publishing the event with an incremented\n * `deliveryAttempt` field, then XACK-ing the original. This trades strict\n * FIFO ordering on retry for a simple, reliable redelivery path.\n */\nexport interface RedisStreamsPubSubConfig {\n url?: string;\n keyPrefix?: string;\n blockMs?: number;\n redisOptions?: RedisClientOptions;\n /**\n * Approximate maximum number of entries kept per stream. On every publish we\n * issue MAXLEN ~ N which lets Redis trim opportunistically. Defaults to\n * 10_000 — set to 0 to disable trimming.\n */\n maxStreamLength?: number;\n /**\n * How often (in ms) each subscription runs XAUTOCLAIM to recover messages\n * that an earlier consumer in the group read but never acked. Defaults to\n * 30_000 ms. Set to 0 to disable.\n */\n reclaimIntervalMs?: number;\n /**\n * Minimum idle time (in ms) before a pending message is eligible for\n * reclaim. Should be much larger than typical in-flight processing time to\n * avoid double-delivery. Defaults to 60_000 ms.\n */\n reclaimIdleMs?: number;\n /**\n * Maximum number of times a single event will be redelivered via nack\n * before it is dropped (acked without republish). Defaults to 5. Set to\n * `Infinity` to disable the cap (events redeliver forever on every nack).\n *\n * `0` is treated as `Infinity` with a one-time warn for back-compat;\n * prefer `Infinity` to disable the cap explicitly.\n */\n maxDeliveryAttempts?: number;\n /**\n * Optional logger for diagnostics. When omitted, suppressed errors\n * (BUSYGROUP, malformed payloads, connection-close races) are swallowed\n * silently. When provided, those paths emit `debug`/`warn` entries so\n * operators can see what's happening without noise on the happy path.\n */\n logger?: { debug?: (...args: unknown[]) => void; warn?: (...args: unknown[]) => void };\n}\n\nexport class RedisStreamsPubSub extends PubSub implements LeaseProvider {\n // Redis Streams is a pull transport: consumers issue XREADGROUP to read\n // events. Mastra reads this to know an OrchestrationWorker is required.\n override get supportedModes(): ReadonlyArray<PubSubDeliveryMode> {\n return ['pull'];\n }\n\n #writeClient: RedisClientType;\n #connectOptions: RedisClientOptions;\n #keyPrefix: string;\n #blockMs: number;\n #maxStreamLength: number;\n #reclaimIntervalMs: number;\n #reclaimIdleMs: number;\n #maxDeliveryAttempts: number;\n #logger?: RedisStreamsPubSubConfig['logger'];\n // Keyed by `${topic}::${cbId}` so the same callback can be subscribed to\n // multiple topics independently. Without the topic in the key,\n // unsubscribe(otherTopic, cb) would tear down the wrong subscription.\n #subscriptions: Map<string, Subscription> = new Map();\n #cbIds: WeakMap<EventCallback, string> = new WeakMap();\n #pendingPublishes: Set<Promise<unknown>> = new Set();\n // `localOnly` publishes bypass Redis entirely so values carrying live\n // methods (e.g. `MastraModelOutput` returned from an evented agent run via\n // `workflows-finish`) survive intact. Mirrors the same contract honored by\n // `UnixSocketPubSub` so consumers like `Mastra.__registerInternalWorkflow`\n // get consistent semantics across transports.\n #localCallbacks: Map<string, Set<EventCallback>> = new Map();\n #closed = false;\n\n constructor(options: RedisStreamsPubSubConfig = {}) {\n super();\n const url = options.url ?? options.redisOptions?.url ?? 'redis://localhost:6379';\n this.#connectOptions = { ...options.redisOptions, url };\n this.#writeClient = createClient(this.#connectOptions) as RedisClientType;\n this.#keyPrefix = options.keyPrefix ?? 'mastra:topic';\n this.#blockMs = options.blockMs ?? 1000;\n this.#maxStreamLength = options.maxStreamLength ?? 10_000;\n this.#reclaimIntervalMs = options.reclaimIntervalMs ?? 30_000;\n this.#reclaimIdleMs = options.reclaimIdleMs ?? 60_000;\n const cap = options.maxDeliveryAttempts ?? 5;\n if (cap === 0) {\n options.logger?.warn?.(\n 'redis-streams: maxDeliveryAttempts=0 is treated as Infinity for back-compat; pass Infinity to disable the cap explicitly.',\n );\n this.#maxDeliveryAttempts = Infinity;\n } else if (cap < 0 || Number.isNaN(cap)) {\n throw new Error(`redis-streams: maxDeliveryAttempts must be >= 1 or Infinity, got ${cap}`);\n } else {\n this.#maxDeliveryAttempts = cap;\n }\n this.#logger = options.logger;\n }\n\n #subKey(topic: string, cb: EventCallback): string {\n let cbId = this.#cbIds.get(cb);\n if (!cbId) {\n cbId = randomUUID();\n this.#cbIds.set(cb, cbId);\n }\n return `${topic}::${cbId}`;\n }\n\n /** Lazily connect the shared writer client. Idempotent. */\n async #ensureWriterConnected(): Promise<void> {\n if (this.#writeClient.isOpen) return;\n await this.#writeClient.connect();\n }\n\n #streamKey(topic: string): string {\n return `${this.#keyPrefix}:${topic}`;\n }\n\n async publish(\n topic: string,\n event: Omit<Event, 'id' | 'createdAt'>,\n options?: { localOnly?: boolean },\n ): Promise<void> {\n if (this.#closed) throw new Error('RedisStreamsPubSub: cannot publish on closed client');\n\n // `localOnly` events stay entirely within the publishing process. They are\n // never serialized through Redis, so live values on the payload (e.g.\n // `MastraModelOutput` returned via `workflows-finish` for an evented\n // agent run) keep their prototype and methods intact.\n if (options?.localOnly) {\n const localEvent: Event = {\n ...event,\n id: randomUUID(),\n createdAt: new Date(),\n deliveryAttempt: event.deliveryAttempt ?? 1,\n };\n this.#deliverLocal(topic, localEvent);\n return;\n }\n\n await this.#ensureWriterConnected();\n\n const id = randomUUID();\n const createdAt = new Date();\n const payload: Event = {\n ...event,\n id,\n createdAt,\n deliveryAttempt: event.deliveryAttempt ?? 1,\n };\n const xaddOptions: { TRIM?: { strategy: 'MAXLEN'; strategyModifier: '~'; threshold: number } } = {};\n if (this.#maxStreamLength > 0) {\n xaddOptions.TRIM = {\n strategy: 'MAXLEN',\n strategyModifier: '~',\n threshold: this.#maxStreamLength,\n };\n }\n const promise = this.#writeClient.xAdd(\n this.#streamKey(topic),\n '*',\n { event: JSON.stringify(payload) },\n xaddOptions,\n );\n this.#pendingPublishes.add(promise);\n try {\n await promise;\n } finally {\n this.#pendingPublishes.delete(promise);\n }\n }\n\n async subscribe(topic: string, cb: EventCallback, options?: SubscribeOptions): Promise<void> {\n if (this.#closed) throw new Error('RedisStreamsPubSub: cannot subscribe on closed client');\n const key = this.#subKey(topic, cb);\n if (this.#subscriptions.has(key)) return; // idempotent: same (topic, cb) already subscribed\n\n // Register for `localOnly` delivery before wiring up the Redis reader so a\n // racing publisher in the same process never misses this subscriber.\n let localBucket = this.#localCallbacks.get(topic);\n if (!localBucket) {\n localBucket = new Set();\n this.#localCallbacks.set(topic, localBucket);\n }\n localBucket.add(cb);\n\n await this.#ensureWriterConnected();\n\n const isGrouped = !!options?.group;\n const group = options?.group ?? `__fanout-${randomUUID()}`;\n const consumer = `${group}-${randomUUID()}`;\n const streamKey = this.#streamKey(topic);\n\n // Create the consumer group if it doesn't exist. MKSTREAM creates the\n // stream if needed. BUSYGROUP means another subscriber raced us — fine.\n //\n // We anchor brand-new groups at '0' (stream start) instead of '$' so that\n // a worker which subscribes after a publish still sees the backlog. This\n // is the \"late join\" case: a server may publish workflow.start before any\n // orchestrator process exists. Without this, that work is silently lost.\n // Existing groups (BUSYGROUP path) keep their own checkpoint, so this\n // doesn't change semantics for already-running clusters. Stream growth is\n // bounded by the MAXLEN ~ trim applied on every publish.\n try {\n await this.#writeClient.xGroupCreate(streamKey, group, '0', { MKSTREAM: true });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes('BUSYGROUP')) throw err;\n this.#logger?.debug?.('redis-streams: consumer group already exists', { topic, group });\n }\n\n // Each subscription gets a dedicated reader connection because XREADGROUP\n // with BLOCK > 0 holds the connection until a message arrives.\n const readClient = createClient(this.#connectOptions) as RedisClientType;\n await readClient.connect();\n\n const sub: Subscription = {\n cb,\n topic,\n streamKey,\n group,\n consumer,\n isGrouped,\n readClient,\n stopped: false,\n loop: undefined,\n reclaimTimer: undefined,\n };\n this.#subscriptions.set(key, sub);\n sub.loop = this.#runReadLoop(sub);\n this.#startReclaimLoop(sub);\n }\n\n /**\n * Periodically run XAUTOCLAIM against this subscription's group so that\n * messages a crashed/stuck consumer read but never acked get redelivered to\n * a live sibling. Runs only for grouped subscriptions — fan-out groups are\n * private to one consumer, so there's no sibling to claim from.\n */\n #startReclaimLoop(sub: Subscription): void {\n if (this.#reclaimIntervalMs <= 0) return;\n if (!sub.isGrouped) return;\n\n const tick = async () => {\n if (sub.stopped || this.#closed) return;\n try {\n const reply = await this.#writeClient.xAutoClaim(\n sub.streamKey,\n sub.group,\n sub.consumer,\n this.#reclaimIdleMs,\n '0-0',\n { COUNT: 100 },\n );\n const messages = (reply?.messages ?? []) as Array<{ id: string; message: Record<string, string> } | null>;\n for (const entry of messages) {\n if (sub.stopped || this.#closed) return;\n if (!entry) continue;\n await this.#deliverMessage(sub, entry.id, entry.message);\n }\n } catch (err) {\n this.#logger?.debug?.('redis-streams: XAUTOCLAIM failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n }\n if (sub.stopped || this.#closed) return;\n sub.reclaimTimer = setTimeout(tick, this.#reclaimIntervalMs);\n };\n\n sub.reclaimTimer = setTimeout(tick, this.#reclaimIntervalMs);\n }\n\n async unsubscribe(topic: string, cb: EventCallback): Promise<void> {\n const localBucket = this.#localCallbacks.get(topic);\n if (localBucket) {\n localBucket.delete(cb);\n if (localBucket.size === 0) this.#localCallbacks.delete(topic);\n }\n\n const key = this.#subKey(topic, cb);\n const sub = this.#subscriptions.get(key);\n if (!sub) return;\n this.#subscriptions.delete(key);\n sub.stopped = true;\n if (sub.reclaimTimer) {\n clearTimeout(sub.reclaimTimer);\n sub.reclaimTimer = undefined;\n }\n\n // Cancel the in-flight blocking XREADGROUP by closing the reader.\n try {\n await sub.readClient.quit();\n } catch (err) {\n this.#logger?.debug?.('redis-streams: reader quit failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n\n if (sub.loop) {\n try {\n await sub.loop;\n } catch (err) {\n // loop exits naturally when readClient closes; surface only at debug.\n this.#logger?.debug?.('redis-streams: read loop exited with error', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n\n // For fan-out, drop the private group entirely so the stream can be reclaimed.\n if (!sub.isGrouped) {\n try {\n await this.#writeClient.xGroupDestroy(sub.streamKey, sub.group);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: xGroupDestroy failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n }\n\n async flush(): Promise<void> {\n // Wait for any in-flight publishes to settle.\n if (this.#pendingPublishes.size > 0) {\n await Promise.allSettled([...this.#pendingPublishes]);\n }\n }\n\n /**\n * Lease key used in Redis. Distinct prefix from streams so leases and\n * streams can't collide on key namespace.\n */\n #leaseKey(key: string): string {\n return `${this.#keyPrefix}:lease:${key}`;\n }\n\n /**\n * Atomic claim via SET NX PX. Idempotent for the same owner: if the\n * current value is already this owner, we refresh the TTL instead of\n * failing. Cross-process callers race here; Redis serializes them.\n */\n async acquireLease(key: string, owner: string, ttlMs: number): Promise<{ acquired: boolean; owner?: string }> {\n if (this.#closed) return { acquired: false };\n await this.#ensureWriterConnected();\n const redisKey = this.#leaseKey(key);\n const result = await this.#writeClient.set(redisKey, owner, { NX: true, PX: ttlMs });\n if (result === 'OK') return { acquired: true, owner };\n // Someone holds the key. Re-claim only if we still own it, refreshing the\n // TTL atomically: a bare GET+PEXPIRE would let us extend a *new* owner's\n // lease if the key expired and was re-acquired between the two calls.\n const script = `\n local current = redis.call(\"GET\", KEYS[1])\n if current == ARGV[1] then\n redis.call(\"PEXPIRE\", KEYS[1], ARGV[2])\n return 1\n end\n return 0\n `;\n const refreshed = await this.#writeClient.eval(script, {\n keys: [redisKey],\n arguments: [owner, String(ttlMs)],\n });\n if (refreshed === 1) return { acquired: true, owner };\n return { acquired: false, owner: (await this.#writeClient.get(redisKey)) ?? undefined };\n }\n\n async getLeaseOwner(key: string): Promise<string | undefined> {\n if (this.#closed) return undefined;\n await this.#ensureWriterConnected();\n const current = await this.#writeClient.get(this.#leaseKey(key));\n return current ?? undefined;\n }\n\n /**\n * Release only if we still own it. Implemented as GET+DEL with a Lua\n * script so the check-and-delete is atomic against concurrent renewals\n * from other processes.\n */\n async releaseLease(key: string, owner: string): Promise<void> {\n if (this.#closed) return;\n await this.#ensureWriterConnected();\n const script = `\n if redis.call(\"GET\", KEYS[1]) == ARGV[1] then\n return redis.call(\"DEL\", KEYS[1])\n else\n return 0\n end\n `;\n await this.#writeClient.eval(script, {\n keys: [this.#leaseKey(key)],\n arguments: [owner],\n });\n }\n\n /**\n * Extend the TTL only if we still own the lease. Returns false if the\n * lease was lost (expired or another owner took it).\n */\n async renewLease(key: string, owner: string, ttlMs: number): Promise<boolean> {\n if (this.#closed) return false;\n await this.#ensureWriterConnected();\n const script = `\n if redis.call(\"GET\", KEYS[1]) == ARGV[1] then\n return redis.call(\"PEXPIRE\", KEYS[1], ARGV[2])\n else\n return 0\n end\n `;\n const result = await this.#writeClient.eval(script, {\n keys: [this.#leaseKey(key)],\n arguments: [owner, String(ttlMs)],\n });\n return result === 1;\n }\n\n /**\n * Atomically hand the lease from `fromOwner` to `toOwner`, refreshing the\n * TTL to the full `ttlMs`, without the key ever going empty.\n *\n * Implemented as a single Lua script (GET == fromOwner -> SET toOwner PX)\n * so a racing process cannot win the key between a release and a re-acquire.\n * Returns false if `fromOwner` no longer holds the lease (expired or taken),\n * in which case the caller should fall back to a fresh `acquireLease`.\n */\n async transferLease(key: string, fromOwner: string, toOwner: string, ttlMs: number): Promise<boolean> {\n if (this.#closed) return false;\n await this.#ensureWriterConnected();\n const script = `\n if redis.call(\"GET\", KEYS[1]) == ARGV[1] then\n redis.call(\"SET\", KEYS[1], ARGV[2], \"PX\", ARGV[3])\n return 1\n else\n return 0\n end\n `;\n const result = await this.#writeClient.eval(script, {\n keys: [this.#leaseKey(key)],\n arguments: [fromOwner, toOwner, String(ttlMs)],\n });\n return result === 1;\n }\n\n /**\n * Disconnect all clients and stop all subscription loops.\n */\n async close(): Promise<void> {\n if (this.#closed) return;\n this.#closed = true;\n\n // Walk the actual subscriptions and pass the original topic through so\n // unsubscribe's key lookup works.\n const subs = [...this.#subscriptions.values()];\n await Promise.all(subs.map(sub => this.unsubscribe(sub.topic, sub.cb)));\n this.#localCallbacks.clear();\n\n if (this.#writeClient.isOpen) {\n try {\n await this.#writeClient.quit();\n } catch (err) {\n this.#logger?.debug?.('redis-streams: writer quit failed', {\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n }\n\n async #runReadLoop(sub: Subscription): Promise<void> {\n while (!sub.stopped) {\n let result;\n try {\n result = await sub.readClient.xReadGroup(sub.group, sub.consumer, [{ key: sub.streamKey, id: '>' }], {\n COUNT: 10,\n BLOCK: this.#blockMs,\n });\n } catch (err) {\n if (sub.stopped) return;\n this.#logger?.debug?.('redis-streams: xReadGroup failed', {\n topic: sub.topic,\n group: sub.group,\n err: err instanceof Error ? err.message : err,\n });\n // Connection error or similar — pause briefly then retry.\n await new Promise(r => setTimeout(r, 100));\n continue;\n }\n\n if (!result || result.length === 0) continue;\n\n for (const stream of result) {\n for (const entry of stream.messages) {\n if (sub.stopped) return;\n await this.#deliverMessage(sub, entry.id, entry.message);\n }\n }\n }\n }\n\n async #deliverMessage(sub: Subscription, streamId: string, fields: Record<string, string>): Promise<void> {\n let event: Event;\n try {\n event = JSON.parse(fields.event ?? '{}') as Event;\n // createdAt is serialized as a string; rehydrate.\n if (typeof event.createdAt === 'string') {\n event.createdAt = new Date(event.createdAt);\n }\n } catch (err) {\n this.#logger?.debug?.('redis-streams: malformed payload, dropping', {\n topic: sub.topic,\n streamId,\n err: err instanceof Error ? err.message : err,\n });\n try {\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (ackErr) {\n this.#logger?.debug?.('redis-streams: xAck after malformed payload failed', {\n topic: sub.topic,\n err: ackErr instanceof Error ? ackErr.message : ackErr,\n });\n }\n return;\n }\n\n let settled = false;\n const ack = async () => {\n if (settled) return;\n settled = true;\n try {\n // Only ack against this consumer group. Do NOT xDel: the stream may\n // be consumed by other groups, and xDel removes the entry for all of\n // them. Stream growth is bounded elsewhere via MAXLEN-style trimming.\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: ack cleanup failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n };\n const nack = async () => {\n if (settled) return;\n settled = true;\n const attempt = event.deliveryAttempt ?? 1;\n // Cap redelivery to avoid an infinite poison-pill loop. When the cap\n // is hit we drop the event (xAck without republish) and warn so an\n // operator can find it in logs.\n if (attempt >= this.#maxDeliveryAttempts) {\n this.#logger?.warn?.('redis-streams: dropping event after max delivery attempts', {\n topic: sub.topic,\n eventType: event.type,\n eventId: event.id,\n attempt,\n max: this.#maxDeliveryAttempts,\n });\n try {\n // Group-scoped ack only — see ack() above for why we never xDel.\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: ack on dropped poison message failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n return;\n }\n // Republish with incremented deliveryAttempt FIRST, then ack the\n // original entry. If the republish fails we deliberately leave the\n // original message pending so XAUTOCLAIM (or another consumer) can\n // pick it up on a future tick — acking first would silently drop it.\n const next: Event = {\n ...event,\n deliveryAttempt: attempt + 1,\n };\n try {\n await this.#writeClient.xAdd(sub.streamKey, '*', { event: JSON.stringify(next) });\n } catch (err) {\n this.#logger?.warn?.('redis-streams: nack republish failed; leaving original pending for reclaim', {\n topic: sub.topic,\n eventId: event.id,\n err: err instanceof Error ? err.message : err,\n });\n // Allow this entry to be redelivered: reset settled so the next\n // delivery attempt (via XAUTOCLAIM) can ack/nack it again.\n settled = false;\n return;\n }\n try {\n await this.#writeClient.xAck(sub.streamKey, sub.group, streamId);\n } catch (err) {\n this.#logger?.debug?.('redis-streams: xAck after nack failed', {\n topic: sub.topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n };\n\n try {\n // EventCallback is typed `=> void` but handlers commonly return a\n // promise (TS allows Promise<void> to satisfy void). If we get one\n // back, attach a catch handler so async rejections route to nack\n // instead of silently dropping the message. We do NOT await here —\n // serializing messages on a subscription would deadlock orchestration\n // callbacks that await their own future events.\n const result: unknown = sub.cb(event, ack, nack);\n if (result && typeof (result as { then?: unknown; catch?: unknown }).catch === 'function') {\n (result as Promise<unknown>).catch(async () => {\n await nack();\n });\n }\n } catch {\n // Caller threw synchronously — treat as nack.\n await nack();\n }\n }\n\n #deliverLocal(topic: string, event: Event): void {\n const callbacks = this.#localCallbacks.get(topic);\n if (!callbacks || callbacks.size === 0) return;\n for (const cb of callbacks) {\n this.#invokeLocalCallback(topic, event, cb);\n }\n }\n\n #invokeLocalCallback(topic: string, event: Event, cb: EventCallback): void {\n // `localOnly` deliveries don't have an external broker to redeliver from,\n // so ack/nack are no-ops here. The caller still gets a real Event object\n // and can branch on `deliveryAttempt` if it cares.\n const ack = async () => {};\n const nack = async () => {};\n try {\n const result: unknown = (cb as (event: Event, ack: () => Promise<void>, nack: () => Promise<void>) => unknown)(\n event,\n ack,\n nack,\n );\n if (result && typeof (result as { then?: unknown; catch?: unknown }).catch === 'function') {\n (result as Promise<unknown>).catch(err => {\n this.#logger?.debug?.('redis-streams: local subscriber rejected', {\n topic,\n err: err instanceof Error ? err.message : err,\n });\n });\n }\n } catch (err) {\n this.#logger?.debug?.('redis-streams: local subscriber threw', {\n topic,\n err: err instanceof Error ? err.message : err,\n });\n }\n }\n}\n\ninterface Subscription {\n cb: EventCallback;\n topic: string;\n streamKey: string;\n group: string;\n consumer: string;\n isGrouped: boolean;\n readClient: RedisClientType;\n stopped: boolean;\n loop: Promise<void> | undefined;\n reclaimTimer: ReturnType<typeof setTimeout> | undefined;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/redis-streams",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-alpha.1",
|
|
4
4
|
"description": "Mastra Redis Streams PubSub integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"redis": "5.12.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
+
"@ai-sdk/openai-v5": "npm:@ai-sdk/openai@2.0.106",
|
|
30
|
+
"@copilotkit/aimock": "^1.29.0",
|
|
29
31
|
"@types/node": "22.19.21",
|
|
30
32
|
"@vitest/coverage-v8": "4.1.8",
|
|
31
33
|
"@vitest/ui": "4.1.8",
|
|
@@ -34,11 +36,13 @@
|
|
|
34
36
|
"tsx": "^4.22.4",
|
|
35
37
|
"typescript": "^6.0.3",
|
|
36
38
|
"vitest": "4.1.8",
|
|
37
|
-
"
|
|
38
|
-
"@
|
|
39
|
-
"@mastra/deployer": "1.45.0",
|
|
39
|
+
"zod": "^4.4.3",
|
|
40
|
+
"@internal/ai-sdk-v5": "0.0.54",
|
|
40
41
|
"@internal/types-builder": "0.0.82",
|
|
41
|
-
"@mastra/core": "1.
|
|
42
|
+
"@mastra/core": "1.46.0-alpha.4",
|
|
43
|
+
"@internal/lint": "0.0.107",
|
|
44
|
+
"@mastra/deployer": "1.46.0-alpha.4",
|
|
45
|
+
"@mastra/libsql": "1.14.1-alpha.1"
|
|
42
46
|
},
|
|
43
47
|
"peerDependencies": {
|
|
44
48
|
"@mastra/core": ">=1.0.0-0 <2.0.0-0"
|