@mastra/redis-streams 0.2.0-alpha.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # @mastra/redis-streams
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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))
8
+
9
+ 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:
10
+
11
+ ```ts
12
+ const result = agent.sendSignal(signal, { resourceId, threadId });
13
+ ctx.waitUntil(
14
+ result.accepted.then(async accepted => {
15
+ if (accepted.action === 'wake') await accepted.output.consumeStream();
16
+ }),
17
+ );
18
+ ```
19
+
20
+ 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.
21
+
22
+ 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).
23
+
24
+ ### Patch Changes
25
+
26
+ - 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))
27
+
28
+ 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.
29
+
30
+ - 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), [`65f255a`](https://github.com/mastra-ai/mastra/commit/65f255a38667beb6ceeadabfa9eb5059bfec8298), [`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), [`5c4e9a4`](https://github.com/mastra-ai/mastra/commit/5c4e9a4cfb2216bb3ea7f8988ad3727f3b92bb3a), [`4a88c6e`](https://github.com/mastra-ai/mastra/commit/4a88c6e2bdce316f8d7551b4ec3449b0b06fc71c), [`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), [`25961e3`](https://github.com/mastra-ai/mastra/commit/25961e3260ff3b1464637af8fcdb36210551c39f), [`6a1428a`](https://github.com/mastra-ai/mastra/commit/6a1428a23133fc070fc6c1caa08d28f3ba4fe5ff), [`87a17ef`](https://github.com/mastra-ai/mastra/commit/87a17efbd725aca6639febdc5e69e2abb3048689), [`e11ff30`](https://github.com/mastra-ai/mastra/commit/e11ff301408bf1731dca2fb7fbfcd8c819500a35), [`7794d71`](https://github.com/mastra-ai/mastra/commit/7794d71872c68733a30e028dfb7b1705daf6c5d2), [`9d2c946`](https://github.com/mastra-ai/mastra/commit/9d2c946d0859e90ae4bcec5beeb1da7398d2ad1e), [`c0eda2b`](https://github.com/mastra-ai/mastra/commit/c0eda2bcd91a228427314b12c91d8b147f3a739f), [`7b29f33`](https://github.com/mastra-ai/mastra/commit/7b29f332a357a83e555f29e718e5f2fab9979943), [`c0eda2b`](https://github.com/mastra-ai/mastra/commit/c0eda2bcd91a228427314b12c91d8b147f3a739f), [`b13925b`](https://github.com/mastra-ai/mastra/commit/b13925bfa91aa8700f56fa54a9ce707ee7e4ba62), [`f1ec385`](https://github.com/mastra-ai/mastra/commit/f1ec385386f62b1a0847ec5353ae2bb169d1c3d9), [`e14986f`](https://github.com/mastra-ai/mastra/commit/e14986f6e5478d6384d04ff9a7f9a79a46a8b529), [`24912b1`](https://github.com/mastra-ai/mastra/commit/24912b1f855d29ec36af4ef4bde1f7417e20cdf5), [`bf94ec6`](https://github.com/mastra-ai/mastra/commit/bf94ec68192d9f16e46ef7e5ac36370aeeddf35d), [`a29f371`](https://github.com/mastra-ai/mastra/commit/a29f371aef629ac8562661524a497127e93b5131), [`7686216`](https://github.com/mastra-ai/mastra/commit/7686216f37e74568feddec17cef3c3d24e10e60a), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`073f910`](https://github.com/mastra-ai/mastra/commit/073f910481e7d94b95ba3830f96531774ae95d33), [`0be490f`](https://github.com/mastra-ai/mastra/commit/0be490fabb538c5a7de796ea0aff7d04a0bea1f3), [`0be490f`](https://github.com/mastra-ai/mastra/commit/0be490fabb538c5a7de796ea0aff7d04a0bea1f3), [`ebbe1d3`](https://github.com/mastra-ai/mastra/commit/ebbe1d31a965a3adb0e728758f326b8122b4b55f), [`974f614`](https://github.com/mastra-ai/mastra/commit/974f614e083bd68278536f94453f7b320b86a3c7), [`3818814`](https://github.com/mastra-ai/mastra/commit/38188149ce454c4403fe9fcbdf73b735c68d36be), [`975c59a`](https://github.com/mastra-ai/mastra/commit/975c59ae363ee275fc55062392e1ffd2cbccbd53), [`1f97ce5`](https://github.com/mastra-ai/mastra/commit/1f97ce5695463bebb4eaacf501da6fb403e20885), [`74955f9`](https://github.com/mastra-ai/mastra/commit/74955f9120cde8b1d8ce4399232b4033236be858), [`7f51548`](https://github.com/mastra-ai/mastra/commit/7f515481213780be7047cef00640b9d35f3d545c), [`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), [`d95f394`](https://github.com/mastra-ai/mastra/commit/d95f394fd24c8411886930d727679c4d5252aa26), [`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), [`f3f0c9d`](https://github.com/mastra-ai/mastra/commit/f3f0c9d7c878db5a13177871ce3523a14f14b311), [`a5b22d3`](https://github.com/mastra-ai/mastra/commit/a5b22d314d62a68d801886a8d3d0eb6c089473db), [`31be1cf`](https://github.com/mastra-ai/mastra/commit/31be1cf5f2a7b5eef12f6123a40653b4d8115c16), [`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)]:
31
+ - @mastra/core@1.46.0
32
+
33
+ ## 0.2.0-alpha.1
34
+
35
+ ### Patch Changes
36
+
37
+ - 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))
38
+
39
+ 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.
40
+
41
+ - 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)]:
42
+ - @mastra/core@1.46.0-alpha.4
43
+
3
44
  ## 0.2.0-alpha.0
4
45
 
5
46
  ### 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;
@@ -328,6 +355,7 @@ var RedisStreamsPubSub = class extends events.PubSub {
328
355
  this.#closed = true;
329
356
  const subs = [...this.#subscriptions.values()];
330
357
  await Promise.all(subs.map((sub) => this.unsubscribe(sub.topic, sub.cb)));
358
+ this.#localCallbacks.clear();
331
359
  if (this.#writeClient.isOpen) {
332
360
  try {
333
361
  await this.#writeClient.quit();
@@ -458,6 +486,39 @@ var RedisStreamsPubSub = class extends events.PubSub {
458
486
  await nack();
459
487
  }
460
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
+ }
461
522
  };
462
523
 
463
524
  exports.RedisStreamsPubSub = RedisStreamsPubSub;
@@ -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, 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 #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 * 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\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
@@ -60,7 +60,9 @@ 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'>): Promise<void>;
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>;
@@ -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,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;gBAmBW,OAAO,GAAE,wBAA6B;IA2C5C,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAkC7E,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6FtF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA+C5D,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;CAuK7B"}
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;
@@ -326,6 +353,7 @@ var RedisStreamsPubSub = class extends PubSub {
326
353
  this.#closed = true;
327
354
  const subs = [...this.#subscriptions.values()];
328
355
  await Promise.all(subs.map((sub) => this.unsubscribe(sub.topic, sub.cb)));
356
+ this.#localCallbacks.clear();
329
357
  if (this.#writeClient.isOpen) {
330
358
  try {
331
359
  await this.#writeClient.quit();
@@ -456,6 +484,39 @@ var RedisStreamsPubSub = class extends PubSub {
456
484
  await nack();
457
485
  }
458
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
+ }
459
520
  };
460
521
 
461
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, 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 #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 * 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\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.2.0-alpha.0",
3
+ "version": "0.2.0",
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
- "@internal/lint": "0.0.107",
38
- "@internal/types-builder": "0.0.82",
39
- "@mastra/core": "1.46.0-alpha.0",
40
- "@mastra/deployer": "1.46.0-alpha.0",
41
- "@mastra/libsql": "1.14.0"
39
+ "zod": "^4.4.3",
40
+ "@internal/lint": "0.0.108",
41
+ "@mastra/core": "1.46.0",
42
+ "@mastra/libsql": "1.14.1",
43
+ "@internal/ai-sdk-v5": "0.0.55",
44
+ "@mastra/deployer": "1.46.0",
45
+ "@internal/types-builder": "0.0.83"
42
46
  },
43
47
  "peerDependencies": {
44
48
  "@mastra/core": ">=1.0.0-0 <2.0.0-0"